named_scope в рельсах с ассоциацией has_many

Я пытаюсь достичь того, что, как мне кажется, будет довольно сложным запросом, используя магию Rails, не имея большого количества уродливого SQL в коде.

Поскольку моя база данных имеет дело с довольно специализированными биомедицинскими моделями, я переведу следующее в более реальный сценарий.

У меня есть модель книги, которая

has_many :главы

и глава, что

принадлежит_к :книге

Скажем, например, главы имеют атрибут имени, и имена могут быть предисловием, введением и приложением, а книга может иметь главу с именем предисловие и главу с именем введение, но не главу с именем приложение. На самом деле любая комбинация этих

Я ищу все книги, в которых обе главы названы предисловием и введением.

В настоящее время у меня есть named_scope следующим образом

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

Если я позвоню Книге. has_chapter? ["предисловие", "введение"] я найду все книги, в которых есть глава с названием предисловие или введение. Как я мог сделать то же самое, чтобы найти изолировать обе главы, предисловие и введение?

Я не так хорошо знаком с SQL, поэтому не совсем уверен, какое соединение потребуется и можно ли этого достичь в именованной области.

Большое спасибо

Энтони


person Anthony    schedule 06.07.2009    source источник


Ответы (3)


Я ищу все книги, в которых обе главы названы предисловием и введением.

Я использую mysql, а не postgres, но я предполагаю, что postgres поддерживает подзапросы? Вы можете попробовать что-то вроде:

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

а потом:

Book.has_preface.has_introduction
person James Healy    schedule 11.07.2009
comment
Это работает очень хорошо, хотя и немного медленно. Добавление нескольких индексов помогло. - person Anthony; 16.07.2009
comment
Да, PostgreSQL поддерживает подзапросы. Он поддерживается намного раньше, чем MySQL, поэтому он тоже работает. - person KARASZI István; 18.02.2011

Мне нравится сокращать код до

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

и это действительно работает для соединения с «ИЛИ». Однако соединение И не будет работать, поскольку это означает, что главы.название в одной строке соединения должно быть, например, как «предисловие», так и «введение».

Еще более аккуратный способ сделать мое первоначальное соединение "ИЛИ" - это

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

Спасибо форуму Duplex on Rails (ссылка на сообщение). проблема, хотя DUPLEX предложил это

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

Это может работать в некоторых вариантах SQL, но не в PostgreSQL. Мне пришлось использовать следующее

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

Код

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

требуется, потому что в PostgreSQL вы не можете выбрать все столбцы с книгами.*, а затем СГРУППИРОВАТЬ ПО книгам.* каждый столбец должен быть назван отдельно :(

person Anthony    schedule 07.07.2009
comment
Лучше использовать более аккуратный способ: :conditions=›{:chapters=›{:name=›названия_глав}}, но мне также нравится :conditions=›[названия_глав_глав В (?),названия_глав] - person MiniQuark; 01.07.2010

Я ищу все книги, в которых обе главы названы предисловием и введением.

В настоящее время у меня есть named_scope следующим образом

Вы можете сделать это без 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
person rnicholson    schedule 06.07.2009
comment
Кажется, это работает хорошо. Единственная проблема в том, что я бы предпочел named_scope, потому что тогда я могу связать его вместе с другими областями. буду продолжать расследование - person Anthony; 07.07.2009