作用域与:has_many:through关联的连接


71
class Users < ActiveRecord::Base
  has_many :meetings, :through => :meeting_participations
  has_many :meeting_participations
end

class Meetings < ActiveRecord::Base
  has_many :users, :through => :meeting_participations
  has_many :meeting_participations
end

class MeetingParticipations < ActiveRecord::Base
  belongs_to :user
  belongs_to :meeting

  scope :hidden, where(:hidden => true)
  scope :visible, where(:hidden => false)
end

hidden是m2m关联表中的一个额外的布尔列。鉴于某些Users情况current_user,我想做

current_user.meetings.visible

它将检索Meetingshidden列所在的用户是其参与者的集合false。我得到的最接近的是将以下范围添加到Meetings类中

scope :visible, joins(:meeting_participations) & MeetingParticipation.visible

scope不过滤MeetingsMeetingParticipations表,但有没有加入/条件对MeetingParticipations相关表格current_user

与此问题是,如果current_useranother_user是两个参与者对于一些Meetings例如,Meetings结果集中的记录将针对已每个参与者返回hidden设置false。如果current_usertrue设置hidden为所有Meetings,如果another_user在任何一个具有相同的会议的参与者hidden设置为false,那些Meetings将出现在Meetings.visible结果集。

是否可以有一个如上所述的作用域,该作用域可以正确地加入User实例?如果没有,有人可以推荐解决方案吗?

Answers:


97

这是我为您解决的问题的解决方案:

class User < ActiveRecord::Base
  has_many :meeting_participations
  has_many :meetings, :through => :meeting_participations do
   def visible
     where("meeting_participations.visible = ?", true)
   end
  end
end

@user.meetings.visible


谢谢您,绝对是简单的答案。
deefour 2012年

2
任何人都建议在这种情况下进行一些急切的加载?例如,如果会议属于某个场地,而我想急于加载这些场地?
Mark Fraser

所以我想这确实可行:include([:assoc1,:assoc2])。where .... geez我想确定在评论之前我已经尝试过了。
马克·弗雷泽

@andrei是的,这就是我想要的。。谢谢+1
Arup Rakshit 2015年

10
知道您可以将一个块应用于关联定义。零想法。我已经和Rails合作了7年了。
约书亚·品特

69

在Rails 4中,您可以在关联本身中指定最初在子对象中定义的范围。简短:您不必了解User模型中MeetingParticipation模型的内部。

class User < ActiveRecord::Base
  has_many :meeting_participations
  has_many :meetings, :through => :meeting_participations
  has_many :visible_participations, -> { visible }, :class_name => 'MeetingParticipation'
  has_many :visible_meetings, :source => :meeting, :through => :visible_participations
end

class Meeting < ActiveRecord::Base
  has_many :meeting_participations
  has_many :users, :through => :meeting_participations
end

class MeetingParticipation < ActiveRecord::Base
  belongs_to :user
  belongs_to :meeting

  scope :hidden, -> { where(:hidden => true) }
  scope :visible, -> { where(:hidden => false) }
end

这将允许您执行以下操作:user1.visible_meetings并且user2.visible_meetings使用不同的结果集


7
我喜欢此解决方案,但要使其正常工作,我还需要将:source添加到:visible_meetings has_many:visible_meetings,class_name:'Meeting',通过::visible_participations,来源::meeting
Darren Hicks

是否需要将'-> {visible}'更改为符号'-> {:visible}'还是上面的代码片段按原样工作?
chaostheory 2014年

2
@chaostheory很抱歉收到较晚的答复,但按原样,它在此情况下不是符号。
Paul Pettengill 2015年

2
就像@DarrenHicks评论的一样,我必须添加:source,但是随后我可以省略:class_name
Raf

这太棒了。有什么办法在Rails 3中做类似的事情吗?
史蒂夫2015年

7
current_user.meetings.merge(MeetingParticipations.visible)

7

干净的关联方法是:

has_many :visible_meetings, -> { merge(MeetingParticipations.visible) },
  :source => :meeting, :through => :meeting_participations

笼统地说:如果您有链式has_many关联,则可以将中间范围(through通过合并)关联。可能需要Rails 4+。

否则,必须通过创建(可能是不需要的)中间作用域关联来完成,如@Paul Pettengill的回答所示。


3
我认为最好的答案
Michael Kosyk,

4

这是一个班轮:

Meeting.joins(:meeting_participations).where(meeting_participation: { hidden: false, user_id: current_user.id })

这很棒,因为您可以使用它来创建作用域,使用它来创建函数,或者直接在任何地方调用它。您还可以将任何其他限制添加到哈希中。


迄今为止最好的答案。使用这种语法,您可以像接受的答案一样添加关联扩展,也可以将范围放在整个类上,并使用传递到范围的参数过滤关联的对象。此外,与利用合并功能的任何答案相比,该连接将大大提高结果的性能。
杰斐逊·哈德森

1

我知道这个问题已在不久前得到解答,但我刚刚遇到了类似的问题,并且一直在寻找解决此问题的最佳方法。可以接受的解决方案非常简单,但我认为将关联范围从Users移到Meeting如下应该会更干净

class Users < ActiveRecord::Base
  has_many :meetings, :through => :meeting_participations
  has_many :meeting_participations
end

class Meetings < ActiveRecord::Base
  has_many :users, :through => :meeting_participations
  has_many :meeting_participations
  scope :hidden, -> { where('meeting_participations.hidden = ?', true) }
  scope :visible, -> { where('meeting_participations.hidden = ?', false) }
end

class MeetingParticipations < ActiveRecord::Base
  belongs_to :user
  belongs_to :meeting

  scope :hidden, where(:hidden => true)
  scope :visible, where(:hidden => false)
end

有了这个,你可以打电话 current_user.meetings.hidden

现在,根据设计,会议决定了隐藏/可见的原因。


这个,我非常喜欢。允许范围由目标对象确定是正确的方法。我想我今天才读到某处(在我正在学习Rails的同时,有很多阅读),在关联上添加方法并不是实现此目的的好方法。这与扩展现有类有关,但不使用元类,而是使用模块。您介意解释MeetingParticipations的作用域如何工作并传递给孩子的父作用域吗?
frostymarvelous

2
@frostymarvelous我的建议是,即使删除MeetingParticipations上的范围,也可以将用户接受的答案中的范围移动到Meeting。MeetingParticipations表具有一个隐藏列。我的解决方案只是对我认为更好的设计进行重组。我个人将MeetingParticipations的作用域保留下来,以防万一您需要隐藏/可见,而此时则不需要where子句。
Abass Sesay

-3

您也可以这样做:

current_user.meeting_participations.visible.map(&:meeting)

-4

在我看来,为您的目的使用Meeting上的范围是不明智的。会议本身不具有可见性,但参与性却具有可见性。因此,我建议对User中的关联进行扩展:

class User < ActiveRecord::Base
  has_many :meetings, :through => :meeting_participations do
    def visible
      ids = MeetingParticipation.
        select(:meeting_id).
        where(:user_id => proxy_owner.id, :visible => true).
        map{|p| p.meeting_id}
      proxy_target.where("id IN (?)", ids)
    end
  end
  ...
end

我希望这有帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.