このページの内容は以下のリポジトリに1日遅れで反映されます(記事執筆前に前日分をコミットしています)。
https://github.com/hkob/hkob_blog
はじめに
Rails Tips の 79 回目です。昨日は記事にユーザの所有・非所有を取り扱うメソッドを追加しました。これを使って、所有しないオブジェクトに対する edit の対応を行います。
article_spec
article_spec の頭に not_mine という記事用の let があるのでコメントを外しました。
RSpec.describe ArticlesController, type: :request do let!(:article) { articles :can_delete } let!(:object) { article } let!(:attrs) { object.attributes } let(:not_mine) { articles :article1 }
昨日は hkob というユーザでログインしていましたが、can_delete オブジェクトの所有者でログインした方がこの後が書きやすいので、can_delete でのログインに変更しました。
context "when login by can_delete" do user_login :can_delete
subject では object を edit していましたが、one という変数に変更しています。この one はその下のwhen owned object および when not owned object のコンテキストの中で let で遅延評価しています。前者はこれまで通り記事更新ページになりますが、自分が所有しない記事についてはトップページに戻るようにしています。基本的にはそのようなリンクはつくらないのですが、ユーザが id を勝手に書き換えて他人のページを編集したりするときの対策です。
describe "GET #edit" do subject { -> { get edit_article_path(one) } } context "when owned object" do let(:one) { object } it_behaves_like "レスポンスコード確認", 200 it_behaves_like "描画結果に文字列が含まれている?", "記事更新" end context "when not owned object" do let(:one) { not_mine } it_behaves_like "レスポンスコード確認", 302 it_behaves_like "rootリダイレクト確認" end end
これを通すために、take_one
で取得した @article
を昨日の owned_by? で所有確認し、所有権がないときには root_path にリダイレクトします。
def take_one @article = object_from_params_id Article redirect_to root_path, alert: I18n.t("errors.messages.not_owned") unless @article.owned_by?(current_user) end
errors.messages.not_owned は以下のように翻訳テキストを準備しました。
ja:
errors:
(中略)
messages:now_owned: は自分のものでなければなりません
ログインしていないときには、current_user が nil になっています。ここで owned_by? に nil を渡すとエラーになってしまっていました。テストを先に修正します。
it_behaves_like "配列メソッド呼び出し" do let(:test_set) do { archived?: [nil, [false, false]], owned_by?: [ [nil], [false, false], article.user, [true, false] ], user_name: [nil, %w[hkob can_delete]], } end end
owned_by? の実装を修正します。u.id の部分でエラーになるので、 &. でエラーにならないようにしました。
# @param [User, nil] u 確認するユーザー # @return [TrueClass, FalseClass] ユーザーが所有していたら true def owned_by?(u) user_id == u&.id end
これで edit は通るようになりましたが、show でエラーになるようになりました。show はログインしていない状態でも take_one を読んでしまうにしていたからでした。この部分は共通化できないので、show における @article
の取得は個別に実施することにします。まず、show で take_one を呼び込まないようにします。
before_action :take_one, only: %i[edit update destroy]
そして、最後に show で article を呼び込むようにしておきます。
def show @article = object_from_params_id Article @comments = @article.comments.order_created_at_desc @comment = objects_from_params(Comment) || @article.comments.build end
おわりに
これで、edit の際に所有権を確認するようにしました。take_one で修正したので、実装的には他のアクション (update, destroy) でも有効になっているはずです。ただし、テストは行なっていないので、明日はテストで無事に所有権が確認できるていることを確認します。