Ruby on Rails如何做到这一点?

时间:2014-02-14 05:10:47

标签: ruby-on-rails ruby

我刚开始学习Rails。所以我创建了一个模型Bookmark,打开了rails控制台,并输入了:

  

Bookmark.all

结果:

  

书签加载(1.0ms)SELECT"书签"。* FROM"书签"   等

然后我输入了这个:

  

Bookmark.all.count

结果:

  

SELECT COUNT(*)FROM"书签"

这种优化的性能提升是显而易见的,所以我理解为什么他们做到了。我不明白的是,如何?我对Ruby太新了,无法轻松搜索源代码。如果你要实现这个,你将如何处理它?<​​/ p>

2 个答案:

答案 0 :(得分:5)

这是一个简单的示例,说明如何在需要数据之前推迟操作(请注意,这比ActiveRecord简单得多,不费力地提高效率,并且只是描述概念的最快方式)。 / p>

首先,我们有一个简单的数据库,由1到100的数字组成。这将是我们查询的数据集:

class Database
  def numbers
    (1..100).to_a
  end
end

Query对象将维护对数据库对象的引用以及一组指令。使用where方法将指令添加到数组中。

class Query
  attr_accessor :lambdas, :database
  def initialize
    @database = Database.new
    @lambdas = []
  end

  def where(condition)
    lambdas << condition
    # after an instruction is added, return the object to allow more chaining.
    self
  end
end

一旦建立了查询的参数,您仍然只有一个查询对象没有向数据库询问任何内容,直到您致电execute

class Query
  def execute
    lambdas.inject(database.numbers) do |set, lambda|
      set.select(&lambda)
    end
  end
end

# the set of numbers divisible by 2 and 3:
q = Query.new.where(->(x){ x.even? }).where(->(x){ x % 3 == 0 })
q.execute #=> [6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96]

ActiveRecord为您做了一些不错的事情,包括必要时的自动数组转换。但是,我们可以简单地定义一个method_missing来捕获尚未为Query类定义的所有消息,而不是实现所有数组/可枚举方法。我们假设它们应该在数组上调用,这很方便,就是从execute返回的数据。

class Query
  def method_missing(name, *args, &block)
    execute.send(name, *args, &block)
  end
end

所以现在我们可以从之前获取我们的查询对象,并直接与它进行交互,就像它是一个数组一样:

q.map { |n| n * 2 }
#=> [12, 24, 36, 48, 60, 72, 84, 96, 108, 120, 132, 144, 156, 168, 180, 192]

q.count #=> 16

但是,您不需要让数组来处理这个问题。在ActiveRecord的情况下,它可以很容易地让转换的数组响应count,但是通过在内部定义该方法,可以以更有效的方式构造SQL查询。在ActiveRecord::Relation上调用不同的方法会更改对象的状态,并且该状态最终会确定在检索数据时弹出的查询。

让我们扩展Query API以响应Ruby的显式和隐式数组转换方法:

class Query
  def to_a
    execute
  end

  def to_ary
    to_a
  end
end

现在,to_ato_aryexecute都做同样的事情,但这两种额外的方法给我们带来了几个好处。首先,显式数组转换方法(to_a)符合Ruby核心和标准库中已有的约定。我们实际上只需将execute重命名为to_a,因为没有必要同时使用这两者。

但是,{p> to_ary给了我们不同的东西。它允许我们将查询对象视为隐式数组,因此如果我们尝试将其添加到另一个数组,或者在其上调用Array()转换方法,它就会按预期运行:

my_array = [1,2,3]
q = Query.new.where(->(x){ x > 95 })

my_array + q # => [1,2,3,96,97,98,99,100]

同样,execute从未被显式调用,但是to_ary方法定义了当您尝试将数组与非Query等非数组对象连接时应该发生的事情。我们所做的就是创建一个“类似数组”的对象。

答案 1 :(得分:4)

总之一句话:懒惰。推迟构建SQL查询(并累积信息,如对wheresortcount的调用),直到您尝试获取实际数据。到那时,您将知道您需要获取什么,并且能够构建有效的查询。