Относительно новичок в rails и попытка смоделировать очень простое семейное «дерево» с помощью единственной модели Person, у которой есть имя, пол, Father_id и mother_id (2 родителя). Ниже в основном то, что я хочу сделать, но, очевидно, я не могу повторить: children в has_many (первый перезаписывается).
class Person < ActiveRecord::Base
belongs_to :father, :class_name => 'Person'
belongs_to :mother, :class_name => 'Person'
has_many :children, :class_name => 'Person', :foreign_key => 'mother_id'
has_many :children, :class_name => 'Person', :foreign_key => 'father_id'
end
Есть ли простой способ использовать has_many с двумя внешними ключами или, возможно, изменить внешний ключ в зависимости от пола объекта? Или есть другой / лучший способ?
Спасибо!
Вы ищете "составные ключи": stackoverflow.com/questions/17882105/…





Я считаю, что вы можете достичь желаемых отношений, используя: has_one.
class Person < ActiveRecord::Base
has_one :father, :class_name => 'Person', :foreign_key => 'father_id'
has_one :mother, :class_name => 'Person', :foreign_key => 'mother_id'
has_many :children, :class_name => 'Person'
end
Подтверждаю и редактирую этот ответ после работы; )
Это не сработало для меня ... казалось, что это правда, но я получил ожидаемый erorr для отношения has_many: 'нет столбца с именем person_id в таблице people.
Нашел простой ответ в IRC, который, кажется, работает (спасибо Radar):
class Person < ActiveRecord::Base
belongs_to :father, :class_name => 'Person'
belongs_to :mother, :class_name => 'Person'
has_many :children_of_father, :class_name => 'Person', :foreign_key => 'father_id'
has_many :children_of_mother, :class_name => 'Person', :foreign_key => 'mother_id'
def children
children_of_mother + children_of_father
end
end
Если есть какие-либо области по умолчанию, в частности те, которые могут повлиять на порядок результатов, это решение не будет работать, так как результаты не будут упорядочены, как ожидалось.
вы можете получить ActiveRecord:Relation, указав #children, как я сделал в своем ответе ниже
Разве это не вызовет два SQL-запроса? Если вы захотите добавить больше связей, это может стать довольно неэффективным.
ВНИМАНИЕ: с Rails 4.2 (и, вероятно, со всем, что было до этого), вызов children запускает два запроса SQL (один для children_of_father и один для children_of_mother). С большим набором данных это не будет эффективно. @stefvhuynh, спасибо, что подняли вопрос.
Используется named_scopes вместо модели Person сделай это:
class Person < ActiveRecord::Base
def children
Person.with_parent(id)
end
named_scope :with_parent, lambda{ |pid|
{ :conditions=>["father_id = ? or mother_id=?", pid, pid]}
}
end
Я предпочитаю использовать прицелы для решения этой проблемы. Нравится:
class Person < ActiveRecord::Base
belongs_to :father, :class_name => 'Person'
belongs_to :mother, :class_name => 'Person'
has_many :children_of_father, :class_name => 'Person', :foreign_key => 'father_id'
has_many :children_of_mother, :class_name => 'Person', :foreign_key => 'mother_id'
scope :children_for, lambda {|father_id, mother_id| where('father_id = ? AND mother_id = ?', father_id, mother_id) }
end
Этот трюк позволяет легко получить детей без использования экземпляров:
Person.children_for father_id, mother_id
Чтобы улучшить ответ Кензи, вы можете достичь отношения ActiveRecord, определив Person#children как:
def children
children_of_mother.merge(children_of_father)
end
см. этот ответ для более подробной информации.
ПРЕДУПРЕЖДЕНИЕ. Как объясняется в упомянутом вами ответе, отношения объединяются с помощью AND. Что не работает с этим примером, потому что это означает, что вы выбираете только людей, у которых mother_idи (вместо или же) father_id установлен на идентификатор цели. Я не врач, но это не должно случаться так часто :)
.merge работал в моем случае, где мне нужно было AND. Спасибо!
Это не решение общего вопроса, как указано («has_many с несколькими внешними ключами»), но, учитывая, что человек может быть либо матерью, либо отцом, но не обоими сразу, я бы добавил столбец gender и пошел бы с
has_many :children_of_father, :class_name => 'Person', :foreign_key => 'father_id'
has_many :children_of_mother, :class_name => 'Person', :foreign_key => 'mother_id'
def children
gender == "male" ? children_of_father : children_of_mother
end
Я искал ту же функцию, если вы не хотите возвращать массив, а ActiveRecord::AssociationRelation, вы можете использовать << вместо +.
(См. Документацию ActiveRecord)
class Person < ActiveRecord::Base
belongs_to :father, :class_name => 'Person'
belongs_to :mother, :class_name => 'Person'
has_many :children_of_father, :class_name => 'Person', :foreign_key => 'father_id'
has_many :children_of_mother, :class_name => 'Person', :foreign_key => 'mother_id'
def children
children_of_mother << children_of_father
end
end
Мой ответ на Ассоциации и (множественные) внешние ключи в рельсах (3.2): как описать их в модели и написать миграции именно для вас!
Что касается вашего кода, вот мои модификации
class Person < ActiveRecord::Base
belongs_to :father, :class_name => 'Person'
belongs_to :mother, :class_name => 'Person'
has_many :children, ->(person) { unscope(where: :person_id).where("father_id = ? OR mother_id = ?", person.id, person.id) }, class_name: 'Person'
end
Есть вопросы?
Это круто! На вопрос о StackOverflow есть несколько неправильных ответов, но это отлично работает. Однако быстрое исправление: вы неправильно написали mother_id.
@KalleSamuelsson благодарит вас за поддержку! Люблю ваш комментарий!
Для Rails 3, цепочка областей видимости, ActiveRecord :: Relation и в конечном итоге
has_many: stackoverflow.com/questions/17476521/…