LoginSignup
7
2

More than 1 year has passed since last update.

rails のconcernを使ってエラーレスポンスを返す忘備録

Last updated at Posted at 2022-04-11

concernとは

app/concernsというディレクトリが存在する。concerns

・複数のコントローラー(モデル)から利用される処理を共通化する目的
・ある特定のまとまった処理を切り出して見通しをよくする目的

上記の目的で使われることが多い。

Concernの書き方

まずはconcernのファイルを記入し、その後コントローラーにてincludeで表記する。

MyConcern.rb
module MyConcern
  extend ActiveSupport::Concern

  # Scope や Callbackなどの処理を実装する
  included do
    scope :without_deleted, lambda{ where(deleted_at: nil) }
  end

  module ClassMethods
    def foo
      puts 'foo'
    end
  end

  def bar
    puts 'bar'
  end
end
application_controller.rb
class ApplicationController < ActionController
   include MyConcern
   
   処理
   .
   .
end

エラーレスポンスを記入

app/controllers/concerns/api/exception_handler.rb
module Api::ExceptionHandler
  extend ActiveSupport::Concern

  included do
    rescue_from StandardError, with: :render_500
    rescue_from ActiveRecord::RecordNotFound, with: :render_404
  end

  private

  def render_400(exception = nil, messages = nil)
    render_error(400, 'Bad Request', exception&.message, *messages)
  end

  def render_404(exception = nil, messages = nil)
    render_error(404, 'Record Not Found', exception&.message, *messages)
  end

  def render_500(exception = nil, messages = nil)
    render_error(500, 'Internal Server Error', exception&.message, *messages)
  end

  def render_error(code, message, *error_messages)
    response = {
        message: message,
        errors: error_messages.compact
    }

    render json: response, status: code
  end
end

・rescue_from
rescue_fromは、特定の種類または複数の種類の例外を1つのコントローラ全体およびそのサブクラスで扱えるようにするもの。
▶︎rescueとrescue_from

・exception
例外オブジェクトを生成して返すメソッド。

e = Exception.new("some message")
p e         # => #<Exception: some message>
p e.message # => "some message"

▶︎exception

・exception&.message
ぼっち演算子。レシーバーがnilのときだけは、メソッドが呼び出されないでnilを返す。
▶︎Ruby ぼっち演算子について

・*messages
メソッドの引数にアスタリスクがある。アスタリスク1つなら可変長引数、アスタリスク2つならオプション引数という。

可変長引数(アスタリスクが1つ)
*をつければ引数を配列に指定できる。
メソッドには1つの引数の定義で、複数の引数を設定でき、配列として結果を返す。ただしこの可変長引数は1メソッドにつき、1つだけしか指定できない。
*と記述することで受け取った引数を無視するようにも使用できる。

#複数の引数を配列にする
def array(*a)
  p a
end

> array(1,2)
=> [1, 2] 
> array(1, 2, 3)
=> [1, 2, 3]

オプション引数(アスタリスクが2つ)
**をつければ引数を、ハッシュに指定できる。
メソッドには一つの引数の定義で、複数の引数を設定でき、ハッシュとして結果を返す。

#複数の引数をハッシュにする
def array(**a)
  p a
end

> array(b: 1, c: 2)
=> {:b=>1, :c=>2} 
> array(b: 1, c: 2, d: 3)
=> {:b=>1, :c=>2, :d=>3}

▶︎*と記述することで受け取った引数を無視するようにも使用

・compact
compactは自身からnilを取り除いた配列を生成して返す。

ary = [1, nil, 2, nil, 3, nil]
p ary.compact   #=> [1, 2, 3]
p ary           #=> [1, nil, 2, nil, 3, nil]
ary.compact!
p ary           #=> [1, 2, 3]
p ary.compact!  #=> nil

▶︎compact

rspec

spec/requests/api/v1/exception_handler_spec.rb
RSpec.describe 'Api::ExceptionHandler', type: :request do
  let!(:user) { create(:user) }

  describe 'render 500' do
    it 'returns errors in json format' do
      allow(Article).to receive(:all).and_raise(StandardError)
      get api_v1_articles_path, headers: { CONTENT_TYPE: 'application/json', ACCEPT: 'application/json' }

      expect(body['message']).to eq('Internal Server Error')
      expect(body['errors'].size).to eq(1)
      expect(response).to have_http_status(500)
    end
  end

  describe 'render 404' do
    let(:id) { 1 }

    it 'returns errors in json format' do
      get api_v1_article_path(id), headers: { CONTENT_TYPE: 'application/json', ACCEPT: 'application/json' }

      expect(body['message']).to eq('Record Not Found')
      expect(body['errors']).to eq(["Couldn't find Article with 'id'=#{id}"])
      expect(response).to have_http_status(404)
    end
  end
end

▶︎使えるRSpec入門・その3「ゼロからわかるモック(mock)を使ったテストの書き方」

・allow(Article).to receive(:all)
allow(モックオブジェクト).to receive(メソッド名)の形で使われる。英語の意味からも分かるように、allow A to receive Bで、「A が B を受け取ることを許可する」の意味になる。
つまり上記は、Articleの全てを受け取ることを許可する、と言う意味になる。

参考記事

rails concernの使い方
Rails/Model(Active Record)のConcern

7
2
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
7
2