Given an array of ActiveRecord objects, can I easily collect their relationships through a method call?

StackOverflow https://stackoverflow.com/questions/220126

Question

Let's say I have the following code:

@sites = Site.find(session[:sites]) # will be an array of Site ids
@languages = Language.for_sites(@sites)

for_sites is a named_scope in the Language model that returns the languages associated with those sites, and languages are associated with sites using has_many through. The goal is for @languages to have a distinct array of the languages associated with the sites.

Instead of calling the Language object on the second line, I'd ideally like to say

@sites.languages

and have the same list returned to me. Is there any way to do that cleanly in Rails 2.1 (or edge)? I know associations and named scopes can extend the array object to have attributes, but unless I'm missing something that doesn't apply here. Any plugins that do this would be welcome, it doesn't have to be in core.

Was it helpful?

Solution

You could extend the array returned by Site.find.

class Site
  def find(*args)
    result = super
    result.extend LanguageAggregator if Array === result
    result
  end
end

module LanguageAggregator
  def languages
    Language.find(:all, :conditions => [ 'id in (?)', self.collect { |site| site.id } ])
  end
end

OTHER TIPS

Why not use named_scopes for both?

class Site
  named_scope :sites, lambda{|ids| :conditions => "id in (#{ids.join(',')})"}
  named_scope :languages, :include => :languages ... (whatever your named scope does)
end

call:

Site.sites(session[:sites]).languages

or, if you want language objects back

Site.sites(session[:sites]).languages.collect{|site| site.languages}.flatten

You can also do it directly on the Language object. I'm using :joins because Rails 2.1 splits up and :include into two queries which means we can't use sites in the :conditions

class Language
  named_scope :for_sites, lambda{|site_ids| :joins => 'inner join sites on languages.site_id = sites.id' :conditions => "sites.id in (#{site_ids.join(',')})"}
end

call:

Language.for_sites(session[:sites])

In both examples I've assumed that session[:sites] is completely controlled by you and not subject to SQL injection. If not, make sure you deal with cleaning up the ID's

Your instance variable @sites is an Array object and not Site so I don't think named_scope can be used. You can open up Array class to achieve this effect though (yikes)

class Array

  def languages
    ...
  end

end

If you added a has_many or has_and_belongs_to_many linking languages to sites, then you could use an include and do something like this:

Site.find( :all, :conditions =>{:id => session[:sites]}, :include => :languages )

You can make a named scope to do the :id => session[:sites] thing, eg:

class Site
  named_scope :for_ids, lambda{ |x| {:conditions => {:id => x }
end

and then do

Site.for_ids(session[:sites]).find(:all, :include => :languages)

Hope this gives you some ideas

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top