Étant donné un tableau d'objets ActiveRecord, puis-je facilement collecter leurs relations via un appel de méthode?
-
03-07-2019 - |
Question
Disons que j'ai le code suivant:
@sites = Site.find(session[:sites]) # will be an array of Site ids
@languages = Language.for_sites(@sites)
for_sites est un named_scope dans le modèle de langage qui renvoie les langues associées à ces sites, et les langues sont associées aux sites à l'aide de has_many. L’objectif est que @languages ??ait un tableau distinct des langues associées aux sites.
Au lieu d'appeler l'objet Language sur la deuxième ligne, j'aimerais idéalement dire
@sites.languages
et que la même liste me soit renvoyée. Existe-t-il un moyen de le faire proprement dans Rails 2.1 (ou edge)? Je sais que les associations et les portées nommées peuvent étendre l’objet tableau à des attributs, mais à moins que je manque quelque chose qui ne s’applique pas ici. Tous les plugins faisant cela seraient les bienvenus, il n’a pas besoin d’être dans le noyau.
La solution
Vous pouvez étendre le tableau renvoyé par 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
Autres conseils
Pourquoi ne pas utiliser named_scopes pour les deux?
class Site
named_scope :sites, lambda{|ids| :conditions => "id in (#{ids.join(',')})"}
named_scope :languages, :include => :languages ... (whatever your named scope does)
end
appeler:
Site.sites(session[:sites]).languages
ou, si vous souhaitez récupérer des objets langage
Site.sites(session[:sites]).languages.collect{|site| site.languages}.flatten
Vous pouvez également le faire directement sur l'objet Langage. J'utilise: join car Rails 2.1 est scindé en deux requêtes, ce qui signifie que nous ne pouvons pas utiliser de sites dans les: 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
appeler:
Language.for_sites(session[:sites])
Dans les deux exemples, j'ai supposé que la session [: sites] est entièrement contrôlée par vous et n'est pas sujette à une injection SQL. Sinon, assurez-vous de nettoyer les identifiants
Votre variable d'instance @sites est un objet Array et non un site. Je ne pense donc pas que named_scope puisse être utilisé. Vous pouvez cependant ouvrir la classe Array pour obtenir cet effet (bravo)
class Array
def languages
...
end
end
Si vous avez ajouté un has_many
ou un has_and_belongs_to_many
reliant des langues à des sites, vous pouvez utiliser une inclusion et procéder de la manière suivante:
Site.find( :all, :conditions =>{:id => session[:sites]}, :include => :languages )
Vous pouvez créer une portée nommée pour: id = > session [: sites] chose, par exemple:
class Site
named_scope :for_ids, lambda{ |x| {:conditions => {:id => x }
end
et ensuite
Site.for_ids(session[:sites]).find(:all, :include => :languages)
J'espère que cela vous donne quelques idées