Ассоциация полиморфных принадлежит определенному типу
Я относительно новичок в Rails. Я хотел бы добавить ассоциацию к модели, которая использует полиморфную ассоциацию, но возвращает только модели определенного типа, например:
class Note < ActiveRecord::Base
# The true polymorphic association
belongs_to :subject, polymorphic: true
# Same as subject but where subject_type is 'Volunteer'
belongs_to :volunteer, source_association: :subject
# Same as subject but where subject_type is 'Participation'
belongs_to :participation, source_association: :subject
end
Я пробовал огромное количество комбинаций из чтения об ассоциациях на ApiDock, но ничего похожего не делает именно то, что я хочу. Здесь лучшее, что у меня есть до сих пор:
class Note < ActiveRecord::Base
belongs_to :subject, polymorphic: true
belongs_to :volunteer, class_name: "Volunteer", foreign_key: :subject_id, conditions: {notes: {subject_type: "Volunteer"}}
belongs_to :participation, class_name: "Participation", foreign_key: :subject_id, conditions: {notes: {subject_type: "Participation"}}
end
И я хочу пройти этот тест:
describe Note do
context 'on volunteer' do
let!(:volunteer) { create(:volunteer) }
let!(:note) { create(:note, subject: volunteer) }
let!(:unrelated_note) { create(:note) }
it 'narrows note scope to volunteer' do
scoped = Note.scoped
scoped = scoped.joins(:volunteer).where(volunteers: {id: volunteer.id})
expect(scoped.count).to be 1
expect(scoped.first.id).to eq note.id
end
it 'allows access to the volunteer' do
expect(note.volunteer).to eq volunteer
end
it 'does not return participation' do
expect(note.participation).to be_nil
end
end
end
Первый тест проходит, но вы не можете напрямую вызвать отношение:
1) Note on volunteer allows access to the volunteer
Failure/Error: expect(note.reload.volunteer).to eq volunteer
ActiveRecord::StatementInvalid:
PG::Error: ERROR: missing FROM-clause entry for table "notes"
LINE 1: ...."deleted" = 'f' AND "volunteers"."id" = 7798 AND "notes"."s...
^
: SELECT "volunteers".* FROM "volunteers" WHERE "volunteers"."deleted" = 'f' AND "volunteers"."id" = 7798 AND "notes"."subject_type" = 'Volunteer' LIMIT 1
# ./spec/models/note_spec.rb:10:in `block (3 levels) in <top (required)>'
Почему?
Причина, по которой я хочу сделать это, заключается в том, что я создаю область, основанную на анализе строки запроса, включая объединение в различные модели /etc; код, используемый для создания области, значительно сложнее, чем выше - он использует collection.reflections
и т.д. Мое текущее решение работает для этого, но оно оскорбляет меня. Я не могу вызвать отношения непосредственно из экземпляра Note.
Я мог бы решить это, разделив его на две проблемы: напрямую используя области
scope :scoped_by_volunteer_id, lambda { |volunteer_id| where({subject_type: 'Volunteer', subject_id: volunteer_id}) }
scope :scoped_by_participation_id, lambda { |participation_id| where({subject_type: 'Participation', subject_id: participation_id}) }
а затем просто используя getter для note.volunteer
/note.participation
, который просто возвращает note.subject
, если он имеет право subject_type
(в противном случае - nil), но я решил, что в Rails должен быть лучший способ?
Ответы
Ответ 1
Я столкнулся с подобной проблемой. и я, наконец, сгладил лучшее и наиболее надежное решение, используя ассоциацию самоначислений, как показано ниже.
class Note < ActiveRecord::Base
# The true polymorphic association
belongs_to :subject, polymorphic: true
# The trick to solve this problem
has_one :self_ref, :class_name => self, :foreign_key => :id
has_one :volunteer, :through => :self_ref, :source => :subject, :source_type => Volunteer
has_one :participation, :through => :self_ref, :source => :subject, :source_type => Participation
end
Чистый и простой, тестируется только на Rails 4.1, но я думаю, что он должен работать для предыдущих версий.
Ответ 2
Я застрял в этой обратной связи, и в Rails 4.2.1
я, наконец, обнаружил это. Надеюсь, это поможет кому-то, если они используют новую версию Rails. Ваш вопрос был самым близким к тому, что я нашел в связи с тем вопросом, который у меня был.
belongs_to :volunteer, foreign_key: :subject_id, foreign_type: 'Volunteer'
Ответ 3
Я нашел хакерский способ обойти эту проблему. У меня есть аналогичный вариант использования в моем проекте, и я нашел, что это работает. В вашей модели Note вы можете добавлять такие ассоциации:
class Note
belongs_to :volunteer,
->(note) {where('1 = ?', (note.subject_type == 'Volunteer')},
:foreign_key => 'subject_id'
end
Вам нужно будет добавить один из них для каждой модели, к которой вы хотите прикреплять заметки. Чтобы сделать этот процесс DRYer, я бы рекомендовал создать такой модуль:
module Notable
def self.included(other)
Note.belongs_to(other.to_s.underscore.to_sym,
->(note) {where('1 = ?', note.subject_type == other.to_s)},
{:foreign_key => :subject_id})
end
end
Затем включите это в свои модели волонтеров и участников.
[EDIT]
Немного лучше лямбда:
->(note) {(note.subject_type == "Volunteer") ? where('1 = 1') : none}
По какой-то причине замена "where" на "все" не работает. Также обратите внимание, что "none" доступен только в Rails 4.
[MOAR EDIT]
Я не запускаю рельсы 3,2 атм, поэтому я не могу проверить, но я думаю, что вы можете добиться аналогичного результата, используя Proc для условий, например:
belongs_to :volunteer, :foreign_key => :subject_id,
:conditions => Proc.new {['1 = ?', (subject_type == 'Volunteer')]}
Возможно, стоит сделать снимок
Ответ 4
Вы можете сделать так:
belongs_to :volunteer, -> {
where(notes: { subject_type: 'Volunteer' }).includes(:notes)
}, foreign_key: :subject_id
includes
выполните left join
, поэтому у вас есть отношение notes
со всеми volunteer
и participation
. И вы пройдете subject_id
, чтобы найти свою запись.