#全体像の把握
editアクションで、編集フォームを作成。updateアクションで編集部分を保存。
edit,update等のアクションに縛りを設けるため、beforeアクションを利用。
indexアクションで、ユーザー一覧ページを作成し、その際、ページネーションを利用。
destroyアクションはadmin権限で使用可能にした。
#editアクション -User編集ページ
GET /users/:id/edit
def edit
@user = User.find(params[:id]) # GETリクエストのidを使い、ユーザー情報を取得
end
ViewはUserコントローラのnewとほぼ同じ
コード上の違いがないのに、newはcreateアクションを発行し、editはupdateアクションを発行する?
→
newはインスタンスを新しく作っていて型だけだが、editはfindを使ってデータを取得しているのでidが入っている。それで判断している
privateメソッド
このメソッド以下のメソッドは全てprivateになる
このコントローラ内でだけ使って外からのアクセスで書き換えられたくないという時に使う
paramsを精査するとかいう場所で使う
#updateアクション -編集内容をDBに保存
def update
@user = User.find(params[:id]) # 入力された内容を取得し、@userに代入
if @user.update_attributes(user_params) #引数には更新したいカラム名
flash[:success] = "Profile updated"
redirect_to @user
else
render "edit"
end
end
updateアクションのテストでpasswordを空にして編集できるようにするallow_nilが機能しなかった
#認可
ログインしていない人がページにアクセスできてしまう問題を解決
#1 権限のないユーザーがアクセスしたらログインするように促す
beforeフィルター
何かを実行する前に、指定した機能を追加してください
→ 編集ページに行く前に、ログインしてください
before_action :logged_in_user, only: [:edit, :update]
def logged_in_user
unless logged_in? #もしログインしていなかったら
flash[:danger] = "Please log in." #コメント表示
redirect_to login_url # loginページに
end
end
beforeのフォローとして、testにログインしたことを確認するコードを書く
beforeが機能しているかコメントアウトしてチェック
beforeが機能していないとErrorとなるようにテストを書く
test "should redirect edit when not logged in" do
get edit_user_path(@user) # loginせずにページにアクセス
assert_not flash.empty? # flashが出る
assert_redirected_to login_url # loginページへ
end
test "should redirect update when not logged in" do
patch user_path(@user), params: { user: { name: @user.name,
email: @user.email } }
# patchリクエストがブラウザ経由ではなく直接送りつけられた場合
assert_not flash.empty?
assert_redirected_to login_url
end
#2 自分のプロフィールページだけを編集できるように
TDDで
テストは
test "should redirect edit when logged in as wrong user" do
log_in_as(@other_user) # ログイン
get edit_user_path(@user) # 違う人の編集ページに行こうとする
assert flash.empty? # flash
assert_redirected_to root_url # topページに
end
# ...(略)
flashが逆であるのが真だとErrorが出た
users_ymlはmemberを左にくっつけないとErrorになる
正しいユーザーかどうかチェックする機能を実装
def correct_user
# GET /users/:id/edit
# PATCH /users/:id どちらかから送られてきたリクエストをparamsに格納
@user = User.find(params[:id])
# currentは第8章で実装、今ログインしているユーザーを示す
redirect_to(root_url) unless @user == current_user
end
#indexアクション - User一覧ページ
TDD
ログインしてないと行けないというテスト
test "should redirect index when not logged in" do
get users_path # users_pathは詳細ページを表す、詳細ページへ移動
assert_redirected_to login_url # beforeアクションでloginページへ
end
def index
@users = User.all # @usersに注意、User情報を全て取得
end
View
<% 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 %> # リンクのuserでユーザー詳細に
# どこでやったか
</li>
<% end %>
</ul>
サンプルユーザーの生成 Fake
Pagenation
install
Viewではページネーションしたいものを囲む
<%= will_paginate @users %> #引数にUserの集合@usersを渡すことで、Userを対象に指定
:
<%= will_paginate %>
Controllerでは
def index
@users = User.paginate(page: params[:page])
# parameterにpageが格納されるようになるので、それを@usersに保存
# 初期では30ずつ
end
IntegrationTest
def setup
@user = users(:michael)
end
test "index including pagination" do
log_in_as(@user) # login
get users_path # indexページにGETリクエスト
assert_template 'users/index' # indexページに
assert_select 'div.pagination' # paginationが機能するか
User.paginate(page: 1).each do |user| #それぞれのリンクが正しく貼られているか
assert_select 'a[href=?]', user_path(user), text: user.name
end
end
リファクタリング
renderを使って、UserオブジェクトのPartialを作り、それをrender @userで呼び出す
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
</li>
この部分は
<%= render user %>
=> app/views/リソース名/モデル名.html.erb
=> app/views/users/_user.html.erb というPartialが呼び出される
とすることができる
今までPartialのrenderはsharedを使っていたが、引数にUserオブジェクトを渡すことができる
さらに、
<%= render @users %>
Userオブジェクトの集合体もrenderの引数として使うことができ、それによりその引数を展開して内部でrenderを呼び出してくれる
つまり
<% @users.each do |user| %>
<%= render user %>
<% end %>
この部分と等価になり、省略できる
#destroyアクション -削除
管理権限を持った人ならアカウントを削除できる
admin
add_column :users, :admin, :boolean, default: false
の,を忘れず
index.htmlのrender @user
が_user.html.erbを呼ぶので
<% if current_user.admin? && !current_user?(user) %>
# admin なら以下のリンクが見える
| <%= link_to "delete", user_path(user), method: :delete,
data: { confirm: "You sure?" } %>
# method:で指定しているのでDELETEリクエストが/users/:idに送られる
#
<% end %>
を書き加える
すると、index -> delete という流れが作れる
def destroy
# 上でDELETE /user/:idに送られたparameterを使い、Userを探し、destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url # indexへ
end
controller_testの方のテストでadmin権限を付与していないacherではdeleteできないというテストが機能しない、acherもdeleteできる仕様になってしまっている、原因不明
test "should redirect destroy when logged in as a non-admin" do
log_in_as(@other_user)
assert_no_difference 'User.count' do
delete user_path(@user)
end
assert_redirected_to root_url
end
IntegrationTest
indexのテストに追加している
# adminであれば、deleteが見えているはずという意味。なんでifじゃない?
unless user == @admin
assert_select 'a[href=?]', user_path(user), text: 'delete'
end
...
delete user_path(@non_admin) #adminなので、non_adminを削除する
...
test "index as non-admin" do
log_in_as(@non_admin) #loginしてみる
get users_path # indexページに行く
assert_select 'a', text: 'delete', count: 0 #しかし、削除されているので行けない
end
Integrationtestでもfailureが出た
このテストで確認していることは、三つある
1ログインしていないユーザーならdeleteリクエストを送ってもユーザー数に変化なし
2admin権限を付与していないユーザーならdeleteリクエストを送ってもユーザー数に変化なし
3Integrationでは、admin権限のあるユーザーでログインし、deleteを送ると、ユーザー数が変化する