Question

J'aime en particulier Rails, c’est que je déteste le langage SQL. Je pense que c’est plutôt un langage d’assemblage qui devrait être manipulé avec des outils de niveau supérieur, tels que ActiveRecord. Il semble cependant que je sois arrivé aux limites de cette approche et que je suis hors d’avantage avec SQL.

J'ai un modèle complexe avec beaucoup de sous-enregistrements. J'ai également un ensemble 30-40 named_scopes qui implémentent la logique métier à partir du client. Ces étendues étant chaînées de manière conditionnelle, c'est pourquoi j'ai ces étendues jointures _ afin que les jointures ne soient pas masquées.

J'en ai quelques-uns qui ne fonctionnent pas bien, ou du moins pas ce que le client veut qu'ils fonctionnent. Voici une idée approximative de la structure du modèle, avec quelques portées nommées (pas toutes nécessaires pour l'exemple) qui illustrent mon approche et indiquent mes problèmes. (veuillez pardonner les erreurs de syntaxe)

class Man < ActiveRecord::Base
  has_many :wives

  named_scope :has_wife_named       lambda { |n| { :conditions => { :wives => {:name => n}}}}
  named_scope :has_young_wife_named lambda { |n| { :conditions => { :wives => {:name => n, :age => 0..30}}}}
  named_scope :has_yw_named_v2      lambda { |n| { :conditions => ["wives.name = ? AND wives.age <= 30", n]}}
  named_scope :joins_wives         :joins => :wives

  named_scope :has_red_cat          :conditions => { :cats => {:color => 'red'}}        
  named_scope :has_cat_of_color     lambda { |c| { :conditions => { :cats => {:color => c}}}}
  named_scope :has_7yo_cat          :conditions => { :cats => {:age => 7}}
  named_scope :has_cat_of_age       lambda { |a| { :conditions => { :cats => {:age => a}}}}
  named_scope :has_cat_older_than   lambda { |a| { :conditions => ["cats.age > ?", a] }}
  named_scope :has_cat_younger_than lambda { |a| { :conditions => ["cats.age < ?", a] }}
  named_scope :has_cat_fatter_than  lambda { |w| { :conditions => ["cats.weight > ?", w] } }
  named_scope :joins_wives_cats     :joins => {:wives => :cats}
end

class Wife < ActiveRecord::Base
  belongs_to :man
  has_many :cats
end

class Cat < ActiveRecord::Base
  belongs_to :wife
end
  1. Je peux trouver des hommes dont les femmes ont des chats rouges ET âgés de sept ans

    @men = Man.has_red_cat.has_7yo_cat.joins_wives_cats.scoped({:select => 'DISTINCT men'})
    

    Et je peux même trouver des hommes dont les femmes ont des chats de plus de 20 livres et âgés de plus de 6 ans

    @men = Man.has_cat_fatter_than(20).has_cat_older_than(5).joins_wives_cats.scoped({:select => 'DISTINCT men'})
    

    Mais ce n'est pas ce que je veux. Je veux trouver les hommes dont les épouses ont au moins un chat rouge et un chat de sept ans, qui ne doivent pas nécessairement être le même chat, ou trouver les hommes dont les épouses ont au moins un chat supérieur à un poids donné et un chat de plus d'un âge donné.
    (dans les exemples suivants, supposez la présence des éléments appropriés joint _ et DISTINCT )

  2. Je peux trouver des hommes avec des femmes nommées Esther

    @men = Man.has_wife_named('Esther')
    

    Je peux même trouver des hommes avec des femmes nommés Esther, Ruth OU Ada (douce!)

    @men = Man.has_wife_named(['Esther', 'Ruth', 'Ada'])
    

    mais je veux trouver des hommes avec des femmes nommées Esther AND Ruth AND Ada.

  3. Ha ha, en plaisantant, en fait, j'ai besoin de ça: je peux trouver des hommes avec des femmes de moins de 30 ans nommés Esther

    @men = Man.has_young_wife_named('Esther')
    

    trouvez des hommes avec de jeunes épouses nommés Esther, Ruth ou Ada

    @men = Man.has_young_wife_named(['Esther', 'Ruth', 'Ada'])
    

    mais comme ci-dessus, je veux trouver des hommes avec de jeunes femmes nommés Esther ET Ruth ET Ada. Heureusement, le minimum est fixé dans ce cas, mais il serait bien de spécifier également un âge minimum.

  4. existe-t-il un moyen de tester une inégalité avec une syntaxe de hachage ou devez-vous toujours revenir à : conditions = > [" ;, n] - notez la différence entre has_young_wife_named et has_yw_named_v2 - J'aime mieux le premier, mais la plage ne fonctionne que pour les valeurs finies. Si vous recherchez une vieille femme, vous pouvez utiliser a..100 , mais quand une femme a 101 ans, elle abandonne la recherche. (hmm. peut-elle cuisiner? j / k)

  5. existe-t-il un moyen d'utiliser une portée dans une portée? J'adorerais que : has_red_cat puisse utiliser : has_cat_of_color , ou s'il existait un moyen d'utiliser l'étendue d'un enregistrement enfant dans son parent afin que je puisse mettre les portées liées au chat dans le modèle Wife .

Je ne veux vraiment pas faire cela en SQL pur sans utiliser named_scope , à moins qu'il y ait autre chose de plus sympa - des suggestions de plugins et ce qui n'est pas vraiment apprécié, ou une direction dans le genre de SQL I ' J'aurai besoin d'apprendre. Un ami a suggéré que les syndicats ou les sous-recherches fonctionneraient ici, mais cela ne semble pas être beaucoup discuté dans le contexte de Rails. Je ne connais pas encore les points de vue - seraient-ils utiles? Existe-t-il un moyen de faire plaisir aux rails?

Merci!

Alors que j'allais à St Ives
J'ai rencontré un homme avec sept épouses
Chaque femme avait sept sacs
Chaque sac avait sept chats
Chaque chat avait sept kits
Kits, chats, sacs, épouses
Combien allaient à St Ives?

Était-ce utile?

La solution

Eh bien, j'ai obtenu d'excellents résultats avec named_scope , comme ci-dessous:

named_scope :has_cat_older_than   lambda { |a| { :conditions => ["men.id in ( select man_id from wives where wives.id in ( select wife_id from cats where age > ? ) )", a] } }

et

named_scope :has_young_wife_named lambda { |n| { :conditions => ["men.id in ( select man_id from wives where name = ? and age < 30)", n] } }

Je peux maintenant faire avec succès

Member.has_cat_older_than(6).has_young_wife_named('Miriam').has_young_wife_named('Vashti')

et obtenez ce que j'attends. Ces étendues ne nécessitent pas l'utilisation de jointures et semblent bien fonctionner avec les autres jointures stylisées.

w00t!

Des commentaires ont été demandés sur le point de savoir s'il s'agit d'un moyen efficace de le faire ou s'il existe un moyen plus "rails-y". Un moyen d’inclure une portée d’un autre modèle en tant que fragment de sous-requête SQL pourrait être utile ...

Autres conseils

J'ai utilisé construct_finder_sql pour effectuer la sous-sélection d'un named_scope dans un autre. Ce n'est peut-être pas pour tout le monde, mais son utilisation nous permet de SÉCHER deux ou trois nommés_scopes que nous avons utilisés pour les rapports.

Man.has_cat_older_than(6).send(:construct_finder_sql,{})

Essayez cela dans votre script / console.

Vous avez utilisé la solution la plus native pour Rails. Straight SQL aura les mêmes performances, il n’ya donc aucune raison de l’utiliser.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top