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

发布于 2024-09-30 02:25:01 字数 2125 浏览 1 评论 0原文

我有以下型号。用户拥有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

但所有这些都存在问题,即无法在关联之上使用其他查找器或named_scopes,例如 @user.contact_actions_or_requests.find(...) 或 <代码>@user.contact_actions_or_requests.expired。

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

由于我在几十个地方都需要这种关联,因此为每种情况编写(和维护!)自定义 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”到用户.rb。

我还不知道如何处理更新记录 - 这对于视图来说似乎是不可能的 - 但也许这是第一次开始。

I have the following models. Users have UserActions, and one possible UserAction can be a ContactAction (UserAction is a polymorphism). There are other actions like LoginAction etc. So

 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

The idea is that a ContactAction joins two users (with other consequences within the app) and always has a receiving and a sending end. At the same time, a ContactAction can have different states, e.g. expired, pending, etc.

I can say @user.contact_actions.pending or @user.contact_requests.expired to list all pending / expired requests a user has sent or received. This works fine.

What I would now like is a way to join both types of ContactAction. I.e. @user.contact_actions_or_requests. I tried the following:

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

but all of these have the problem that it is not possible to use additional finders or named_scopes on top of the association, e.g. @user.contact_actions_or_requests.find(...) or @user.contact_actions_or_requests.expired.

Basically, I need a way to express a 1:n association which has two different paths. One is User -> ContactAction.user_id, the other is User -> UserAction.user_id -> UserAction.user_actionable_id -> ContactAction.id. And then join the results (ContactActions) in one single list for further processing with named_scopes and/or finders.

Since I need this association in literally dozens of places, it would be a major hassle to write (and maintain!) custom SQL for every case.

I would prefer to solve this in Rails, but I am also open to other suggestions (e.g. a PostgreSQL 8.3 procedure or something simliar). The important thing is that in the end, I can use Rails's convenience functions like with any other association, and more importantly, also nest them.

Any ideas would be very much appreciated.

Thank you!


To provide a sort-of answer to my own question:

I will probably solve this using a database view and add appropriate associations as needed. For the above, I can

  • use the SQL in finder_sql to create the view,
  • name it "contact_actions_or_requests",
  • modify the SELECT clause to add a user_id column,
  • add a app/models/ContactActionsOrRequests.rb,
  • and then add "has_many :contact_actions_or_requests" to user.rb.

I don't know how I'll handle updating records yet - this seems not to be possible with a view - but maybe this is a first start.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

夜深人未静 2024-10-07 02:25:01

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

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

假设您有一个 Page 模型。它具有正常的created_at和updated_at属性,因此我们可以拥有如下范围:
:更新-> { where('created_at != Updated_at') }
:未更新-> { 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)
=> []

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

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'

所以,它可能适合你,但也可能不适合你,这取决于你的情况。

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

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

它将您想要在一个查询中收集的不同特征结合起来......

The method you are looking for is merge. If you have two ActiveRecord::Relations, r1 and r2, you can call r1.merge(r2) to get a new ActiveRecord::Relation object that combines the two.

If this will work for you depends largely on how your scopes are set up and if you can change them to produce a meaningful result. Let's look at a few examples:

Suppose you have a Page model. It has the normal created_at and updated_at attributes, so we could have scopes like:
:updated -> { where('created_at != updated_at') }
:not_updated -> { where('created_at = updated_at') }

If you pull this out of the database you'll get:

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)
=> []

So it did combine the two relations, but not in a meaningful way. Another one:

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'

So, it might work for you, but maybe not, depending on your situation.

Another, and recommended, way of doing this is to create a new scope on you model:

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

That combines the different traits you want to collect in one query ...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文