4
0

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 1 year has passed since last update.

ActiveRecord オブジェクトを dup メソッドでコピーする場合は、インスタンス変数もコピーすることに注意する

Last updated at Posted at 2022-06-15

バージョン情報

$ ruby -v
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [arm64-darwin21]

$ bin/rails -v
Rails 7.0.3

内容

ActiveRecord オブジェクトの属性 (attributes) を引き継いだ別のオブジェクトを作成し、データベースに保存したい場合に ActiveRecord::Core#dup を使用することがある。この場合、たしかに属性は引き継がれるが、同時に Object#dup の振る舞いによりインスタンス変数の値も引き継ぐ

https://docs.ruby-lang.org/en/3.1/Object.html#method-i-dup より

Produces a shallow copy of obj—the instance variables of obj are copied, but not the objects they reference.

そのため、例えば以下のようにインスタンス変数を使って特定の値をメモ化している場合、コピー先にもその値を意図せず引き継ぐ場合があるので注意が必要だ。

app/models/monster.rb
class Monster < ApplicationRecord
  def monster_itself
    @monster_itself ||= self
  end
end
monster = Monster.find_by!(name: 'リオレウス')
#=> #<Monster:0x00000001098be980 id: 1, name: "リオレウス", category: "飛竜種", habitat: "森丘", created_at: ...>
monster.monster_itself.name
#=> "リオレウス"

subsp_monster = monster.dup
#=> #<Monster:0x000000010b0af508 id: nil, name: "リオレウス", category: "飛竜種", habitat: "森丘", created_at: nil, updated_at: nil>
subsp_monster.assign_attributes(name: 'リオレウス亜種')
subsp_monster.name
#=> "リオレウス亜種"
subsp_monster.monster_itself.name # あれ? 🤔
#=> "リオレウス"

# subsp_monster.monster_itself が自分自身ではなく、コピー元のオブジェクトを返す 🤔
subsp_monster.monster_itself == monster
#=> true
subsp_monster.monster_itself == subsp_monster
#=> false

これを防ぐには、dup メソッドを使用せずにコピー元のオブジェクトの属性を使用して新しいオブジェクトを作成するようにする。

monster = Monster.find_by!(name: 'リオレウス')
monster.monster_itself.name
#=> "リオレウス"

attributes = monster.attributes.symbolize_keys.except(:id, :created_at, :updated_at)
#=> {:name=>"リオレウス", :category=>"飛竜種", :habitat=>"森丘"}
subsp_monster = Monster.new(attributes.merge(name: 'リオレウス亜種'))
#=> #<Monster:0x00000001073bb408 id: nil, name: "リオレウス亜種", category: "飛竜種", habitat: "森丘", created_at: nil, updated_at: nil>
subsp_monster.name
#=> "リオレウス亜種"
subsp_monster.monster_itself.name
#=> "リオレウス亜種"

# subsp_monster.monster_itself が自分自身を返すようになった 😉
subsp_monster.monster_itself == monster
#=> false
subsp_monster.monster_itself == subsp_monster
#=> true

参考

4
0
1

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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?