🐙

【Rails】Model.find失敗時にNotFoundErrorが発生する内部的な仕組み

2024/01/15に公開

一度はActiveRecode moduleの中に潜ったことありますよね。
自分は始めたての頃に「このメソッドが失敗したら、このエラークラスが実行される」っていう仕組みが何が何だかわからなかったので、ActiveRecodeの内部実装を見ながらfindメソッドの一例を解説します。

ActiveRecode module

よく使うwhere,find,createとかのActiveRecodeから提供されるメソッドは、railsの/rails/activerecode/lib/active_recode/配下のファイルにそれぞれ実装されています。
https://github.com/rails/rails/tree/0ecfbe25688a0deb9aaee0596c99148002b9f3a4/activerecord/lib/active_record

  • where
    /rails/activerecode/lib/active_recode/relation/query_methods.rb
def where(*args)
  if args.empty?
    WhereChain.new(spawn)
  elsif args.length == 1 && args.first.blank?
    self
  else
    spawn.where!(*args)
  end
end
  • create
    /rails/activerecode/lib/active_recode/persistence.rb
def create!(attributes = nil, &block)
  if attributes.is_a?(Array)
    attributes.collect { |attr| create(attr, &block) }
  else
    object = new(attributes, &block)
    object.save!
    object
  end
end

その中で、今回はfindメソッドについて詳細に見ていきます。

FinderMethods module

https://github.com/rails/rails/blob/0ecfbe25688a0deb9aaee0596c99148002b9f3a4/activerecord/lib/active_record/relation/finder_methods.rb#L6

見ていただけるとわかりますが、finderの名前の通り「絞り込んで取得する系」のメソッドがまとまっています.

find

/rails/activerecode/lib/active_recode/relation/finder_methods.rb

def find(*args)
  return super if block_given?
  find_with_ids(*args)
end

def find_with_ids(*ids)
  raise UnknownPrimaryKey.new(@klass) if primary_key.nil?

  expects_array = if klass.composite_primary_key?
    ids.first.first.is_a?(Array)
  else
    ids.first.is_a?(Array)
  end

  return [] if expects_array && ids.first.empty?

  ids = ids.first if expects_array

  ids = ids.compact.uniq

  model_name = @klass.name

  case ids.size
  when 0
    error_message = "Couldn't find #{model_name} without an ID"
    raise RecordNotFound.new(error_message, model_name, primary_key)
  when 1
    result = find_one(ids.first)
    expects_array ? [ result ] : result
  else
    find_some(ids)
  end
end
  • findメソッドにブロックが渡された場合(block_given?)、早期returnを抜けてfind_with_idsを実行する
  • find_with_ids内で色々やってidsにidの配列が格納された状態でcase文でsize(配列の要素数)による分岐処理を行う
  • 見つけました。今回の主題であるraise RecordNotFound.newの実行箇所です。idsの要素数が0だった場合にraiseでエラークラスが実行されています。このerror_messageをいつもブラウザやpostmanで見ていたわけですね。

ActiveRecodeのメソッド実行に失敗した時に特定のエラークラスが実行される仕組みをざっくり理解できました。
ここまで読んでいただき、ありがとうございました。

Discussion