Programing

여러 외래 키와 Rails 연관

crosscheck 2020. 10. 24. 09:37
반응형

여러 외래 키와 Rails 연관


한 테이블에 두 개의 열을 사용하여 관계를 정의 할 수 있기를 원합니다. 따라서 작업 앱을 예로 사용합니다.

시도 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)

이 날 수행 할 수 있습니다 Task.first.owner반환하는 사용자를 하고 Task.first.assignee반환하는 사용자이 있지만 User.first.task반환 아무것도. 작업은 사용자에게 속하지 않고 소유자담당자 에게 속하기 때문 입니다. 그래서,

시도 2 :

class User < ActiveRecord::Base
  has_many :tasks, foreign_key: [:owner_id, :assignee_id]
end

class Task < ActiveRecord::Base
  belongs_to :user
end

두 개의 외래 키가 지원되지 않는 것처럼 보이므로 모두 실패합니다.

그래서 내가 원하는 것은 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

TL; DR

class User < ActiveRecord::Base
  def tasks
    Task.where("owner_id = ? OR assigneed_id = ?", self.id, self.id)
  end
end

수업 has_many :tasks에서 제거하십시오 User.


table에 has_many :tasks이름이 지정된 열이 없기 때문에 사용 은 전혀 의미가 없습니다 .user_idtasks

내 경우 문제를 해결하기 위해 내가 한 일은 다음과 같습니다.

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.

이제 tasks의 조합을 반환하는 라는 메서드를 정의 할 수 있습니다 .assigned_tasksowned_tasks

가독성에 관한 한 좋은 해결책이 될 수 있지만 성능 측면에서 지금만큼 좋지 않을 것입니다.를 얻으려면 tasks한 번이 아닌 두 개의 쿼리가 실행되고 결과가 이 두 쿼리 중 또한 조인되어야합니다.

따라서 사용자에게 속한 작업을 가져 오기 위해 다음과 같은 방식으로 클래스에서 사용자 지정 tasks메서드를 정의합니다 User.

def tasks
  Task.where("owner_id = ? OR assigneed_id = ?", self.id, self.id)
end

이렇게하면 단일 쿼리로 모든 결과를 가져 오므로 결과를 병합하거나 결합 할 필요가 없습니다.


위의 @ dre-hh의 답변을 확장하면 Rails 5에서 더 이상 예상대로 작동하지 않는 것으로 나타났습니다. Rails 5에는 이제의 효과에 대한 기본 where 절이 포함되어 있으며이 시나리오 WHERE tasks.user_id = ?에는 user_id이 없으므로 실패합니다 .

I've found it is still possible to get it working with a has_many association, you just need to unscope this additional where clause added by Rails.

class User < ApplicationRecord
  has_many :tasks, ->(user) { unscope(:where).where("owner_id = :id OR assignee_id = :id", id: user.id) }
end

Rails 5:

you need to unscope the default where clause see @Dwight answer if you still want a has_many associaiton.

Though User.joins(:tasks) gives me

ArgumentError: The association scope 'tasks' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.

As it is no longer possible you can use @Arslan Ali solution as well.

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: Regarding @JonathanSimmons comment

Having to pass the user object into the scope on the User model seems like a backwards approach

You don't have to pass the user model to this scope. The current user instance is passed automatically to this lambda. Call it like this:

user = User.find(9001)
user.tasks

Update2:

if possible could you expand this answer to explain what's happening? I'd like to understand it better so I can implement something similar. thanks

Calling has_many :tasks on ActiveRecord class will store a lambda function in some class variable and is just a fancy way to generate a tasks method on its object, which will call this lambda. The generated method would look similar to following pseudocode:

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

I worked out a solution for this. I'm open to any pointers on how I can make this better.

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

This basically overrides the has_many association but still returns the ActiveRecord::Relation object I was looking for.

So now I can do something like this:

User.first.tasks.completed and the result is all completed task owned or assigned to the first user.


My answer to Associations and (multiple) foreign keys in rails (3.2) : how to describe them in the model, and write up migrations is just for you!

As for your code,here are my modifications

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

Warning: If you are using RailsAdmin and need to create new record or edit existing record,please don't do what I've suggested.Because this hack will cause problem when you do something like this:

current_user.tasks.build(params)

The reason is that rails will try to use current_user.id to fill task.user_id,only to find that there is nothing like user_id.

So,consider my hack method as an way outside the box,but don't do that.

참고URL : https://stackoverflow.com/questions/24642005/rails-association-with-multiple-foreign-keys

반응형