2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

replaceメソッドを活用した関連テーブルの更新

Last updated at Posted at 2024-08-26

はじめに

Rubyにはreplaceというメソッドがあります。

Rubyの配列 (Array) やハッシュ (Hash) などのオブジェクトに対して使われ、対象オブジェクトの内容を別のオブジェクトの内容で置き換えるメソッドです。

よく見るのは

a = [1, 2, 3]
a.replace [4, 5, 6]
p a                 #=> [4, 5, 6]
hash1 = { a: 1, b: 2 }
hash2 = { c: 3, d: 4 }

hash1.replace(hash2)
puts hash1.inspect #=> { c: 3, d: 4 }

この辺りでしょうか??

今回はreplaceを配列やハッシュではなく、ActiveRecordと一緒に使ってみることにします。

replaceを使ったコード例

設計内容や実装メソッドの意図に関しては思いつきなので、あまり気にしないでください

シナリオ

今回は、ユーザー (User) モデルがプロジェクト (Project) とそのプロジェクトに関連するタスク (Task)、さらにタスクに紐づくコメント (Comment) に関連しているという設定を考えます。

モデル関係

User: 複数の Project を持つ。
Project: 複数の Task を持つ。
Task: 複数の Comment を持つ。
UserProject: ユーザに関連するプロジェクト。

やること

Userに紐づいているUserProjectを全て削除する
新たに紐づいているTaskを取得し、それをキーにUserProjectをbuildする

今までdestroy_allを使用して実装していたので、今回はreplaceを使用して実装してみます。

コード

user.rb
class User < ApplicationRecord
  # association
  has_many :user_projects, dependent: :destroy
  has_many :projects

  # callback
  before_save :set_user_projects

  def set_user_projects
    user_projects.replace(
      project_tasks.map do |task|
        user_projects.build(task: task)
      end
    )
  end

  private

  def project_tasks
    Task.joins(project: :user)
        .where(projects: { id: project_ids })
        .distinct
  end
end

説明

ここでは、user_projects に対して replace を使用して、関連するタスク (Task) の情報を一括で更新しています。

user_projects.replace

既存の user_projects を置き換える処理です。このメソッドを使うと、destroy_all を使って既存のレコードを一旦削除する必要がなく、パフォーマンスの改善や無駄な削除処理の回避が可能です。

project_tasks.map

ユーザーに関連するプロジェクト、タスクを辿ってタスク (Task) を取得しています。

user_projects.build
それぞれの Task に対して、新たに user_project を作成しています。
replace メソッドを使うことで、既存のデータを無駄に削除することなく、効率的に関連テーブルのデータを更新できます。

destroy_allでの実装時との違い

類似の処理を行うには、destroy_all を使って既存の関連を削除し、その後新しい関連を追加する方法もあります。

例えば、destroy_all を使った場合のコードは以下のようになります。

user.rb
def set_user_projects
  user_projects.destroy_all

  project_tasks.map do |task|
    user_projects.build(task: task)
  end
end

こちらはぱっと見ると理解しやすいですが、次のようなデメリットもあります。

パフォーマンスの低下: destroy_allは個別に削除を行うため、多くの関連オブジェクトがある場合にパフォーマンスが低下する可能性がある。

トランザクション管理の煩雑さ: 一連の操作が複数のステップに分かれているため、エラーが発生した場合にトランザクションの管理が複雑になります。

まとめ

個人的にあまりreplaceを使用する場面に出会わなかった(気づいていなかった)のですが、今回の例に出したように時にdestroy_allの代わりに使用したりと、使い所はあるなと感じました。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?