ztbuz@dev

人生に絶望しているZが、それでも技術を身につけようと必死になるブログ

Ruby on RailsのStrong Parametersの使い方

Ruby on Rails 4から、Strong Parametersというものが導入されたのですね。 これはRails 3でGitHubがMass Assignment脆弱性なるものを突かれた、という経緯があるらしいです。

このStrong Parametersの使い方についてまとめました。 もちろん自分のためです。。。

Mass Assignment脆弱性とは

たとえば、以下の内容がフォームからPOSTされたとします。

p params[:user] # => { :name => "foo", :email => "foo@foo.com" }

それを、このようにしてupdateしたとします。

@user = User.find(params[:id])
if @user.update_attributes(params[:user])
  # 処理
end

このままだと特に問題ないように思えます。 ……が、不正なフォームの使用などにより、以下のような内容がPOSTされるとします。

p params[:user] # => { :name => "foo", :email => "foo@foo.com", :token => "XXXXXXXXXXXXXXXX" }

Rails 3まではattr_accessibleで受け取るパラメータをホワイトリスト指定できたのですが、これを知らない開発者は:tokenの更新を許容してしまう、という問題です(たぶん)。

これを防ぐのがStrong Parameters、という訳です。

Strong Parametersとは

以下のように、params[:user]を直接渡すのではなく、以下のように一度フィルタリングします。

@user = User.find(params[:id])
if @user.update_attributes(user_params)
  # 処理
end

private

  def user_params
    params.require(:user).permit(:name, :email)
  end

requireは、ここで指定したキーを持っている場合のみ値を返します。 いわば一次フィルタです。

permitは、ここで指定したシンボルのみを返します。 先ほどの不正な値がPOSTされた場合でも、ここで:tokenpermitしていなければ、値が変えられることはない、ということです。

朗報だね(ニッコリ)。

参考記事

Ruby on Railsのflashの使い方

Ruby on Railsを学んでいると度々出てくるflashについて、しっかり学ぼうと思い、使い方をまとめてみました。

基本的な使い方

コントローラでflashをセット

まず、以下のようにflashにメッセージをセットします。

flash[:success] = 'アカウントを作成しました。'
redirect_to root_url

# このようにも書ける
# redirect_to root_url, :flash => { success: 'アカウントを作成しました。' }

ビューでflashを表示

あとは、以下のようにflashを表示させるだけです。 content_tagを使うとスッキリします。

<% flash.each do |key, value| %>
<%= content_tag :div, value, class: "flash flash-#{key}" %>
<% end %>

生成されるHTML

生成されるHTMLは以下のようになります。

<div class="flash flash-success">アカウントを作成しました。</div>

flashのメソッド

以下に詳しいのですが、特徴的なものをいくつか書きます。

flash.now

flash.nowは、そのアクションでflashを消す際に用います。 たとえば、先ほど以下のように書きました。

flash[:success] = 'アカウントを作成しました。'

普通はこれでよいのですが、たとえばrenderでビューを指定すると、アクションが進む訳ではないので、flashが2回表示されることになります。 これを避けるために、flash.nowを用います。

flash.now[:success] = 'アカウントを作成しました。'

flash.keep

これは、flashを保持したいときに用います。 通常であれば消えるところを、flash.keepを用いることによって、次のアクションまで保持することができます。

flash.keep # flash 全体を保持する
flash.keep(:success) # :success のみを保持する

flash.discard

discardは「破棄する」という意味ですが、その名のとおりflashを破棄します。

flash.discard # flash 全体を破棄する
flash.discard(:success) # :success のみを破棄する

おわりに

flashを効果的に使うとユーザビリティが向上すると思うので、しっかり使いこなせるようになります。。。

Ruby on Railsでユーザ登録/ログイン機能を自作する方法

先日Ruby on Railsチュートリアルを完了しました。 疲れた。。。

それで、ちょっとごちゃごちゃしてきたので、ユーザ登録〜ログイン機能までの実装のみを抜き出して自分なりに整理してみました。

ちなみに今回長くなるのでバリデーションやテストは省略しています。

準備

ディレクトリ構成

編集するファイルは以下のような感じです。

app/
  controllers/
    application_controller.rb
    sessions_controller.rb
    users_controller.rb
  helpers/
    sessions_helper.rb
  models/
    user.rb
  views/
    layouts/
      application.html.erb
    sessions/
      new.html.erb
    users/
      edit.html.erb
      index.html.erb
      new.html.erb
      show.html.erb
