여러 외래 키와 Rails 연결


84

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

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

2
이 보석이 당신이 찾고있는 것입니까? github.com/composite-primary-keys/composite_primary_keys
mus

정보 mus에 감사하지만 이것은 내가 찾고있는 것이 아닙니다. 주어진 값이되는 또는 열에 대한 쿼리를 원합니다. 복합 기본 키가 아닙니다.
JonathanSimmons

예, 업데이트를 통해 명확 해집니다. 보석은 잊어 버려. 우리는 둘 다 구성된 기본 키를 사용하기를 원한다고 생각했습니다. 이는 최소한 사용자 지정 범위를 범위 관계로 정의하여 가능해야합니다. 흥미로운 시나리오. 나중에 그것을보고해야합니다
DRE-HH에게

FWIW 여기서 내 목표는 주어진 사용자 작업을 가져오고 ActiveRecord :: Relation 형식을 유지하여 검색 / 필터링 결과에서 작업 범위를 계속 사용할 수 있도록하는 것입니다.
JonathanSimmons

답변:


80

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

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


이 솔루션은 훌륭합니다! 이는 각각 할당, 소유 및 모든 작업에 대한 별도의 액세스에 대한 요구 사항을 의미합니다. 대규모 프로젝트에서 좋은 예감이 될 것입니다. 그러나 그것은 내 경우에는 요구 사항이 아닙니다. has_many"이치에 맞지 않음"에 관해서 받아 들여진 답변을 읽으면 우리는 has_many선언을 전혀 사용하지 않았 음 을 알 수 있습니다. 귀하의 요구 사항이 다른 모든 방문자의 요구 사항과 일치한다고 가정하면이 답변은 덜 판단적일 수 있습니다.
JonathanSimmons

이것이 우리가이 문제를 가진 사람들에게 옹호해야 할 더 나은 장기적인 설정이라고 생각합니다. 다른 모델의 기본 예와 결합 된 작업 집합을 검색하는 사용자 방법을 포함하도록 답변을 수정하려면 선택한 답변으로 수정하겠습니다. 감사!
JonathanSimmons

그래 나도 너와 같은 생각이야. 사용 owned_tasksassigned_tasks성능 힌트를해야합니다 모든 작업을 얻을 수 있습니다. 어쨌든, 나는 내 대답을 업데이트하고 User모든 관련 tasks을 가져 오기 위해 클래스에 메서드를 포함 시켰 으며 결과를 하나의 쿼리로 가져 오며 병합 / 결합을 수행 할 필요가 없습니다.
아르 슬란 알리

2
참고로 Task모델에 지정된 외래 키 는 실제로 필요하지 않습니다. 및 belongs_to라는 관계 가 있으므로 Rails는 기본적으로 외래 키의 이름이 각각 및 라고 가정합니다 . :owner:assigneeowner_idassignee_id
jeffdill2

1
@ArslanAli 문제 없습니다. :-)
jeffdill2

47

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

has_many연관 과 함께 작동하는 것이 여전히 가능 하다는 것을 알았습니다. Rails에서 추가 한이 추가 where 절의 범위를 해제하면됩니다.

