←Rails 6で認証認可入り掲示板APIを構築する #12 userとpostの関連付け
factoryの修正
factoryから直していきます。
一番多く飛んでいるのが
ActiveRecord::RecordInvalid:
        Validation failed: User must exist
というエラーですね。
これはcreate(:post)した時に、user_idがnilになることで発生しています。
factoryの修正で潰しましょう。
   factory :post do
     subject { "MyString" }
     body { "MyText" }
+
+    after(:build) do |obj|
+      obj.user = build(:user) if obj.user.nil?
+    end
   end
after(:build)は、buildやcreateをした後に実行されます。
post.userにbuildしたuserを入れることにより、User must existエラーを潰せます。
なお、if obj.user.nil?をすることでcreate(:post, user: user)のように特定ユーザーを渡して生成した際、内部処理で上書きされてしまうのを防いでいます。
実はもっとシンプルな方法で、とりあえず大半潰すことができたりします。
   factory :post do
     subject { "MyString" }
     body { "MyText" }
+    user
   end
ただしこの方法だとcreate(:post)した時はいいのですが、build(:post)した時にuserがnilで返ってくるため、前者の対応をしています。
request specとcontrollerの修正
   describe "POST /v1/posts#create" do
+    let(:authorized_headers) do
+      user = create(:user)
+      post v1_user_session_url, params: { email: user.email, password: "password" }
+      headers = {}
+      headers["access-token"] = response.header["access-token"]
+      headers["client"] = response.header["client"]
+      headers["uid"] = response.header["uid"]
+      headers
+    end
     let(:new_post) do
       attributes_for(:post, subject: "create_subjectテスト", body: "create_bodyテスト")
     end
     it "正常レスポンスコードが返ってくる" do
-      post v1_posts_url, params: new_post
+      post v1_posts_url, params: new_post, headers: authorized_headers
       expect(response.status).to eq 200
     end
     it "1件増えて返ってくる" do
       expect do
-        post v1_posts_url, params: new_post
+        post v1_posts_url, params: new_post, headers: authorized_headers
       end.to change { Post.count }.by(1)
     end
     it "subject, bodyが正しく返ってくる" do
-      post v1_posts_url, params: new_post
+      post v1_posts_url, params: new_post, headers: authorized_headers
       json = JSON.parse(response.body)
       expect(json["post"]["subject"]).to eq("create_subjectテスト")
       expect(json["post"]["body"]).to eq("create_bodyテスト")
     end
     it "不正パラメータの時にerrorsが返ってくる" do
-      post v1_posts_url, params: {}
+      post v1_posts_url, params: {}, headers: authorized_headers
       json = JSON.parse(response.body)
       expect(json.key?("errors")).to be true
     end
   end
ユーザーを生成し、そのユーザー情報を元にログイン。
レスポンスヘッダにある認証用3キーをheadersに加えてpostをすることで、create(:user)したユーザーとして認証された状態でアクセスをします。
ただしcontroller側をまだ直していないのでエラーのままです。
controllerの修正
     def create
-      post = Post.new(post_params)
+      post = current_v1_user.posts.new(post_params)
       if post.save
上記修正でテスト通過するようになるはずです。
挙動を説明すると、まずheadersで認証情報が渡ってきているため、controllerではcurrent_v1_userというメソッドが使えます。これはログイン中のユーザーインスタンスが返ってくるものです。
つまりcurrent_v1_user.posts.newは、ログイン中のユーザーに紐づくpostをインスタンス化しています。
それにより、ログインしているユーザーのpostが作られます。
rspecの認証済みヘッダ取得処理をhelperに移動
テストは通るようになったのですが、今後Punditを入れて認可を実装していくにあたり、認証済みヘッダを取得する処理を都度書いていては保守性が下がるので、spec用のhelperに移動します。
spec用helperは一般的にspec/supportに置いていくのでディレクトリを作ります。
$ mkdir spec/support
$ touch spec/support/authorization_spec_helper.rb
rspecにあった処理をごっそりこっちに持ってきます。
# frozen_string_literal: true
#
# 認証用ヘルパ
#
module AuthorizationSpecHelper
  def authorized_user_headers
    user = create(:user)
    post v1_user_session_url, params: { email: user.email, password: "password" }
    headers = {}
    headers["access-token"] = response.header["access-token"]
    headers["client"] = response.header["client"]
    headers["uid"] = response.header["uid"]
    headers
  end
end
spec/support下に配置しただけでは勝手に読み込んでくれないので、spec/rails_helper.rbを修正します。
-# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
+Dir[Rails.root.join("spec", "support", "**", "*.rb")].sort.each { |f| require f }
...
 RSpec.configure do |config|
...
+  config.include(AuthorizationSpecHelper, type: :request)
 end
コメントアウトされていたspec/support下を読みにいく処理を有効化するのと、AuthorizationSpecHelperをincludeします。上記のように書くことで、request specのみ有効になります。
...
 require "rails_helper"
 
 RSpec.describe "V1::Posts", type: :request do
+  let(:authorized_headers) do
+    authorized_user_headers
+  end
...
   describe "POST /v1/posts#create" do
-    let(:authorized_headers) do
-      user = create(:user)
-      post v1_user_session_url, params: { email: user.email, password: "password" }
-      headers = {}
-      headers["access-token"] = response.header["access-token"]
-      headers["client"] = response.header["client"]
-      headers["uid"] = response.header["uid"]
-      headers
-    end
...
あとは上記対応で完了。
テスト結果が変わらずgreenであればとりあえずOKです。
テストは全部通過するものの、そもそもテストコードが不十分。
認証されていない時に#createを叩くと500エラーになったり、そもそも自分以外の投稿を更新したり削除できてしまう現状の仕様は困るので、次次回でいよいよ認可を入れていきます。
次回はseedの整備を行います。
本日はここまで。