关联特定类型的多态belongs_to

我对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 ' 

为什么?

我想这样做的原因是因为我正在构建一个基于解析查询字符串的范围,包括加入各种模型/等; 用于构造范围的代码比上面的代码复杂得多 – 它使用了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}) } 

然后只使用note.volunteer / note.participation的getter,只要它返回note.subject如果它有正确的subject_type (否则为nil),但我想在Rails中必须有更好的方法吗?

我碰到了类似的问题。 我终于通过使用下面的自引用关联来解决最好和最强大的解决方案。

 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上测试过,但我想它应该适用于以前的版本。

我坚持这种反向关联,在Rails 4.2.1我终于发现了这一点。 希望这可以帮助某人,如果他们使用更新版本的Rails。 你的问题与我发现的问题最接近。

 belongs_to :volunteer, foreign_key: :subject_id, foreign_type: 'Volunteer' 

我找到了解决这个问题的一种破解方式。 我在我的一个项目中有一个类似的用例,我发现这个工作。 在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 

然后将其包含在您的志愿者和参与模型中。

[编辑]

一个略好的lambda将是:

  ->(note) {(note.subject_type == "Volunteer") ? where('1 = 1') : none} 

出于某种原因,将’where’替换为’all’似乎不起作用。 另请注意,’none’仅适用于Rails 4。

[MOAR编辑]

我没有运行rails 3.2 atm所以我无法测试,但我认为你可以通过使用Proc来获得类似的结果,例如:

 belongs_to :volunteer, :foreign_key => :subject_id, :conditions => Proc.new {['1 = ?', (subject_type == 'Volunteer')]} 

可能值得一试

你可以这样做:

 belongs_to :volunteer, -> { where(notes: { subject_type: 'Volunteer' }).includes(:notes) }, foreign_key: :subject_id 

includesleft join所以你有所有volunteerparticipationnotes关系。 然后你通过subject_id找到你的记录。