Rails: Active Supportのdeep_dupの適切な使い所を理解する(翻訳)
Railsで複雑なデータ構造を扱う場合、ネストしたオブジェクトもすべて含んだ形でオブジェクトのコピーを作成する必要が生じることがあります。
ただし、このような複雑な構造のコピーでは注意が必要です。
Railsが提供しているdeep_dup
メソッドは、オブジェクト構造のディープコピーを作成する強力な手段です。
🔗 以下のように書くのではなく
通常のdup
メソッドを使う(ネストした構造全体まではコピーされません)。
og_andy = {
name: "Andy",
age: 45,
address: { city: "Brighton", country: "UK" },
hobbies: ["football", "parenting"]
}
new_andy = og_andy.dup
# [thankgoodness.game](https://thankgoodness.game)を参照
new_andy[:address][:city] = "Barnsworth"
new_andy[:hobbies] << "slapping"
new_andy[:hobbies] << "jumping"
og_andy[:address][:city]
#=> "Barnsworth" (元の"Brighton"が変わってしまった)
og_andy[:hobbies]
#=> ["football", "parenting", "slapping", "jumping"] (元のhobbies:の内容がが変わってしまった)
🔗 以下のように書くこと
Railsのdeep_dup
を用いて、構造全体をコピーする。
og_andy = {
name: "Andy",
age: 45,
address: { city: "Brighton", country: "UK" },
hobbies: ["football", "parenting"]
}
new_andy = og_andy.deep_dup
new_andy[:address][:city] = "Barnsworth"
new_andy[:hobbies] << "slapping"
new_andy[:hobbies] << "jumping"
og_andy[:address][:city]
#=> "Brighton"
og_andy[:hobbies]
#=> ["football", "parenting"]
new_andy[:address][:city]
#=> "Barnsworth"
new_andy[:hobbies]
#=> ["football", "parenting", "slapping", "jumping"]
🔗 そうする理由
RailsのActive Supportが提供するdeep_dup
メソッドは、元のオブジェクトとのつながりを持たない完全な複製を作成します。元のオブジェクトの中でハッシュや配列などのオブジェクトがネストしている場合は、ネストした構造全体が複製されます。
これによって、複製したオブジェクトを変更したときに、誤ってコピー元のオブジェクトを意図せず変更してしまい、微妙なバグが発生してしまう上述のような事態を防げるようになります。
#dup
メソッドは、RubyのObject
にあるコアメソッドの中に含まれているので、ほとんどのオブジェクトで実装されます。Railsの実装では、独自の#dup
メソッドを実装するRubyオブジェクトも適切に利用するようになっています。
この機能が特に有用なのは、データの一貫性や分離が重要な作業(Service Objectやフォーム、複雑なparams
の処理など)、あるいは元データに影響を与えないためにデータを複製して作業する必要が生じた場合です。
Rails自身のソースコードでも、複雑な引数をメソッドやオブジェクトに渡して処理するときにdeep_dup
メソッドが多用されています。
また、deep_dup
は以下のように利用することも可能です。
def mess_with_passed_params(params)
attributes = params.deep_dup
# これで、paramsを誤って改変せずにattributesを変更可能になる
end
🔗 そうしない理由があるとすれば
複製する構造が極めて大きい場合は、deep_dup
を使うとパフォーマンスに悪影響が生じる可能性があることも考慮しておく必要があります。たとえば、ネスト構造が非常に深い大規模な構造をdeep_dup
すると、パフォーマンスが目に見えて落ちる可能性があります。
また、データベースコネクションやファイルハンドルなどの外部リソースに対してdeep_dup
を行っても、外部リソースは複製されないため、そのような状況での利用は制限されます。
また、構造にシングルトンオブジェクトが含まれている場合は複製を避けるべきでしょう。仕様上、シングルトンオブジェクトは複数存在するべきではありません。シングルトンオブジェクトを#dup
しようとするのは本来の目的に反しており、予期しない形で振る舞う可能性があります。
概要
元サイトの許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。