【问题标题】:Ruby on rails active record queries which one is efficientRuby on rails 活动记录查询哪一个是高效的
【发布时间】:2018-05-10 10:35:22
【问题描述】:

我最近在做一个项目,我面临着在两种获得相同结果的方式之间进行选择的两难境地。这是类结构:

class Book < ApplicationRecord
  belongs_to :author
end

class Author < ApplicationRecord
  has_many :books
end

作者有名字,姓氏。我想获取给定书籍的作者全名作为实例方法。

在简单的活动记录术语中,由于 book 与 author 相关联,我们可以如下获取一本书的作者姓名:

例如在 Book 类中,我们有:

class Book < ApplicationRecord
  belongs_to :author

  def author_name
    "#{author.first_name} #{author.last_name}"
  end

end

我们得到了结果!

但是,根据最小化依赖关系(POODR Book)、未来易于更改和更好的面向对象设计的目标,这本书不应该知道作者的属性。它应该通过接口与作者对象交互。

所以 Book 不应该是负责获取作者姓名的人。作者类应该。

class Book < ApplicationRecord
  belongs_to :author

  def author_name
    get_author_name(self.author_id)
  end

  private

  #minimizing class dependecies by providing private methods as external interfaces
  def get_author_name(author_id)
    Author.get_author_name_from_id(author_id)
  end

end

class Author < ApplicationRecord
    has_many :books

    #class methods which provides a gate-way for other classes to communicate through interfaces, thus reducing coupling.   

    def self.get_author_name_from_id(id)
        author = self.find_by_id(id)
        author == nil ? "Author Record Not Found" : "#{author.first_name.titleize} #{author.last_name.titleize}" 
    end

end

现在,图书只是与 Author 提供的公共接口进行交互,而 Author 负责从其属性中获取全名,这无疑是一个更好的设计。

我尝试在控制台中将查询作为两种不同的方法运行:

class Book < ApplicationRecord
  def author_name
    get_author_name(self.author_id)
  end

  def author_name2
    "#{author.last_name} + #{author.first_name}"
  end
end

结果如下图:

看起来两者都运行相同的查询。

我的问题是

  1. rails 是否将 Book 类中调用的 author.last_name 转换为 与内部调用的 Author.find_by_id(author_id).last_name 相同的 SQL 查询 在数据量较大的情况下,作者类(通过从 Book 类传递的消息)?
  2. 在数据量更大的情况下,哪一个性能更高?
  3. 不从 Book 类调用 author.last_name 违反了设计 原则?

【问题讨论】:

    标签: ruby-on-rails ruby oop activerecord ruby-on-rails-5


    【解决方案1】:

    实际上使用委托更常见也更简单。

    class Book < ApplicationRecord
      belongs_to :author
      delegate :name, to: :author, prefix: true, allow_nil: true
    end
    
    class Author < ApplicationRecord
      has_many :books
      def name
        "#{first_name.titleize} #(last_name.titleize}" 
      end
    end
    

    至于性能,如果您在图书查询时join作者,您最终会执行一次查询。

    @books = Book.joins(:author)
    

    现在,当您遍历 @books 并单独调用 book.author_name 时,不需要对 authors 表进行 SQL 查询。

    【讨论】:

    • 最佳答案在这里 - 干净、整洁且不言自明。 prefixallow_nil 选项特别适合这种情况。 (虽然你认为你有一个轻微的错字 - 它不应该是joins复数吗?)
    【解决方案2】:

    1) 显然不是,它执行书籍和作者表的 JOIN。您所做的需要 2 个查询,而不是 1 个 join 您将拥有 book.find(id)author.find(book.author_id)

    2) JOIN 应该更快。

    3) 由于last_name 是一个公共接口,它绝对不违反设计原则。如果您像这样从外部访问作者的姓氏,那将违反原则:Book.find(1).author.last_name - 这是一件坏事。正确的是:Book.find(1).authors_last_name - 并在模型类中访问作者的姓名。

    您提供的示例对我来说似乎过于复杂。

    根据您分享的示例,您只想获取本书作者的全名。所以,分担责任的想法是正确的,但是在Author类中应该是简单的instance方法full_name,比如:

    class Author < ApplicationRecord
      has_many :books
    
      def full_name
        "#{author.first_name.titleize} #{author.last_name.titleize}"
      end
    end
    
    class Book < ActiveRecord::Base
      belongs_to :author
    
      def author_name
        author.full_name
      end
    end
    

    请注意,此代码中没有直接查询。一旦您在某处需要作者的姓名(在视图中、在 api 响应中等),Rails 将使最优化的查询成为可能(但取决于您的用例,它可能无效,例如,如果您调用 iterate over books并循环调用author

    【讨论】:

    • IMO,Book 类中的author_name 方法是不必要的,因为它委托给作者类full_name 方法。想要获取这些信息的代码可以直接从作者类本身获取。因此,减少耦合
    【解决方案3】:

    我更喜欢第二种方法,因为full_name 是作者的财产,而不是一本书。如果这本书想要访问该信息,它可以使用book.author&amp;.full_name&amp; 用于处理没有作者的书籍的情况)。

    但我建议进行如下重构:

    class Book < ApplicationRecord
      belongs_to :author
    end
    
    class Author < ApplicationRecord
      has_many :books
    
      def full_name
        "#{firstname} #{lastname}"
      end
    end
    

    【讨论】:

      【解决方案4】:
      1. Rails 是否会将 Book 类中调用的 author.last_name 转换为与 Author 类中调用的 Author.find_by_id(author_id).last_name 相同的 SQL 查询(通过从 Book 类传递的消息)以防数据量较大?

      取决于调用因素,就像在您的示例中一样,两者都会生成相同的查询。但是如果你在获取 Book/Author 时有一个 include\join 子句,两者都会生成不同的查询。

      根据 Rails 约定,不建议使用 Author.find_by_id(author_id).last_name,因为每当调用该方法时,它总是会触发对数据库的查询。应该使用rails的关联接口来调用相关对象上的方法,该方法可以智能地从内存中识别对象,如果不在内存中,则从数据库中获取它。

      1. 在数据量更大的情况下,哪一个性能更高?

        author.last_name 更好,因为如果使用它会处理连接、包含和记忆子句,并避免 N+1 查询问题。

      2. 从 Book 类调用 author.last_name 不违反设计原则吗?

        ,您甚至可以使用 @Steve Suggested 之类的委托。

      【讨论】:

        【解决方案5】:

        根据我的经验,这是最小化代码复杂性和最小化可扩展性问题之间的平衡。

        但是,在这种情况下,我认为分离类关注点并最小化代码的最简单解决方案是简单地使用:@book.author.full_name

        并在您的 Author.rb 中定义 Author.rb 中的全名:

        def full_name   
          "#{self.first_name} #{self.last_name}" 
        end
        

        这将大大简化您的代码。例如,如果将来您有另一个名为 Magazine 的模型,它有一个 Author,那么您也不必在 Magazine 模型中定义 author_name。您只需使用@magazine.author.full_name。这将很好地干燥您的代码。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-12-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-01-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多