LoginSignup
4
2

More than 3 years have passed since last update.

railsのメソッド"save"がどう定義されているのか読解してみた

Posted at

rails読解シリーズ第5回目はsaveメソッド。

save
# File activerecord/lib/active_record/base.rb, line 2575
def save
  create_or_update
end

create_or_updateメソッドが呼ばれているだけのようです。

create_or_update
# File activerecord/lib/active_record/base.rb, line 2916
def create_or_update
  raise ReadOnlyRecord if readonly?
  result = new_record? ? create : update
  result != false
end

2行目で、対象のインスタンスが読み取り専用かどうか確認するようです。
メソッドの名前からして、readonly属性はboolean型で定義して、readonly: trueとすることで、利用することができそうです。
プログラマーが自ら定義することもできますが、railsのことだし、メソッドによって定義する方法もありそう。

readonly?
# File activerecord/lib/active_record/base.rb, line 628
      def readonly?
        @readonly
      end

読み取り専用のインスタンスであった場合はReadOnlyRecordエラーが出力されます

ReadOnlyRecordモデル
class ReadOnlyRecord < ActiveRecordError
end
3行目
result = new_record? ? create : update

3項演算子の条件はnew_record?メソッド。
名前の通り、新規レコードか確認するものでしょう。

new_record?
# File activerecord/lib/active_record/base.rb, line 2554
def new_record?
  @new_record || false
end

なんとなく予想できたかもしれませんが、こちらも先程のreadonly?メソッドと同じ仕様のようです。
new_record属性にtrueを設定していた場合はtrueを返し、特に指定がなければfalseとなる模様。
先程は|| falseの記述がなかったので、どう違う動きをするのか気になります。

rubyの場合、値が設定されていない=nilはfalseと判定されます。
わざわざ設定する理由としては2つ考えられます。
1. true、false以外の値が代入された場合にtrue判定してしまうのを防ぐため
2. new_record属性として参照したメモリの位置に以前使用したときのデータが残っている場合の誤動作を防ぐため

2.については組み込みで用いられるC言語など高級でない言語が用いられる分野で考慮されることがあるそうです。
Rubyのような言語でも懸念は必要なのでしょうか。

逆に、readonly?メソッドに同じ処理が施されていないということは、readonly属性として、必ず値が設定される仕組みがあるということでしょうか。

3行目
result = new_record? ? create : update

3項演算子に戻ります。
新しいレコードであればcreateメソッド、そうでなければupdateメソッドが呼び出されます。
ここはシンプルですね。

4行目
result != false

3行目でresultに値が代入された場合はこの等式はtrueとなり、それ以外の場合はfalseとなります。

よくコントローラで

if save
  redirect_to xxx
else
  render :new
end

のような用いられ方をしますが、4行目の等式の結果が返されて、それをif文に使用しているということですね。

saveメソッドの読解は完了です。
ついでに類似メソッドとして、save!メソッドを読んでみましょう。

save!メソッドの読解

save!
# File activerecord/lib/active_record/base.rb, line 2592
      def save!
        create_or_update || raise(RecordNotSaved)
      end

create_or_updateはsaveメソッドと同じ動きですね。

save!メソッドの特徴は保存に失敗したときにエラーを吐くこと。
保存に失敗するとcreate_or_updateがfalseを返します。
A||BのときAがfalseの場合はBが判定されます。

ここでraiseメソッドによりRecordNotSavedエラーが出力されます。

RecordNotSavedモデル
class RecordNotSaved < ActiveRecordError
  attr_reader :record

  def initialize(message = nil, record = nil)
    @record = record
    super(message)
  end
end

このクラスのモデルにより、エラーメッセージが出力されるようです。
エラーの動作の仕組みは勉強にはなりそうですが、今回の記事の趣旨からは外れるので、別の機会に筆は譲りましょう。

まとめ

saveメソッドの動き

  1. 読み取り専用か確認し、必要に応じてエラーを吐く
  2. 新規データかどうかによってcreateまたはupdateメソッドを呼び出す。
  3. 結果に応じてtrueまたはfalseを返す

save!メソッドの動き

saveメソッドの中で上記1~3を実行するメソッドが3にてfalseを返した時、RecordNotSavedエラーを吐く

感想

  • railsの学習を始めた頃のイメージはcreateはnew+saveだよ〜とおぼえていたので、saveの中でcreateメソッドが呼ばれていたのは驚きだった
  • create/updateメソッドとエラー周りの仕組みに関心をもったので読んでみたい。
  • めちゃくちゃ重要な機能をシンプルで短いコードで実現していて美しい!! 特に4行目。

今回もマニアックな記事を読んでいただきありがとうございました。
またお会いしましょう。

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