Rails has_many :through 在关联模型中查找额外属性

72

我对Ruby和Rails都不熟悉,但我现在已经通过书本学习了这两个东西(显然这意味着什么都不算,哈哈)。

我有两个模型,Event和User,它们是通过一个名为EventUser的表进行关联的。

class User < ActiveRecord::Base
  has_many :event_users
  has_many :events, :through => :event_users
end

class EventUser < ActiveRecord::Base
  belongs_to :event
  belongs_to :user

  #For clarity's sake, EventUser also has a boolean column "active", among others
end

class Event < ActiveRecord::Base
  has_many :event_users
  has_many :users, :through => :event_users
end

这个项目是一个日历,在其中我需要跟踪人们报名和取消报名某个事件。我认为多对多是一个不错的方法,但我不能做出这样的事情:

u = User.find :first
active_events = u.events.find_by_active(true)

因为事件本身并没有那些额外的数据,这些额外的数据在EventUser模型中。尽管我可以这样做:

u = User.find :first
active_events = []
u.event_users.find_by_active(true).do |eu|
  active_events << eu.event
end

这似乎与“Rails之道”相矛盾。 有人能给我指点一下吗?这件事困扰了我很长时间,今晚(今天早上)。


1
你也可以使用Gareth的建议创建一个命名作用域。 - arjun
4个回答

124

你可以在你的用户模型中添加类似以下代码:

has_many  :active_events, :through => :event_users, 
          :class_name => "Event", 
          :source => :event, 
          :conditions => ['event_users.active = ?',true]

之后,您只需调用以下代码即可获取用户的活动事件:

User.first.active_events

1
我已经标记了你,但如果明天早上我没有得到更好的答案,那就归你了。 - Stefan Mai
请看下面我的答案,其中包含了一种更为现代的方法。 - msanteler
10
谢谢,Rails 4的意思是: -> { where event_users: { active: true } } (查询时)筛选出 event_users 中 active 字段为 true 的记录。 - Ivan Black
但是没有机会通过 event_users 范围来定义条件吗?就像 scope :active, where(:active => true) 这样的形式?(使用 Rails 3.2) - Augustin Riedinger
@IvanBlack,你能解释一下这个在has_many through:中的位置吗?你能包含整个Rails 4特定的等效代码吗? - Damien Roche
@DamienRoche,使用->{where...}代替:conditions完整版本 - Ivan Black

23

Milan Novota有一个不错的解决方案 - 但是:conditions现在已经被弃用了,而且:conditions => ['event_users.active = ?',true]也似乎不太符合Rails的风格。我更喜欢像这样的写法:

has_many :event_users
has_many :active_event_users, -> { where active: true }, class_name: 'EventUser'
has_many :active_events, :through => :active_event_users, class_name: 'Event', :source => :event

此后,您仍应该能够通过调用以下内容来获取用户的活动事件:

User.first.active_events

1
如果你还没有使用Rails 4,你仍然可以按照这个优秀的例子进行操作,但是你可能需要使用已弃用的:conditions => {active: true}。对我很有帮助,谢谢! - monozok

12

即使您的事件没有明确调用user_events表,该表仍然会因为必要的联接在SQL中隐式包含。因此,您仍然可以在查找条件中使用该表:

u.events.find(:all, :conditions => ["user_events.active = ?", true])

当然,如果你计划经常进行这种查找操作,那么按照Milan Novota的建议,可以将它作为一个单独的关联对象处理,但并不要求必须这样做。


我喜欢在模型中定义所有的作用域,即使我只使用它们一次。这样可以保持我的控制器简洁,并使整体设计更加一致。 - Milan Novota

9

其实User模型中放置了比实际需要更多的责任,而且没有很好的理由这样做。

我们可以首先在EventUser模型中定义它所属的范围,如下:

class EventUser < ActiveRecord::Base
  belongs_to :event
  belongs_to :user

  scope :active,   -> { where(active: true)  }
  scope :inactive, -> { where(active: false) } 
end

现在,用户可能有两种类型的事件:活动事件和非活动事件,因此我们可以在User模型中定义如下关系:
class User < ActiveRecord::Base
  has_many :active_event_users,   -> { active },   class_name: "EventUser"
  has_many :inactive_event_users, -> { inactive }, class_name: "EventUser"

  has_many :inactive_events, through: :inactive_event_user,
                             class_name: "Event",
                             source: :event
  has_many :active_events,   through: :active_event_users,
                             class_name: "Event",
                             source: :event
end

这种技术的优点在于,活动或非活动事件的功能属于“EventUser”模型,如果将来需要修改功能,只需在一个地方修改:“EventUser”模型,所有其他模型都会反映出这些变化。

1
你的解决方案让我感到非常开心 :) - Bilal Maqsood

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接