これまでのあらすじ
さて、突然ですが10回クイズの時間です。Rails って10回言ってください。えぇ…、RailsRailsRailsRailsRails...。はい、よくできました(オチなし)。
ということで今までの知識の総集編だと個人的に思ってる第10章はーじめーるよー‼︎前回はログイン機能などを作り込んでいきました。今回はログイン後のユーザー周りの編集機能を充実させていきます。
ユーザー機能の充実化をはかるっぴ
とにもかくにもまずはガワを作ります。コントローラにedit
を追加して、こんなの作ります。


ガワ系の話、基本的にERBいじるだけだしコード載せる必要ないと思ってる。パーシャルつないでごちゃごちゃしてるけど、基本的にテンプレート行き来してるだけですし……。まあいいや、基本的な機能作ったのでテストを書きます。
参考:ERB
ビュー(view) - - Railsドキュメント
$ rails generate integration_test users_edit
# test/integration/users_edit_test.rb require 'test_helper' class UsersEditTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "編集失敗時" do get edit_user_path(@user) assert_template 'users/edit' patch user_path(@user), params: { user: { name: "", email: "foo@invalid", password: "foo", password_confirmation: "bar" } } assert_template 'users/edit' end test "編集成功時" do get edit_user_path(@user) assert_template 'users/edit' name = "Foo Bar" email = "foo@bar.com" patch user_path(@user), params: { user: { name: name, email: email, password: "", password_confirmation: "" } } assert_not flash.empty? # => validates に allow_nil: true 追加 assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email end test "フレンドリーフォワーディング" do get edit_user_path(@user) log_in_as(@user) assert_redirected_to edit_user_url(@user) name = "Foo Bar" email = "foo@bar.com" patch user_path(@user), params: { user: { name: name, email: email, password: "", password_confirmation: "" } } assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email end end
# test/controllers/users_controller_test.rb require 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) @other_user = users(:archer) end test "ログインしてない時にindexをリダイレクト" do get users_path assert_redirected_to login_url end test "ログインしてない時にeditをリダイレクト" do get edit_user_path(@user) assert_not flash.empty? assert_redirected_to login_url end test "ログインしてないときにupdateをリダイレクト" do patch user_path(@user), params: { user: { name: @user.name, email: @user.email } } assert_not flash.empty? assert_redirected_to login_url end test "不正なユーザでのログインのときにeditをリダイレクト" do log_in_as(@other_user) get edit_user_path(@user) assert flash.empty? assert_redirected_to root_url end test "不正なユーザでのログインのときにupdateをリダイレクト" do log_in_as(@other_user) patch user_path(@user), params: { user: { name: @user.name, email: @user.email } } assert flash.empty? assert_redirected_to root_url end test "Web経由でのadmin属性の変更を禁止" do # 演習 log_in_as(@other_user) assert_not @other_user.admin? patch user_path(@other_user), params: { user: { password: "password", password_confirmation: "password", admin: true } } assert_not @other_user.reload.admin? end end
テストはこんなんで。カジュアルに日本語表記しているけど、多分よろしいやり方でないから真似しないで*1。テストの方もだいぶ肥大化してきたけど、このままべた書きするのとまとめるのどっちが良いんかね。まあ、多分僕はまとめるとわけわかんなくなるけどもね……。
そんで更新とか追加とかやって機能面はこんな感じに落ち着きます。
# app/controllers/users_controller.rb class UsersController < ApplicationController before_action :logged_in_user, only: [:index, :edit, :update, :destroy] before_action :correct_user, only: [:edit, :update] before_action :admin_user, only: :destroy ### 略 ### def create @user = User.new(user_params) if @user.save log_in @user flash[:success] = "Welcome to the Sample App!" redirect_to @user else render 'new' end end def edit @user = User.find(params[:id]) end def update @user = User.find(params[:id]) if @user.update_attributes(user_params) flash[:success] = "Profile updated" redirect_to @user else render 'edit' end end ### ユーザ削除 ### def destroy User.find(params[:id]).destroy flash[:success] = "User deleted" redirect_to users_url end private def user_params params.require(:user).permit(:name, :email, :password,:password_confirmation) end # ログイン済みユーザーかどうか確認 def logged_in_user unless logged_in? store_location flash[:danger] = "Please log in." redirect_to login_url end end # 正しいユーザーかどうか確認 def correct_user @user = User.find(params[:id]) redirect_to(root_url) unless current_user?(@user) end # 管理者かどうか確認 def admin_user redirect_to(root_url) unless current_user.admin? end end
before_action
ってのを使うとユーザ権限ごとに良い感じに行動を振り分けられるとのこと。なんかCSSの!important
味を感じる。
とまあそんな感じで、ユーザの編集機能ができます。新たな学びがあるとしたらbefore_action
でフィルタリングできるとかその辺?
ユーザリストをつくるっぴ
今度はユーザの全リストをつくります。Ruby on Rails の醍醐味である「詳しい動作はよくわかんないがGemインスコしておけば動くサービスがつくれる」を体感できます。てなわけで以下を入れます。一応Gemのリンク貼っておきます。詳しい使い方はリンク先の「Homepage」ってところに飛べば載ってたりします。
faker
faker | RubyGems.org | your community gem host
ダミーの名前をつくってくれるやつ。
# db/seeds.rb ### 略 99.times do |n| name = Faker::Name.name # Faker email = "example-#{n+1}@railstutorial.org" password = "password" User.create!(name: name, email: email, password: password, password_confirmation: password) end
使用方法は上のような感じだそうです*2。Faker::Internet.email
なんてのも用意されているからemailもつくれちゃう。
will_paginate
will_paginate | RubyGems.org | your community gem host
ページネーションを実装するやつ。
$ rails console >> User.paginate(page: 1)
使用方法は上のような感じです。
bootstrap-will_paginate
bootstrap-will_paginate | RubyGems.org | your community gem host
Bootstrap用ページネーションを生成してくれるやつ。内部動作はどうなっているのかしらない。Bootstrap4系で確認する場合は以下のように使えばいけるらしい。
<%= will_paginate(@things, :renderer => WillPaginate::ActionView::Bootstrap4LinkRenderer) %>
そんで全部実装したらこう!!
まじで何もコードいじらない。jQueryプラグイン感覚で使える!!
そんでテストはこう!!
$ rails generate integration_test users_index
# 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 'nav.pagination', count: 2 #Bootstrap4仕様 User.paginate(page: 1).each do |user| assert_select 'a[href=?]', user_path(user), text: user.name end end end
Bootstrap4仕様でやると生成タグも変化するんですね……。assert_select
での判定結構ボロボロだ。
とりあえずユーザリストの生成はそんな感じでできます。やってみた学びとしてはGem便利ってのとページネーションの使い方ググっても「Kaminari」薦める人間しかいないせいで公式ドキュメントしか参考にならないってことがわかった。
ユーザの削除機能つくるっぴ
最後は削除機能を実装します。まずは削除用の管理ユーザを追加します。今更この機能を追加されると行き当たりばったり感がしてならない……。
$ rails generate migration add_admin_to_users admin:boolean $ rails db:migrate
# app/views/users/_user.html.erb <!-- 管理者のみ対応 --> <% if current_user.admin? && !current_user?(user) %> | <%= link_to "delete", user, method: :delete, data: { confirm: "You sure?" } %> <% end %>
削除ボタンは上みたいな感じで、そんでコントローラは一番上の方で書いたようにdestroy
を追加します。
そうするとこんな機能追加できます。
テストは以下。
# test/controllers/users_controller_test.rb require 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) @other_user = users(:archer) end ### 略 ### test "ログインしてないときにdestroyをリダイレクト" do assert_no_difference 'User.count' do delete user_path(@user) end assert_redirected_to login_url end test "管理者でないログインのときにdestroyをリダイレクト" do log_in_as(@other_user) assert_no_difference 'User.count' do delete user_path(@user) end assert_redirected_to root_url end end
# test/integration/users_index_test.rb require 'test_helper' class UsersIndexTest < ActionDispatch::IntegrationTest def setup @admin = users(:michael) @non_admin = users(:archer) end test "管理者のindexではページネーションと削除リンクを含む" do log_in_as(@admin) get users_path assert_template 'users/index' assert_select 'nav.pagination' first_page_of_users = User.paginate(page: 1) first_page_of_users.each do |user| assert_select 'a[href=?]', user_path(user), text: user.name unless user == @admin assert_select 'a[href=?]', user_path(user), text: 'delete' end end assert_difference 'User.count', -1 do delete user_path(@non_admin) end end test "非管理者のindex" do log_in_as(@non_admin) get users_path assert_select 'a', text: 'delete', count: 0 end end
結合テストってここまでがっつりいるんですかね?そしてテストに関しては割といわれるがままに書いていて応用できる技術が身についたって感じがしない。まあ、それでも僕はカバレッジ100%のテストコード書ける自信ありますけどね(不正はない。いいね?)。