Ruby on Rails 3: Combine the results of multiple has_many or has_many_through associations

I have the following models. Users have UserActions, and one of the possible UserActions can be ContactAction (UserAction is 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 ContactAction combines two users (with other consequences in the application) and always has a reception and sending. At the same time, ContactAction can have different states, for example. expired, pending, etc.

I can say @user.contact_actions.pending or @user.contact_requests.expired to list all pending / expired requests sent or received by the user. It works great.

Now I would like to combine both types of ContactAction. That is @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 they all have a problem that it is not possible to use additional crawlers or named_scopes over the association, for example. @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 of them is User -> ContactAction.user_id , the other is User -> UserAction.user_id -> UserAction.user_actionable_id -> ContactAction.id . And then add the results (ContactActions) to a single list for further processing using named_scopes and / or finders.

Since I need this association in literally dozens of places, it would be a big problem to write (and support!) My own SQL for each case.

I would prefer to solve this problem in Rails, but I am also open to other suggestions (e.g. PostgreSQL 8.3 procedure or something similar). The important thing is that in the end, I can use the convenient functions of Rails, such as any other association, and, more importantly, also nest them.

Any ideas would be greatly appreciated.

Thanks!


To give a kind of answer to my own question:

I will probably resolve this using the database view, and add the appropriate associations if necessary. For the above, I can

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

I don’t know how I will handle record updates - this seems impossible with the view - but maybe this is the first run.

+6
polymorphism ruby-on-rails associations has-many-polymorphs
source share
1 answer

The method you are looking for is merging. 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 works for you, it depends a lot on how your areas are set up and if you can change them to get meaningful results. Let's look at a few examples:

Suppose you have a page model. It has the usual attributes created_at and updated_at, so we can have areas such as:: updated β†’ {where ('created_at! = Updated_at')}: not_updated β†’ {where ('created_at = updated_at')}

If you pull this out of the database, you will 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) => [] 

Thus, he combined these two relationships, but not in a meaningful way. One more:

 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, this may work for you, but maybe not, depending on your situation.

Another and recommended way to do this is to create a new area on your 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 

This combines the different features that you want to put together in a single query ...

+2
source share

All Articles