Ruby on Rails 3:结合多个has_many或has_many_through关联的结果

Jen*_*ens 6 polymorphism ruby-on-rails associations has-many-polymorphs

我有以下型号.用户有UserActions,一个可能的UserAction可以是ContactAction(UserAction是一个多态).还有LoginAction等其他动作.所以

 class User < AR::Base
  has_many :contact_requests, :class_name => "ContactAction"
  has_many :user_actions
  has_many_polymorphs :user_actionables, :from => [:contact_actions, ...], :through => :user_actions
 end

class UserAction < AR::Base
 belongs_to :user
 belongs_to :user_actionable, :polymorphic => true
end

class ContactAction < AR::Base
 belongs_to :user
 named_scope :pending, ...
 named_scope :active, ...
end

这个想法是一个ContactAction加入两个用户(在应用程序中有其他后果),并始终有一个接收端和一个发送端.同时,ContactAction可以具有不同的状态,例如过期,待定等.

我可以说@user.contact_actions.pending@user.contact_requests.expired列出用户发送或接收的所有待处理/过期请求.这很好用.

我现在想要的是一种加入两种类型的ContactAction的方法.即@user.contact_actions_or_requests.我尝试了以下方法:

class User

 def contact_actions_or_requests
  self.contact_actions + self.contact_requests
 end

 # or
 has_many :contact_actions_or_requests, :finder_sql => ..., :counter_sql => ...

end

但是所有这些都存在这样的问题:在关联之上不可能使用额外的finder或named_scopes,例如@user.contact_actions_or_requests.find(...)@user.contact_actions_or_requests.expired.

基本上,我需要一种表达1:n关联的方法,它有两条不同的路径.一个是User -> ContactAction.user_id,另一个是User -> UserAction.user_id -> UserAction.user_actionable_id -> ContactAction.id.然后将结果(ContactActions)连接到一个列表中,以便使用named_scopes和/或finders进行进一步处理.

由于我需要在几十个地方进行这种关联,因此为每种情况编写(并维护!)自定义SQL将是一个很大的麻烦.

我更愿意在Rails中解决这个问题,但我也对其他建议(例如PostgreSQL 8.3程序或类似的东西)持开放态度.重要的是,最后,我可以像使用其他任何关联一样使用Rails的便利功能,更重要的是,还可以嵌套它们.

任何想法都将非常感激.

谢谢!


为我自己的问题提供一种答案:

我可能会使用数据库视图解决这个问题,并根据需要添加适当的关联.对于上述,我可以

  • 使用finder_sql中的SQL创建视图,
  • 将其命名为"contact_actions_or_requests",
  • 修改SELECT子句以添加user_id列,
  • 添加app/models/ContactActionsOrRequests.rb,
  • 然后将"has_many:contact_actions_or_requests"添加到user.rb.

我不知道我将如何处理更新记录 - 这似乎不可能有一个视图 - 但也许这是第一次开始.

Jon*_*son 2

您正在寻找的方法是合并。如果您有两个 ActiveRecord::Relations,r1 和 r2,则可以调用 r1.merge(r2) 来获取将两者组合在一起的新 ActiveRecord::Relation 对象。

这是否适合您很大程度上取决于您的范围如何设置以及您是否可以更改它们以产生有意义的结果。让我们看几个例子:

假设您有一个 Page 模型。它具有正常的created_at和updated_at属性,因此我们可以具有如下范围: :updated -> { where('created_at != Updated_at') } :not_updated -> { where('created_at = Updated_at') }

如果您将其从数据库中取出,您将得到:

r1 = Page.updated # SELECT `pages`.* FROM `pages` WHERE (created_at != updated_at)
r2 = Page.not_updated # SELECT `pages`.* FROM `pages` WHERE (created_at = updated_at)
r1.merge(r2) # SELECT `pages`.* FROM `pages` WHERE (created_at != updated_at) AND (created_at = updated_at)
=> []
Run Code Online (Sandbox Code Playgroud)

所以它确实结合了这两种关系,但不是以一种有意义的方式。另一个:

r1 = Page.where( :name => "Test1" ) # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test1'
r2 = Page.where( :name => "Test2" ) # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test2'
r1.merge(r2) # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test2'
Run Code Online (Sandbox Code Playgroud)

因此,它可能适合您,但也可能不适合您,具体取决于您的情况。

另一种推荐的方法是在模型上创建一个新范围:

class ContactAction < AR::Base
  belongs_to :user
  scope :pending, ...
  scope :active, ...
  scope :actions_and_requests, pending.active # Combine existing logic
  scope :actions_and_requests, -> { ... } # Or, write a new scope with custom logic
end
Run Code Online (Sandbox Code Playgroud)

这结合了您想要在一个查询中收集的不同特征......