【问题标题】:named_scope in rails with a has_many association具有 has_many 关联的 rails 中的 named_scope
【发布时间】:2009-07-06 17:07:55
【问题描述】:

我正在尝试使用 Rails 的魔力来实现我认为相当复杂的查询,而代码中不会出现很多丑陋的 SQL。

由于我的数据库正在处理相当专业的生物医学模型,因此我将以下内容转换为更真实的场景。

我有一本样书

has_many:章节

和那一章

belongs_to :book

例如,Chapters 有一个 name 属性,名称可以是 preface,introduction 和 appendix,而 Book 可以有一个名为 preface 的 Chapter 和一个名为 Introduction 但没有名为 appendix 的章节。实际上这些的任意组合

我正在寻找所有同时包含名为序言和介绍的章节的书籍。

目前我有一个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]}
  }

如果我打电话给书。有章? ["preface", "introduction"] 这将为我找到所有具有名为序言或介绍的章节的书籍。我怎么能做类似的事情来发现我将两个章节序言和介绍隔离开来?

我对 SQL 不是很熟悉,所以不太确定需要什么样的连接,以及这是否可以在命名范围内实现。

非常感谢

安东尼

【问题讨论】:

    标签: ruby-on-rails activerecord associations named-scope


    【解决方案1】:

    我正在寻找所有符合以下条件的书籍 将两章都命名为序言和 介绍。

    我使用 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
    

    【讨论】:

    • 这个工作非常好,如果有点慢的话。添加一些索引会有所帮助。
    • 是的,PostgreSQL 支持子查询。它比 MySQL 更早地受到支持,因此它也可以在其中工作。
    【解决方案2】:

    我喜欢把代码压缩到

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

    这确实适用于带有“OR”的连接。但是 AND 连接不起作用,因为这意味着连接的单行中的 chapters.name 必须同时是“前言”和“简介” .

    进行我原来的“OR”连接的一种更简洁的方法是

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

    感谢 Duplex on Rails 论坛 (Post link) 为了解决我原来的问题,虽然 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 中,您不能选择所有包含书籍的列。* 然后 GROUP BY 书籍。* 每列必须单独命名:(

    【讨论】:

    • 简洁的方式最好::conditions=>{:chapters=>{:name=>chapter_names}},但我也喜欢:conditions=>["chapters.chapter_names IN (?)" ,chapter_names]
    【解决方案3】:

    我正在寻找所有符合以下条件的书籍 将两章都命名为序言和 介绍。

    目前我有一个 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
    

    【讨论】:

    • 这似乎运作良好。唯一的问题是我更喜欢 named_scope,因为这样我可以将它与其他范围链接在一起。我会继续调查
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多