39
18

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 5 years have passed since last update.

ActiveRecordのsave()の挙動、あるいはcreate()とupdate()の挙動の違いについて

Last updated at Posted at 2019-08-08

update()はattributesを引数に取れるのでsave()でも出来るよなーと思ってRails Consoleで確認したら出来るときと出来ないときがあったので確認した。

まずはupdateの実装がどうなっているかを公式ドキュメントのupdateで確認。

update()は以下の実装になっている。

    # File activerecord/lib/active_record/persistence.rb, line 423
    def update(attributes)
      # The following transaction covers any possible database side-effects of the
      # attributes assignment. For example, setting the IDs of a child collection.
      with_transaction_returning_status do
        assign_attributes(attributes)
        save
      end
    end

内部的には assign_attributes してからsaveしているだけ。

ここで疑問としてsaveって引数に更新対象渡せば更新してくれたと思うんだけど何故assign_attributesしているのか?と疑問に思った。

なのでsaveの実装を確認。

save()は以下の実装になっており、この create_or_update*argsが有効になるのは更新時のみ。

    # File activerecord/lib/active_record/persistence.rb, line 274
    def save(*args, &block)
      create_or_update(*args, &block)
    rescue ActiveRecord::RecordInvalid
      false
    end
    # File activerecord/lib/active_record/persistence.rb, line 702
    def create_or_update(*args, &block)
      _raise_readonly_record_error if readonly?
      return false if destroyed?
      result = new_record? ? _create_record(&block) : _update_record(*args, &block)
      result != false
    end

なのでsave()で新規登録時に引数に name: 'luccafort', message: 'こんにちわこんにちわ'というのを期待しても保存はされず、更新時にのみ有効になる。
なので当初思い込んでいたsave()で渡した引数が有効になる、は半分正解だけど半分不正解。

save()でも値を反映されてほしい場合は↓みたいにする必要があるってことでいいのかな?


User.save() do |u|
  u.name = '名無しの権兵衛'
end

追記:
Twitterで「save()で新規登録するときはcreateを使っている」という指摘を受けた。
明示的に新規登録と更新は分けたほうがコンテクストが混在せずに良さそうだし、新規登録時の処理もupdate()と同じように引数を取るのでこちらのほうが勘違いせずに済みそう。

createの実装はこうなっているので明示的にするならこっちのほうが勘違いせずにすむのでこちらのほうが良さそう。

      # File activerecord/lib/active_record/base.rb, line 504
      def create(attributes = nil, options = {}, &block)
        if attributes.is_a?(Array)
          attributes.collect { |attr| create(attr, options, &block) }
        else
          object = new(attributes, options)
          yield(object) if block_given?
          object.save
          object
        end
      end

この辺の違いをちゃんと認識してなかったので update() は引数渡すと更新してくれるけど save() はOKなときとNGなときがあるなーとざっくりした認識をしていたがようやくきちんと理解できた。

似たような勘違いをしている人がいたのでままあることなのか?と思いつつ、めちゃくちゃお世話になるメソッドの割にちゃんとわかってないの怖いなと思いましたまる

Railsのfind_or_create_byのblockはcreateした際にしか実行されない

39
18
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
39
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?