class User < ApplicationRecord
  has_many :tasks, ->(user) {
    unscope(:where).where(owner: user).or(where(assignee: user)
  }
end

감사합니다 @Dwight, 나는 이것을 결코 생각하지 못했을 것입니다!
Gabe Kopley

belongs_to(부모에 ID가 없으므로 다중 열 PK를 기반으로해야하는 경우) 이 작업을 수행 할 수 없습니다 . 관계가 nil이라고 만 말합니다 (콘솔을 보면 람다 쿼리가 실행되는 것을 볼 수 없습니다).
fgblomqvist

@fgblomqvist belongs_to에는 : primary_key라는 옵션이 있습니다.이 옵션은 연결에 사용되는 관련 개체의 기본 키를 반환하는 메서드를 지정합니다. 기본적으로 이것은 id입니다. 나는 이것이 당신을 도울 수 있다고 생각합니다.
Ahmed Kamal

@AhmedKamal 나는 그것이 전혀 유용하지 않다는 것을 알지 못합니까?
fgblomqvist

24

레일즈 5 :

여전히 has_many 연관성을 원한다면 @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 솔루션도 사용할 수 있습니다.

레일스 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.find(9001)
user.tasks

업데이트 2 :

가능하다면이 답변을 확장하여 무슨 일이 일어나고 있는지 설명 할 수 있습니까? 비슷한 것을 구현할 수 있도록 더 잘 이해하고 싶습니다. 감사

has_many :tasksActiveRecord 클래스를 호출 하면 일부 클래스 변수에 람다 함수가 저장되며이 람다 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

1
정말 대단, 다른 곳 난 사람들이 복합 키의이 오래된 보석을 사용하는 것이 좋습니다 오려고 보았다
FireDragon

2
가능하다면 무슨 일이 일어나고 있는지 설명하기 위해이 답변을 확장 할 수 있습니까? 비슷한 것을 구현할 수 있도록 더 잘 이해하고 싶습니다. 감사합니다
스티븐 리드

1
이것은 연관성을 유지하지만 다른 문제를 유발합니다. 문서에서 : 참고 : 이러한 연관의 조인, 즉시로드 및 사전로드는 완전히 불가능합니다. 이러한 작업은 인스턴스 생성 전에 발생하며 범위는 nil 인수로 호출됩니다. 이로 인해 예상치 못한 동작이 발생할 수 있으며 더 이상 사용되지 않습니다. api.rubyonrails.org/classes/ActiveRecord/Associations/…
s2t2

1
약속하지만, 레일 5 개 끝을 여전히 함께 foreign_key 사용이 외모 where쿼리를 대신 바로 위의 람다를 사용하여
모하메드 엘 Mahallawy

1
예, Rails 5에서 더 이상 작동하지 않는 것처럼 보입니다.
Dwight

13

나는 이것에 대한 해결책을 찾았습니다. 나는 이것을 어떻게 더 좋게 만들 수 있는지에 대한 어떤 조언도 열려 있습니다.

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 결과는 모든 완료된 작업이 소유하거나 첫 번째 사용자에게 할당됩니다.


여전히이 방법을 사용하여 질문을 해결하고 있습니까? 나는 같은 배를 타고 새로운 방법을 배웠는지 아니면 이것이 여전히 최선의 선택인지 궁금합니다.
Dan

여전히 내가 찾은 최고의 옵션입니다.
JonathanSimmons

그것은 아마도 오래된 시도의 잔재 일 것입니다. 그것을 제거하기 위해 답변을 편집했습니다.
JonathanSimmons

1
이것과 아래의 dre-hh의 대답이 같은 일을 할 수 있습니까?
dkniffin 2015 년

1
> 사용자 개체를 사용자 모델의 범위로 전달해야하는 것은 역방향 접근 방식처럼 보입니다. 아무것도 전달할 필요가 없습니다. 이것은 람다이고 현재 사용자 인스턴스가 자동으로 여기에 전달됩니다.
dre-hh

2

Rails (3.2)의 Associations 및 (여러) 외래 키에 대한 나의 대답 : 모델에서 그들을 설명하고 마이그레이션을 작성하는 방법 은 당신을위한 것입니다!

귀하의 코드는 다음과 같습니다.

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가 task.user_id를 채우기 위해 current_user.id를 사용하려고 시도하지만 user_id와 같은 것이 없다는 것을 발견하기 때문입니다.

따라서 내 해킹 방법을 상자 밖의 방법으로 생각하되 그렇게하지 마십시오.


이 답변이 승인 된 답변이 아직 제공하지 않은 내용을 제공하는지 확실하지 않습니다. 또한 다른 질문에 대한 광고처럼 느껴집니다.
JonathanSimmons

@JonathanSimmons 감사합니다. 광고처럼 행동하는 것이 나쁘면이 답변을 제거하겠습니다.
sunsoft

2

Rails 5부터는 ActiveRecord보다 안전한 방법으로 할 수 있습니다.

def tasks
  Task.where(owner: self).or(Task.where(assignee: self))
end
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.