config/
  routes.rb

テーブル

テーブルは以下のようにUserモデルのみ利用します。 ちなみにイカの頭以下の図はRails ERDを用いて生成しました(なんつー誤字だ……)。

f:id:ztbuz:20131229090804p:plain

上記を準備するコマンド

とりあえずパパパパーンッと上記を準備します。

$ rails new Login
$ cd Login
$ rails g controller Sessions
$ rails g controller Users
$ rails g model User name:string email:string password_digest:string remember_token:string
$ touch app/views/sessions/new.html.erb
$ touch app/views/users/edit.html.erb
$ touch app/views/users/index.html.erb
$ touch app/views/users/show.html.erb
$ touch app/views/users/new.html.erb
$ bundle exec rake db:migrate
$ vi Gemfile # bcrypt-ruby をコメントアウト
$ bundle install

各ファイル

各ファイルの中身と、コメントをちらほら記します。 それではディレクトリ構成の上から順に(適当)。

application_controller.rb

コントローラ全体からSessionsHelperを呼び出すことになるので、ここでこれをincludeしておきます。 以上。

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper
end

sessions_controller.rb

Sessionコントローラはその名のとおりセッションを司ります。 RESTな考えでいくと、newはログインフォーム、createはログイン処理、destroyはログアウトですね。

class SessionsController < ApplicationController
  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      sign_in user
      redirect_to user
    else
      render 'new'
    end
  end

  def destroy
    sign_out
    redirect_to root_url
  end
end

users_controller.rb

こちらはよくあるUserコントローラです。 今回はeditupdateのみ権限を課しているので、before_actionでそれっぽいことをします。

class UsersController < ApplicationController
  before_action :signed_in_user, only: [:edit, :update]
  before_action :correct_user, only: [:edit, :update]

  def index
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end

  def edit
  end

  def create
    @user = User.new(user_params)
    if @user.save
      sign_in @user
      redirect_to @user
    else
      render 'new'
    end
  end

  def update
    if @user.update_attributes(user_params)
      redirect_to @user
    else
      render 'edit'
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password, :password_confirmation)
    end

    def correct_user
      @user = User.find(params[:id])
      redirect_to root_url unless current_user?(@user)
    end
end

sessions_helper.rb

今回のメインっぽい部分。 ログイン時にトークンを更新して、以降は該当するトークンをfindしてcurrent_userにセットします。

この辺でインスタンスメソッドとクラスメソッドの理解がだいぶ深まりました。。。

module SessionsHelper
  def sign_in(user)
    remember_token = User.new_remember_token
    cookies.permanent[:remember_token] = remember_token
    user.update_attribute(:remember_token, User.encrypt(remember_token))
    self.current_user = user
  end

  def sign_out
    self.current_user = nil
    cookies.delete(:remember_token)
  end

  def current_user=(user)
    @current_user = user
  end

  def current_user
    remember_token = User.encrypt(cookies[:remember_token])
    @current_user ||= User.find_by(remember_token: remember_token)
  end

  def current_user?(user)
    user == current_user
  end

  def signed_in?
    !current_user.nil?
  end

  def signed_in_user
    redirect_to signin_url unless signed_in?
  end
end

user.rb

Userモデル。 このhas_secure_passwordがいろいろよしなにやってくれます。 というかやってくれ過ぎでしょ。。。

後ろ2つはインスタンスに関係ない部分なのでクラスメソッドとして実装しています。

class User < ActiveRecord::Base
  before_save { email.downcase! }

  has_secure_password

  def self.new_remember_token
    SecureRandom.urlsafe_base64
  end

  def self.encrypt(token)
    Digest::SHA1.hexdigest(token.to_s)
  end
end

application.html.erb

ここからはただのViewです。 application_controller.rbSessionsHelperincludeしたので、signed_in?で便利に振り分けることができます。

<!DOCTYPE html>
<html>
<head>
  <title>Login</title>
  <%= stylesheet_link_tag    "application", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>
</head>
<body>

<ul>
  <li><%= link_to 'ユーザ一覧', users_path %></li>
  <% if signed_in? %>
  <li><%= link_to 'プロフィール', current_user %></li>
  <li><%= link_to 'ログアウト', signout_path, method: :delete %></li>
  <% else %>
  <li><%= link_to 'ログイン', signin_path %></li>
  <li><%= link_to 'アカウント作成', signup_path %></li>
  <% end %>
