복붙노트

[RUBY-ON-RAILS] 여러 외래 키와 관련 레일

RUBY-ON-RAILS

여러 외래 키와 관련 레일

나는 관계를 정의하는 하나 개의 테이블에 두 개의 열을 사용할 수 있어야합니다. 예를 들어 작업 응용 프로그램을 사용 그래서.

일을 시도 :

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)

이 날은 사용자가이 반환하지만 User.first.task는 아무 것도 반환하지 않습니다 사용자 하나 Task.first.assignee을 반환 Task.first.owner을 수행 할 수 있습니다. 어떤 작업이 사용자에 속해 있지 않기 때문에, 그들은 소유자 및 양수인에 속한다. 그래서,

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을 사용 찾는 게 아니에요,하지만 내가 원하는이 문제의 용인되지 응답 외모에 가까운 수 : 레일 - 여러 인덱스 키 협회

이 방법은 다음과 같을 것입니다 그래서,

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

나는 레일 4 일에 그것을 얻을 수 있지만, 나는 다음과 같은 오류가 점점 계속 :

ActiveRecord::PreparedStatementInvalid: missing value for :owner_id in :donor_id => :person_id OR assignee_id => :person_id

해결법

  1. ==============================

    1.

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

    has_many 제거 : 사용자 클래스의 작업을.

    has_many 사용 : 우리가 테이블 작업에 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.owned_tasks로 User.first.assigned_tasks를 호출 할 수 있습니다.

    지금, 당신은 assigned_tasks 및 owned_tasks의 조합을 반환하는 작업이라는 방법을 정의 할 수 있습니다.

    훨씬 가독성이 간다 그거 좋은 솔루션이 될 수 있지만,보기의 성능 지점에서, 그 다음의 작업을 얻기 위해, 두 개의 쿼리 대신 한 번으로 발급됩니다, 이제 훨씬 좋은, 그리고 것 두 쿼리의 결과뿐만 아니라 가입해야합니다.

    그래서 사용자에 속하는 작업을 얻기 위해, 우리는 다음과 같은 방법으로 사용자 클래스에서 사용자 지정 작업 방법을 정의하는 것입니다 :

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

    이 방법은, 그것은 하나 개의 쿼리에서 모든 결과를 가져올 것입니다, 우리는 병합하거나 어떤 결과를 결합하지 않을 것입니다.

  2. ==============================

    2.레일 5에 예상대로 내가 뭔가 레일 (5)는 이제 기본 포함되어 나타납니다 더 이상 작품을 발견 위 @ 드레-HH의 대답에 확장 경우의 효과에 WHERE 절 tasks.user_id =?, 실패하는 더 USER_ID 컬럼이 없기 때문에 이 시나리오한다.

    레일 5에 예상대로 내가 뭔가 레일 (5)는 이제 기본 포함되어 나타납니다 더 이상 작품을 발견 위 @ 드레-HH의 대답에 확장 경우의 효과에 WHERE 절 tasks.user_id =?, 실패하는 더 USER_ID 컬럼이 없기 때문에 이 시나리오한다.

    나는 그것이 has_many 협회와 함께 작업을 진행하는 것은 여전히 ​​가능하다 발견했습니다, 당신은 절 레일 추가 곳이 추가 unscope해야합니다.

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

    3.5 레일 :

    5 레일 :

    당신은 여전히 ​​has_many 연결을 원하는 경우 절은 @Dwight 대답을 참조 기본값을 unscope해야합니다.

    비록 User.joins (: 작업) 저를 준다

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

    더 이상 가능한 한 당신은뿐만 아니라 @Arslan 알리 솔루션을 사용할 수 없습니다.

    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
    

    갱신 1 :    에 관한 @JonathanSimmons 코멘트

    이 범위를 사용자 모델을 통과 할 필요가 없습니다. 현재 사용자의 인스턴스가이 람다에 자동으로 전달됩니다. 이처럼 전화 :

    user = User.find(9001)
    user.tasks
    

    갱신 2 :

    호출 has_many : 액티브 클래스의 작업이 어떤 클래스 변수에 람다 함수를 저장하고이 람다를 호출은 객체의 작업 방법을 생성하는 단지 멋진 방법입니다 것입니다. 생성 방법은 다음 의사 유사합니다 :

    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
    
  4. ==============================

    4.나는 이것에 대한 해결책을했다. 나는이 더 잘 할 수있는 방법에 어떤 포인터에 열려있어.

    나는 이것에 대한 해결책을했다. 나는이 더 잘 할 수있는 방법에 어떤 포인터에 열려있어.

    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 협회를 무시하지만 아직도 내가 찾고 있던 액티브 :: 관계 개체를 반환합니다.

    그래서 지금은 이런 식으로 뭔가를 할 수 :

    User.first.tasks.completed 그 결과는 소유하거나 첫 번째 사용자에게 할당 된 모든 완료 작업입니다.

  5. ==============================

    5.레일 5 있기 때문에 당신은 또한 액티브 안전한 방법 인 그렇게 할 수 있습니다 :

    레일 5 있기 때문에 당신은 또한 액티브 안전한 방법 인 그렇게 할 수 있습니다 :

    def tasks
      Task.where(owner: self).or(Task.where(assignee: self))
    end
    
  6. ==============================

    6.내 협회에 대한 답변과 레일 (복수) 외래 키 (3.2) : 모델에서 그들을 설명하고 마이그레이션을 작성하는 방법을 당신을 위해 단지입니다!

    내 협회에 대한 답변과 레일 (복수) 외래 키 (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과 필요성을 사용하는 경우, 당신은 같은 것을 할 때이 해킹 문제가 발생할 것입니다 제가 suggested.Because을했습니다하지 마십시오 :

    current_user.tasks.build(params)
    

    그 이유는 사용 task.user_id 채우기 위해 current_user.id에 레일 만 USER_ID 같은 것이없는 것을 찾기 위해 시도 할 것입니다.

    그래서, 박스 외부의 방법으로 내 해킹 방법을 고려하지만, 그렇게하지 않습니다.

  7. from https://stackoverflow.com/questions/24642005/rails-association-with-multiple-foreign-keys by cc-by-sa and MIT license