我刚开始学习Rails。所以我创建了一个模型Bookmark,打开了rails控制台,并输入了:
Bookmark.all
结果:
书签加载(1.0ms)SELECT"书签"。* FROM"书签" 等
然后我输入了这个:
Bookmark.all.count
结果:
SELECT COUNT(*)FROM"书签"
这种优化的性能提升是显而易见的,所以我理解为什么他们做到了。我不明白的是,如何?我对Ruby太新了,无法轻松搜索源代码。如果你要实现这个,你将如何处理它?</ p>
答案 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_a
,to_ary
和execute
都做同样的事情,但这两种额外的方法给我们带来了几个好处。首先,显式数组转换方法(to_a
)符合Ruby核心和标准库中已有的约定。我们实际上只需将execute
重命名为to_a
,因为没有必要同时使用这两者。
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查询(并累积信息,如对where
,sort
或count
的调用),直到您尝试获取实际数据。到那时,您将知道您需要获取什么,并且能够构建有效的查询。