</ul>

<hr>

<%= yield %>

</body>
</html>

sessions/new.html.erb

何の変哲もないViewです。 Sessionはモデルがないので、form_forがちょっと特殊です。

<h1>ログイン</h1>

<%= form_for(:session, url: sessions_path) do |f| %>

  <table border="1">
    <tr>
      <th><%= f.label :email, 'メールアドレス' %></th>
      <td><%= f.text_field :email %></td>
    </tr>
    <tr>
      <th><%= f.label :password, 'パスワード' %></th>
      <td><%= f.password_field :password %></td>
    </tr>
  </table>

  <%= f.submit 'ログインする' %>

<% end %>

users/edit.html.erb

これもただのViewです。

<h1>アカウント編集</h1>

<%= form_for(@user) do |f| %>

  <table border="1">
    <tr>
      <th><%= f.label :name, '名前' %></th>
      <td><%= f.text_field :name %></td>
    </tr>
    <tr>
      <th><%= f.label :email, 'メールアドレス' %></th>
      <td><%= f.text_field :email %></td>
    </tr>
    <tr>
      <th><%= f.label :password, 'パスワード' %></th>
      <td><%= f.password_field :password %></td>
    </tr>
    <tr>
      <th><%= f.label :password_confirmation, '確認' %></th>
      <td><%= f.password_field :password_confirmation %></td>
    </tr>
  </table>

  <%= f.submit 'アカウントを編集する' %>

<% end %>

users/index.html.erb

ただ一覧を表示するだけです。 link_touserを渡すとshowしてくれるらしいです。 この辺のRailsのノリについていけない。。。

<h1>ユーザ一覧</h1>

<ul>
  <% @users.each do |user| %>
  <li><%= link_to user.name, user %></li>
  <% end %>
</ul>

users/new.html.erb

そのうちPartialの記事も書きます。。。

<h1>アカウント作成</h1>

<%= form_for(@user) do |f| %>

  <table border="1">
    <tr>
      <th><%= f.label :name, '名前' %></th>
      <td><%= f.text_field :name %></td>
    </tr>
    <tr>
      <th><%= f.label :email, 'メールアドレス' %></th>
      <td><%= f.text_field :email %></td>
    </tr>
    <tr>
      <th><%= f.label :password, 'パスワード' %></th>
      <td><%= f.password_field :password %></td>
    </tr>
    <tr>
      <th><%= f.label :password_confirmation, '確認' %></th>
      <td><%= f.password_field :password_confirmation %></td>
    </tr>
  </table>

  <%= f.submit 'アカウントを作成する' %>

<% end %>

users/show.html.erb

自分のプロフィールページのみ、編集リンクを表示するようにします。

<h1>ユーザ情報</h1>

<table border="1">
  <tr>
    <th>名前</th>
    <td><%= @user.name %></td>
  </tr>
  <tr>
    <th>メールアドレス</th>
    <td><%= @user.email %></td>
  </tr>
</table>

<% if current_user?(@user) %>
<p><%= link_to '編集', edit_user_path %></p>
<% end %>

routes.rb

この辺は無抵抗に書きます。

Login::Application.routes.draw do
  resources :users, only: [:index, :show, :new, :edit, :create, :update]
  resources :sessions, only: [:new, :create, :destroy]

  match 'signup', to: 'users#new', via: 'get'
  match 'signin', to: 'sessions#new', via: 'get'
  match 'signout', to: 'sessions#destroy', via: 'delete'

  root 'users#index'
end

おわりに

以上です。 便利なGemにdeviseとかcancanとかあるらしいですが、私みたいなシロウトはまず自分で書いて理解しなければならないと思うのです。。。

Rubyのor equal演算子の使い方

Rubyには、以下のような書き方があるらしいです。

a ||= b

これはor equal演算子と呼ぶらしいのですが、これについて動きなどを調べてみたのでメモします。

or equal演算子について

コード例

以下のコードについて、or_equal(name)の部分がそれにあたります。 これはつまり、インスタンス変数@nameが存在すればそれを返し、なければ引数のnameを返す、という動きになります。

class Foo
  def name=(name)
    @name = name
  end

  def or_equal(name)
    @name ||= name
  end
end

インスタンス変数がない場合

name=(name)インスタンス変数を生成しない場合、そのままor_equal(name)の引数が返ります。

foo = Foo.new
p foo.or_equal('foo') # => "foo"

