1つのテーブルで2つの列を使用してリレーションシップを定義できるようにしたい。そのため、タスクアプリを例として使用します。
試み1:
_class User < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
end
_
それでTask.create(owner_id:1, assignee_id: 2)
これにより、ser oneを返す_Task.first.owner
_とser twoを返す_Task.first.assignee
_を実行できますが、_User.first.task
_は何も返しません。これは、タスクがユーザーに属しておらず、ownerおよびassigneeに属しているためです。そう、
試み2:
_class User < ActiveRecord::Base
has_many :tasks, foreign_key: [:owner_id, :assignee_id]
end
class Task < ActiveRecord::Base
belongs_to :user
end
_
2つの外部キーがサポートされていないように見えるため、これは完全に失敗します。
したがって、私が望むのは_User.tasks
_と言い、ユーザーが所有するタスクと割り当てられたタスクの両方を取得できるようにすることです。
基本的に、何らかの方法でTask.where(owner_id || assignee_id == 1)
のクエリと同等の関係を構築します
それは可能ですか?
_Finder_sql
_を使用するつもりはありませんが、この問題の受け入れられない答えは、私が望むものに近いようです: Rails-Multiple Index Key Association
したがって、このメソッドは次のようになります。
試み3:
_class Task < ActiveRecord::Base
def self.by_person(person)
where("assignee_id => :person_id OR owner_id => :person_id", :person_id => person.id
end
end
class Person < ActiveRecord::Base
def tasks
Task.by_person(self)
end
end
_
_Rails 4
_で動作させることはできますが、次のエラーが発生し続けます。
_ActiveRecord::PreparedStatementInvalid: missing value for :owner_id in :donor_id => :person_id OR assignee_id => :person_id
_
class User < ActiveRecord::Base
def tasks
Task.where("owner_id = ? OR assigneed_id = ?", self.id, self.id)
end
end
User
クラスのhas_many :tasks
を削除します。
テーブルtasks
にhas_many :tasks
という名前の列がないため、user_id
を使用しても意味がありません。
私の場合の問題を解決するために私がしたことは:
class User < ActiveRecord::Base
has_many :owned_tasks, class_name: "Task", foreign_key: "owner_id"
has_many :assigned_tasks, class_name: "Task", foreign_key: "assignee_id"
end
class Task < ActiveRecord::Base
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
# Mentioning `foreign_keys` is not necessary in this class, since
# we've already mentioned `belongs_to :owner`, and Rails will anticipate
# foreign_keys automatically. Thanks to @jeffdill2 for mentioning this thing
# in the comment.
end
このようにして、User.first.assigned_tasks
とUser.first.owned_tasks
を呼び出すことができます。
これで、assigned_tasks
とowned_tasks
の組み合わせを返すtasks
というメソッドを定義できます。
読みやすさに関してはこれは良い解決策かもしれませんが、パフォーマンスの観点からは、tasks
を取得するために、1回ではなく2つのクエリが発行されます。 、その後、これら2つのクエリの結果も結合する必要があります。
したがって、ユーザーに属するタスクを取得するには、次の方法でtasks
クラスのカスタムUser
メソッドを定義します。
def tasks
Task.where("owner_id = ? OR assigneed_id = ?", self.id, self.id)
end
この方法では、1つのクエリですべての結果を取得し、結果をマージまたは結合する必要はありません。
上記の@ dre-hhの回答を拡張すると、Rails 5で期待どおりに動作しなくなったことがわかりました。Rails 5には、WHERE tasks.user_id = ?
、このシナリオにはuser_id
列がないため失敗します。
has_many
アソシエーションで動作させることはまだ可能であることがわかりました。Railsによって追加されたこの追加のwhere句のスコープを解除する必要があります。
class User < ApplicationRecord
has_many :tasks, ->(user) { unscope(:where).where("owner_id = :id OR assignee_id = :id", id: user.id) }
end
Rails 5:
has_manyの関連付けが必要な場合は、デフォルトのwhere句で@Dwightの回答を参照する必要があります。
User.joins(:tasks)
は私に
ArgumentError: The association scope 'tasks' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.
もはや不可能なので、@ Arslan ALiソリューションも使用できます。
Rails 4:
class User < ActiveRecord::Base
has_many :tasks, ->(user){ where("tasks.owner_id = :user_id OR tasks.assignee_id = :user_id", user_id: user.id) }
end
Update1:@JonathanSimmonsコメントについて
UserオブジェクトをUserモデルのスコープに渡す必要があるのは、逆向きのアプローチのようです
ユーザーモデルをこのスコープに渡す必要はありません。現在のユーザーインスタンスは、このラムダに自動的に渡されます。次のように呼び出します。
user = User.find(9001)
user.tasks
Update2:
可能であれば、この答えを展開して何が起こっているのか説明してもらえますか?同様のことを実装できるように、よりよく理解したいと思います。ありがとう
ActiveRecordクラスでhas_many :tasks
を呼び出すと、ラムダ関数がクラス変数に格納され、このラムダを呼び出すオブジェクトでtasks
メソッドを生成するための単なる便利な方法になります。生成されたメソッドは、次の擬似コードのようになります。
class User
def tasks
#define join query
query = self.class.joins('tasks ON ...')
#execute tasks_lambda on the query instance and pass self to the lambda
query.instance_exec(self, self.class.tasks_lambda)
end
end
私はこれに対する解決策を考え出しました。私はこれをどのように改善できるかについてのポインタを受け入れています。
class User < ActiveRecord::Base
def tasks
Task.by_person(self.id)
end
end
class Task < ActiveRecord::Base
scope :completed, -> { where(completed: true) }
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
def self.by_person(user_id)
where("owner_id = :person_id OR assignee_id = :person_id", person_id: user_id)
end
end
これは基本的にhas_manyアソシエーションをオーバーライドしますが、ActiveRecord::Relation
探していたオブジェクト。
だから今、私はこのようなことをすることができます:
User.first.tasks.completed
そして、結果はすべて所有されているか、最初のユーザーに割り当てられた完了したタスクです。
Associations and(multiple)foreign key in Rails(3.2):モデルでそれらを記述し、マイグレーションを記述する方法 に対する私の答えはあなたのためです!
あなたのコードについては、ここに私の修正があります
class User < ActiveRecord::Base
has_many :tasks, ->(user) { unscope(where: :user_id).where("owner_id = ? OR assignee_id = ?", user.id, user.id) }, class_name: 'Task'
end
class Task < ActiveRecord::Base
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
end
警告:RailsAdminを使用していて、新しいレコードを作成したり、既存のレコードを編集する必要がある場合は、私が提案したことをしないでください。このようなことをすると問題が発生します。
current_user.tasks.build(params)
理由は、Railsはcurrent_user.idを使用してtask.user_idを埋めようとしますが、user_idのようなものがないことを見つけるためだけです。
ですから、私のハック方法を箱の外の方法と考えてください。しかし、そうしないでください。