简体   繁体   中英

named_scope in rails with a has_many association

I am trying to achieve what I think will be a fairly complex query using the magic of Rails without having lots of ugly looking SQL in the code.

Since my database is dealing with rather specialised biomedical models I'll translate the following to a more real world scenario.

I have a model Book that

has_many :chapters

and Chapter that

belongs_to :book

Say for instance Chapters have a name attribute and the names can be preface ,introduction and appendix, and a Book could have a Chapter named preface and a Chapter named introduction but no Chapter named appendix. In fact any combination of these

I am looking to find all Books that have both Chapters named preface and introduction.

At present I have a named_scope as follows

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]}
  }

If I call Book. has_chapter? ["preface", "introduction"] this will find me all books that have either a Chapter named preface or introduction. How could I do a similar thing that would find me isolates that both Chapters preface and introduction?

I am not that familiar with SQL so am not quite sure what kind of join would be needed and whether this could be achieved in a named scope.

Many thanks

Anthony

I am looking to find all Books that have both Chapters named preface and introduction.

I use mysql, not postgres, but I assume postgres has support for sub-queries? You could try something like:

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

and then:

Book.has_preface.has_introduction

I like the condensing of code to

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

and this does indeed work for a join with "OR".However the AND join will not work since this means that chapters.name in a single row of a join has to be both 'preface' and 'introduction' for example.

An even neater way of doing my original "OR" join is

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

Thanks to Duplex on Rails Forum ( Post link ) In order to achieve my original problem though DUPLEX suggested this

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}" }
}

This may work in some flavour of SQL but not in PostgreSQL. I had to use the following

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}" }
}

The code

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

is required because in PostgreSQL you can't select all columns with books.* and then GROUP BY books.* each column has to be named individually :(

I am looking to find all Books that have both Chapters named preface and introduction.

At present I have a named_scope as follows

You can do this without a 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM