1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-09-17

Rails 6で認証認可入り掲示板APIを構築する #12 userとpostの関連付け

factoryの修正

factoryから直していきます。

一番多く飛んでいるのが

ActiveRecord::RecordInvalid:
        Validation failed: User must exist

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

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

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

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

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

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

request specとcontrollerの修正

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の修正

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に置いていくのでディレクトリを作ります。

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

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

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を修正します。

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

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実行時間の表示
連載目次へ

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?