Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: Active Supportのdeep_dupの適切な使い所を理解する(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

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しようとするのは本来の目的に反しており、予期しない形で振る舞う可能性があります。

関連記事

Rails 8 API: ActionController::Parameters(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。