インスタンス変数がある場合

name=(name)インスタンス変数を生成した場合、or_equal(name)の引数によらず、@nameが返されます。

foo.name = 'bar'
p foo.or_equal('foo') # => "bar"

おわりに

使い方が分かりやすいですし、使いこなせればコードがシンプルになると思うので、積極的に使っていこうと思います。 まずはコードを書く時間を確保しよう。。。

Rubyのイコールを用いた要素代入関数の書き方

Rubyでは、関数名にイコールを使えると風のうわさで聞きました。 その書き方についてのメモです。

要素代入関数とは

コード例

以下のようなコードがあるとします。 この中のname=要素代入関数というらしいです。

要素代入関数とは、簡単にいうと「代入演算子=)を用いて引数を渡せる関数」だと思います。 たぶん……。

class Foo
  def name=(name)
    @name = name
  end

  def name
    @name
  end
end

代入する

上のコード例について、インスタンスの生成〜代入までは以下のように書きます。 こうすると、@nameというインスタンス変数が生成されます。

foo = Foo.new
foo.name = 'foo'

インスタンス変数を表示する

で、こうすると、インスタンス変数が表示される、という訳です。 同じnameという名前を持つ関数なのに、=の有無で違うメソッドが呼び出されるんですね(当たり前)。

p foo.name # => "foo"

おわりに

Rubyは書き方がいろいろあって、PHPすらまともに書いたことのない私は心底絶望しています。。。

Rubyのインスタンスメソッドとクラスメソッドの使い分けについて

Ruby on Railsの学習中にModel周りで悩んでしまったのでメモ。 ひとことで言うと「クラス内のdef self.〜ってなんぞ?」という話についてです。

インスタンスメソッドとクラスメソッドの使い分け

コード例

たとえば、以下のようなコードがあるとします。 このとき、self.barクラスメソッドbazインスタンスメソッドとなります。

class Foo
  def self.bar
    p 'bar'
  end

  def baz
    p 'baz'
  end
end

インスタンスメソッド

インスタンスメソッドとは、newインスタンスを生成したときにのみ呼び出せるメソッドです。 生成するインスタンスによって異なる結果を返す場合に用いる、といったところでしょうか(となると上のコード例は微妙ですね……)。

foo = Foo.new
foo.bar # => undefined method `bar' for #<Foo:0x007f7f6413aae8> (NoMethodError)
foo.baz # => "baz"

クラスメソッド

クラスメソッドとは、ダブルコロン::)によってクラス経由で直接呼び出すメソッドです。 返る結果がインスタンスによらず一定の場合に用いる、という感じだと思います。

Foo::bar # => "bar"
Foo::baz # => undefined method `baz' for Foo:Class (NoMethodError)

また、インスタンスメソッドからクラスメソッドを呼び出すには、以下のように書きます。

def baz
  self.class.bar # => "bar"
end

おわりに

Javaを少し学んでいたときに曖昧にしていたのですが、改めて学習したことで理解が深まりました。 さて、次の記事を書き始めます。。。

参考記事

Rubyの関数名の感嘆符「!」と疑問符「?」の意味

Ruby on Railsをお勉強中なのです。 ところどころ出てくるメソッドの!?について、すこし整理してみます。

感嘆符「!」

これは破壊的メソッドを表します。 Rubyのリファレンスには、以下のようにあります。

「!」はメソッド名の一部です。慣用的に、 同名の(! の無い)メソッドに比べてより破壊的な作用をもつメソッド(例: tr と tr!)で使われます。

例えば、sort関数の場合、以下のようになります。

array = [3, 1, 5, 2, 4]

a = array.sort
p a     # => [1, 2, 3, 4, 5]
p array # => [3, 1, 5, 2, 4]

b = array.sort!
p b     # => [1, 2, 3, 4, 5]
p array # => [1, 2, 3, 4, 5]

一言で言うと「メソッドを呼びだしたオブジェクト自身が影響を受ける」、という感じでしょうか。。。

疑問符「?」

これは真偽値を返すメソッドを表します。 Rubyのリファレンスには、以下のようにあります。

この場合の「?」はメソッド名の一部分です。 慣用的に、真偽値を返すタイプのメソッドを示すために使われます。

これは直感的に分かりやすいです。

p @foo.nil? # => true

@foo = 'foo'
p @foo.nil? # => false

おわりに

自分でメソッドを定義するときは、この2点をしっかりおさえて作るようにします。。。