2009-07-06 7 views
1

J'essaie de réaliser ce que je pense sera une requête assez complexe en utilisant la magie de Rails sans avoir beaucoup de SQL laide dans le code.named_scope dans les rails avec une association has_many

Puisque ma base de données traite de modèles biomédicaux plutôt spécialisés, je vais traduire ce qui suit en un scénario plus réaliste.

J'ai un livre de modèle qui

has_many: chapitres

et chapitre qui

belongs_to: livre

Say pour les chapitres d'instance ont un attribut de nom et les noms peuvent être préface, introduction et annexe, et un livre pourrait avoir un chapitre nommé préface et un chapitre nommé introduction mais pas de chapitre nommé annexe. En fait n'importe quelle combinaison de ces

Je cherche à trouver tous les livres qui ont les deux chapitres nommés préface et introduction.

À l'heure actuelle j'ai un named_scope comme suit

Book 
    named_scope :has_chapter?, lambda { |chapter_names| 
    condition_string_array = [] 
    chapter_names.size.times{condition_string_array << "chapters.name = ?"} 
    condition_string = condition_string_array.join(" OR ") 
    {:joins => [:chapters] , :conditions => [condition_string, * chapter_names]} 
    } 

Si j'appelle livre. has_chapter? ["préface", "introduction"] cela me trouvera tous les livres qui ont soit un chapitre intitulé préface ou introduction. Comment pourrais-je faire une chose similaire qui me trouverait isoler que les deux chapitres préface et introduction? Je ne suis pas très familier avec SQL donc je ne suis pas sûr du type de jointure qui serait nécessaire et si cela pourrait être réalisé dans une portée nommée.

Un grand merci

Anthony

Répondre

8

Je cherche à trouver tous les livres qui ont les deux chapitres nommés avant-propos et l'introduction .

J'utilise mysql, pas postgres, mais je suppose que postgres supporte les sous-requêtes? Vous pouvez essayer quelque chose comme:

class Book 

    named_scope :has_preface, :conditions => "books.id IN (SELECT book_id FROM chapters WHERE chapters.name = 'preface')" 
    named_scope :has_introduction, :conditions => "books.id IN (SELECT book_id FROM chapters WHERE chapters.name = 'introduction')" 

end 

puis:

Book.has_preface.has_introduction 
+0

Cela fonctionne très bien, si un peu lentement. Ajout de quelques indices aidés. – Anthony

+0

+1 exactement ce que je cherchais, merci! – MiniQuark

+0

Oui, PostgreSQL supporte les sous-requêtes. Il est supporté bien avant MySQL, donc ça marche aussi. –

1

Je cherche à trouver tous les livres qui ont les deux chapitres nommés avant-propos et l'introduction .

À l'heure actuelle j'ai un named_scope comme suit

Vous pouvez le faire sans named_scope.

class Book < ActiveRecord::Base 
    has_many :chapters 

    def self.has_chapter?(chapter_names) 
    Chapter.find_all_by_name(chapter_names, :include => [:book]).map(&:book) 
    end 
end 
+0

Cela semble bien fonctionner. Le seul problème est que je préfère un named_scope parce que je peux le chaîner avec d'autres portées. Je vais continuer à enquêter – Anthony

2

J'aime la condensation de code à

condition_string = Array.new(chapter_names.size, "chapters.name=?").join(" AND ") 

et cela ne fonctionne en effet pour une jointure avec « OR » .Cependant la ENTREZ ne fonctionnera pas car cela signifie que dans un chapters.name une seule ligne d'une jointure doit être à la fois 'préface' et 'introduction' par exemple.

Une même façon plus propre de faire mon original "ou d '" est

named_scope :has_chapter?, lambda { | chapter_names | 
    {:joins => [: chapters] , :conditions => { :chapters => {:name => chapter_names}}} 
} 

Merci à Duplex on Rails Forum (Post link) Pour atteindre mon problème d'origine si DUPLEX a suggéré que ce

named_scope :has_chapters, lambda { |chapter_names| 
    {:joins => :chapters , :conditions => { :chapters => {:name => chapter_names}}, 
    :select => "books.*, COUNT(chapters.id) AS c_count", 
    :group => "books.id", :having => "c_count = #{chapter_names.is_a?(Array) ? chapter_names.size : 1}" } 
} 

Cela peut fonctionner dans certaines versions de SQL, mais pas dans PostgreSQL. Je devais utiliser les éléments suivants

named_scope : has_chapter?, lambda { | chapter_names | 
    {:joins => :chapters , :conditions => { :chapters => {:name => chapter_names}}, 
    :select => "books.* , COUNT(chapters.id)", 
    :group => Book.column_names.collect{|column_name| "books.#{column_name}"}.join(","), :having => "COUNT(chapters.id) = #{chapter_names.is_a?(Array) ? chapter_names.size : 1}" } 
} 

Le code

Book.column_names.collect{|column_name| "books.#{column_name}"}.join(",") 

est nécessaire parce que PostgreSQL vous ne pouvez pas sélectionner toutes les colonnes avec des livres. * Et GROUP BY livres. * Chaque colonne doit être nommé individuellement :(

+0

La manière la plus propre est la meilleure:: conditions => {: chapitres => {: nom => chapitres_names}}, mais j'aime aussi: conditions => ["chapters.chapter_names IN (?)" , chapter_names] – MiniQuark

Questions connexes