Rails 6で認証認可入り掲示板APIを構築する #13 認証ヘッダの付与

←[Rails 6で認証認可入り掲示板APIを構築する #12 userとpostの関連付け](https://qiita.com/rf_p/items/bcdcbb7c6cbd725b5b94)

# factoryの修正

factoryから直していきます。

一番多く飛んでいるのが

```powershell
ActiveRecord::RecordInvalid:
        Validation failed: User must exist
```

というエラーですね。
これは`create(:post)`した時に、user_idがnilになることで発生しています。

factoryの修正で潰しましょう。

```diff:spec/factories/posts.rb
   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)`のように特定ユーザーを渡して生成した際、内部処理で上書きされてしまうのを防いでいます。

実はもっとシンプルな方法で、とりあえず大半潰すことができたりします。

```diff:spec/factories/posts.rb
   factory :post do
     subject { "MyString" }
     body { "MyText" }
+    user
   end
```

ただしこの方法だと`create(:post)`した時はいいのですが、`build(:post)`した時にuserがnilで返ってくるため、前者の対応をしています。

# request specとcontrollerの修正

```diff:spec/requests/v1/posts_request_spec.rb
   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の修正

```diff:app/controllers/v1/posts_controller.rb
     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に置いていくのでディレクトリを作ります。

```powershell
$ mkdir spec/support
$ touch spec/support/authorization_spec_helper.rb
```

rspecにあった処理をごっそりこっちに持ってきます。

```rb:spec/support/authorization_spec_helper.rb
# 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を修正します。

```diff: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のみ有効になります。

```diff:spec/requests/v1/posts_request_spec.rb
...
 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の整備を行います。
本日はここまで。

# 続き

→[Rails 6で認証認可入り掲示板APIを構築する #14 seed実行時間の表示](https://qiita.com/rf_p/items/e4e840884e925c0cc053)
【[連載目次へ](https://qiita.com/rf_p/items/a846553c11571f4177eb#%E7%9B%AE%E6%AC%A1)】