【112日目】【1日20分のRailsチュートリアル】【第9章】Strong Parametersでadmin属性の変更をガードする
今日は「9.4.1 管理ユーザー」の「Strong Parameters、再び」から。
9.4.1 管理ユーザー
Strong Parameters、再び
任意のWebリクエストの初期化ハッシュをオブジェクトに渡せるとなると、攻撃者は以下のようなPATCHリクエストを送信してくるかもしれません。
patch /users/17?admin=1
リクエスト送られて改ざんできるのはやばいね。。。ガードしておかないと。
以下のようにparamsハッシュに対してrequireとpermitを呼び出します。
app/controllers/users_controller.rb
def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end
上記コードは7.3.2で追加済み。
上のコードでは、許可された属性リストにadminが含まれていないことに注目してください。これにより、任意のユーザーが自分自身にアプリケーションの管理者権限を与えることを防止できます。
許可された属性リストにadmin入れてたら変更できちゃうのか。
アプリケーションに登録した(悪意のある)任意のユーザーが自分自身に管理者権限を与えて好き放題する、ということを防げるんだね。
前の内容も読み返してたら時間かかった。
今日の学習時間は【18分】。
次は「9.4.2 destroyアクション」から。
【111日目】【1日20分のRailsチュートリアル】【第9章】管理ユーザーを識別するadmin属性を追加する
今日は「9.4 ユーザーを削除する」から。
9.4 ユーザーを削除する
削除を行うのに必要なdestroyアクションも実装します。しかしその前に、削除を実行できる権限を持つ管理 (admin) ユーザーのクラスを作成しましょう。
indexアクションの追加が終わったので次はdestroyアクション。
の前にユーザー削除を実行できる権限を持った管理ユーザーを実装する。
9.4.1 管理ユーザー
特権を持つ管理ユーザーを識別するために、論理値をとるadmin属性をUserモデルに追加します。
マイグレーションを実行してUserモデルにadmin属性を追加。
$ rails generate migration add_admin_to_users admin:boolean invoke active_record create db/migrate/20170215024617_add_admin_to_users.rb
リスト9.50では、default: falseという引数をadd_columnに追加しています。これは、デフォルトでは管理者になれないということを示すためです。
default: false
は追加しなくてもデフォルトで管理者になれないらしいけど、明記しておくことでRailsと開発者にコードの意図を示せるらしい。
というわけでadd_cloumにdefault: false
を追加。
db/migrate/20170215024617_add_admin_to_users.rb
class AddAdminToUsers < ActiveRecord::Migration def change add_column :users, :admin, :boolean, default: false end end
マイグレーションを更新できたら実行する。
$ bundle exec rake db:migrate : -- add_column(:users, :admin, :boolean, {:default=>false}) -> 0.0051s :
admin属性追加できました。
Rails consoleで動作を確認すると、期待どおりadmin属性が追加されて論理値をとり、さらに疑問符の付いたadmin?メソッドも利用できるようになっています。
Rails consoleでadmin?
メソッドが使えることを確認。
$ rails console --sandbox >> user = User.first >> user.admin? => false >> user.toggle!(:admin) => true >> user.admin? => true
仕上げに、最初のユーザーだけをデフォルトで管理者にするようサンプルデータを更新しましょう。
最初のテストユーザーだけadmin: true
を追加。
db/seeds.rb
User.create!(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar", admin: true) :
次に、データベースをリセットして、サンプルデータを再度生成します。
$ bundle exec rake db:migrate:reset $ bundle exec rake db:seed
サンプルデータを再生成しました。
これで最初のユーザーだけ管理権限持ってるはず。
アプリケーション設計時にデータベース設計も済ませて実装する、ウォーターフォール型の開発が多かったので こうやって後からデータベースにカラム追加とかなんか新鮮。
でもこれできないと後から機能追加とかしにくいよね。上手くできてますなー
今日の学習時間は【20分】。
次は「9.4.1 管理ユーザー」の「Strong Parameters、再び」から。
【110日目】【1日20分のRailsチュートリアル】【第9章】パーシャルのリファクタリングを行う
今日は「9.3.5 パーシャルのリファクタリング」から。
9.3.5 パーシャルのリファクタリング
実はRailsにはコンパクトなビューを作成するための素晴らしいツールがいくつもあります。
この節ではそれらのツールを使用して一覧ページのリファクタリング (動作を変えずにコードを整理すること) を行うことにします。
へー。ツール使ってシンプルに書けるならそれに越したことはないね。
リファクタリングの第一歩は、リスト9.41のユーザーのliをrender呼び出しに置き換えることです (リスト9.46)。
<li>
タグ使ってた箇所を<%= render user %>
に変更。
app/views/users/index.html.erb
: <ul class="users"> <% @users.each do |user| %> <%= render user %> <% end %> </ul> :
この場合、Railsは自動的に_user.html.erbという名前のパーシャルを探します。このパーシャルを作成する必要があります (リスト9.47)。
touchコマンドでファイルを作成して、
$ touch app/views/users/_user.html.erb
下記内容に更新。
さっき置き換えた内容をパーシャルに持って来る感じ。
app/views/users/_user.html.erb
<li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li>
これは間違いなく大きな進歩です。しかしここで終わらせず、さらに改良してみましょう。今度はrenderを@users変数に対して直接実行します (リスト9.48)。
更にすっきりした。eachメソッドすらなくなった。
app/views/users/index.html.erb
: <ul class="users"> <%= render @users %> </ul> :
Railsは@usersをUserオブジェクトのリストであると推測します。さらに、ユーザーのコレクションを与えて呼び出すと、Railsは自動的にユーザーのコレクションを列挙し、それぞれのユーザーを_user.html.erbパーシャルで出力します。
へー。追加したパーシャルの方にeachメソッド移すわけじゃないんだ。
そんなツールがあるんだなぁ…。
リファクタリングを行う場合には、アプリケーションのコードを変更する前と後で必ずテストを実行し、いずれも成功になることを確認するようにしてください
リファクタリングできたのでテスト実行。
$ bundle exec rake test 37 tests, 117 assertions, 0 failures, 0 errors, 0 skips
問題ないですね。
今日の学習時間は【15分】。
次は「9.4 ユーザーを削除する」から。
【109日目】【1日20分のRailsチュートリアル】【第9章】ユーザーインデックスのテストを追加する
今日は「9.3.4 ユーザーインデックスのテスト」から。
9.3.4 ユーザーインデックスのテスト
今回のテストでは、ログイン、indexページにアクセス、最初のページにユーザーがいることを確認、ページネーションのリンクがあることを確認、といった順でテストしていきます。
ページネーションに関するテストを追加する。
リスト9.20で2人目のユーザーをfixtureに追加しましたが、今回はもっと多くのユーザーを作成する必要があります。
fixtureでも埋め込みRubyとやらが使えるらしいのでコード書いてテストユーザーを追加する。
コピペでできなくはないにしろ、もし31人以上のテストユーザーを手作業で追加しろとか言われたら拷問だよね……。(単純作業きらい)
test/fixtures/users.yml
lana: name: Lana Kane email: hands@example.gov password_digest: <%= User.digest('password') %> mallory: name: Mallory Archer email: boss@example.gov password_digest: <%= User.digest('password') %> <% 30.times do |n| %> user_<%= n %>: name: <%= "User #{n}" %> email: <%= "user-#{n}@example.com" %> password_digest: <%= User.digest('password') %> <% end %>
今後必要になるらしいので名前付きユーザー(“lana"と"mallory”)も追加。
リスト9.43のfixtureファイルができたので、indexページに対するテストを書いてみます。まずは、いつものように統合テストを生成します。
統合テストを生成。
$ rails generate integration_test users_index invoke test_unit create test/integration/users_index_test.rb
今回のテストでは、paginationクラスを持ったdivタグをチェックして、最初のページにユーザーがいることを確認します。
1ページ目にユーザーがいる(リンクとはユーザー名が表示されている)ことをテストする。
うーん、何のテストかは何となく分かるけど一からは書けない。。。
test/integration/users_index_test.rb
require 'test_helper' class UsersIndexTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "index including pagination" do log_in_as(@user) get users_path assert_template 'users/index' assert_select 'div.pagination' User.paginate(page: 1).each do |user| assert_select 'a[href=?]', user_path(user), text: user.name end end end
テストが通ることを確認。
$ bundle exec rake test 37 tests, 117 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【19分】。
次は「9.3.5 パーシャルのリファクタリング」から。
【108日目】【1日20分のRailsチュートリアル】【第9章】indexページでページネーションを動作させる
今日は「9.3.3 ページネーション」から。
9.3.3 ページネーション
これで、最初のユーザーにも仲間ができました。しかし今度は逆に、1つのページに大量のユーザーが表示されてしまっています。
(中略)
これを解決するのがページネーション (pagination) というもので、この場合は、たとえば1つのページに一度に30人だけユーザーを表示するというものです。
数千になってしまうとユーザー情報をデータベースから取得するのに時間かかるしもページを表示するのにも時間かかるし良くないね。
Railsには豊富なページネーションメソッドがあります。今回はその中で最もシンプルかつ堅牢なwill_paginateメソッドを使用してみましょう。
へー、いくつもメソッドあるんだ。
will_paginateメソッドを使うためにwill_paginate gem とbootstrap-will_paginate gemをGemfileにインクルードする。
Gemfile
gem 'will_paginate', '3.0.7' gem 'bootstrap-will_paginate', '0.0.10'
bundle installを実行。
$ bundle install Installing will_paginate 3.0.7 : Installing bootstrap-will_paginate 0.0.10 :
will_paginateメソッドを使う準備ができました。
ページネーションが動作するには、ユーザーのページネーションを行うようにRailsに指示するコードをindexビューに追加する必要があります。
また、indexアクションにあるUser.allを、ページネーションを理解できるオブジェクトに置き換える必要もあります。
まずindexページの上下でwill_paginateメソッドを呼ぶ。
app/views/users/index.html.erb
<% provide(:title, 'All users') %> <h1>All users</h1> <%= will_paginate %> <ul class="users"> <% @users.each do |user| %> <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li> <% end %> </ul> <%= will_paginate %>
このwill_paginateメソッドは少々不思議なことに、usersビューのコードの中から@usersオブジェクトを自動的に見つけ出し、それから他のページにアクセスするためのページネーションリンクを作成しています。
へー、メソッドに@usersオブジェクトを指定しなくていいのか。
というのも、現在の@users変数にはUser.allの結果が含まれていますが (リスト9.33)、will_paginateではpaginateメソッドを使った結果が必要だからです。
allではなくpaginateメソッドを使うらしい。
$ rails console >> User.paginate(page: 1) User Load (4.7ms) SELECT "users".* FROM "users" LIMIT 30 OFFSET 0 (0.5ms) SELECT COUNT(*) FROM "users" => #<ActiveRecord::Relation [#<User id: 1, name: "Example User", ...
デフォルトでは30個分のUserを取得する。標準?でこんなメソッドがあるのかー
paginateを使用することで、サンプルアプリケーションのユーザーのページネーションを行えるようになります。具体的には、indexアクション内のallをpaginateメソッドに置き換えます 。
indexアクションにてUser.paginateメソッドを使うように修正。
app/controllers/users_controller.rb
def index @users = User.paginate(page: params[:page]) end
ページネーションの動作見てみよう。
サーバーを起動して
$ rails server -b $IP -p $PORT
<ローカルアドレス>/users
にアクセス。
ページの上下にページネーションが表示されてます。
3ページ目も表示できるよ。実装簡単だな~
今日の学習時間は【22分】。
次は「9.3.4 ユーザーインデックスのテスト」から。
【107日目】【1日20分のRailsチュートリアル】【第9章】サンプルユーザーを一気に追加する
今日は「9.3.2 サンプルのユーザー」から。
9.3.2 サンプルのユーザー
この節では、一人ぼっちのユーザーに仲間を加えてあげることにします。
(中略)
せっかくなのでRubyとRakeを使用してユーザーを一気に作成しましょう。
一気に作成できるなら一気に作成しましょう。
まず、GemfileにFaker gemを追加します (リスト9.38)。これは、実際にありそうなユーザー名とメールアドレスを持つサンプルユーザーを自動的に作成するものです。
へー。いちいちテスト用ユーザーを考えなくていいからいいね。
Gemfile
: gem 'faker', '1.4.2' :
Gemfileを更新したら以下コマンドを実行。
$ bundle install Installing faker 1.4.2
インストールできた。
では、サンプルユーザーを生成するRakeタスクを追加してみましょう。
サンプルユーザーを計100人(1人 + 99人)分生成するタスクを追加する。
db/seeds.rb
User.create!(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar") 99.times do |n| name = Faker::Name.name email = "example-#{n+1}@railstutorial.org" password = "password" User.create!(name: name, email: email, password: password, password_confirmation: password) end
Fakerを使うのはユーザー名だけなのかー。
それでは、データベースをリセットして、リスト9.39のRakeタスクを実行 (db:seed) してみましょう。
$ bundle exec rake db:migrate:reset $ bundle exec rake db:seed
ちょっと時間かかった。
サンプルユーザーの追加ができたので実際の表示を確認してみる。
サーバーを起動して
$ rails server -b $IP -p $PORT
<ローカルアドレス>/users
にアクセス。
したらログインを要求されるので"Example User"のアカウントでログイン。
ユーザーがたくさん表示されてます。さみしくない!
最初のいくつかのメールアドレスについては、デフォルトのGravatar画像以外の写真を関連付けてみました。
なるほど、メールアドレスをexample-*@railstutorial.orgにしたのはこのためか。。
自動生成にするとGravatar画像見れないもんね。
今日の学習時間は【20分】。
次は「9.3.3 ページネーション」から。
【106日目】【1日20分のRailsチュートリアル】【第9章】すべてのユーザーを表示するビューを作成する
今日は「9.3.1 ユーザーインデックス」のindexビューを追加するところから。
9.3.1 ユーザーインデックス
今度はすべてのユーザーを表示するために、全ユーザーが格納された変数を作成し、順々に表示するindexビューを実装します。
Userコントローラーにindexアクションを追加。
app/controllers/users_controller.rb
def index @users = User.all end
(すべてのユーザーを一気に読み出すとデータ量が多い場合に問題が生じるのではないかと思われた方、そのとおりです。このキズは9.3.3で修正します。)
一気に読み出すと時間かかりそう。
実際のインデックスページを作成するには、ユーザーを列挙してユーザーごとにliタグで囲むビューを作成する必要があります。
ファイルを作成して
$ touch app/views/users/index.html.erb
下記内容でファイル更新。
app/views/users/index.html.erb
<% provide(:title, 'All users') %> <h1>All users</h1> <ul class="users"> <% @users.each do |user| %> <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li> <% end %> </ul>
訳注: 7.7の1つ目の演習 (リスト7.31) でgravatarメソッドを拡張していない場合、以下のリストがうまく動きません。まだの方は当該リストのコードを先に反映させておいてください。
演習向けブランチでgravatarメソッドを拡張しているのでmasterブランチには反映してない。
リスト7.31の内容を反映しておく。
app/helpers/users_helper.rb
module UsersHelper # 引数で与えられたユーザーのGravatar画像を返す def gravatar_for(user, options = { size: 80 }) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) size = options[:size] gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" image_tag(gravatar_url, alt: user.name, class: "gravatar") end end
CSS (正確にはSCSSですが) にもちょっぴり手を加えておきましょう (リスト9.35)。
ファイルの一番下に下記を追加。
app/assets/stylesheets/custom.css.scss
/* Users index */ .users { list-style: none; margin: 0; li { overflow: auto; padding: 10px 0; border-bottom: 1px solid $gray-lighter; } }
最後に、サイト内移動用のヘッダーにユーザー一覧表示用のリンクを追加します。
これにはusers_pathを使用し、表7.1に残っている最後の名前付きルートを割り当てます。変更の結果をリスト9.36に示します。
ナビゲーションに追加してたUsers
のリンク先をusers_path
に変更。
app/views/layouts/_header.html.erb
: <li><%= link_to "Users", users_path %></li> :
これでユーザーのインデックスは完全に動くようになり、テストも全てパスするようになります。
テストが通ることを確認。
$ bundle exec rake test 36 tests, 85 assertions, 0 failures, 0 errors, 0 skips
表示も見てみよう。
サーバーを起動して
$ rails server -b $IP -p $PORT
<ローカルアドレス>/users
にアクセス。(ヘッダーの「Users」をクリック)
…1人しかいない。さみしい。
今日の学習時間は【20分】。
次は「9.3.2 サンプルのユーザー」から。