将一种方法链接到另一种方法如何改变原始方法

How can chaining one method onto another change the original method

解释这个难题的最简单方法是举个例子:

假设我有两个 Mongoid 模型,它们通过 has_many 关系关联: 博客 post

class Post
   include Mongoid::Document
   field :body, type: String
   
   has_many :comments
end

这是评论

class Comment
   include Mongoid::Document
   field :text, type: String
   
   belongs_to :post
end

现在我创建了一个 Post,它在 IRB 中有两个评论,我试图通过关系加载它们。我启用了一些数据库日志记录,所以我可以看到何时进行查询:

post.comments #=>
 2016-04-27 13:51:52.144 [DEBUG MONGODB | localhost:27017 | test.find | STARTED | {"find"=>"comments", "filter"=>{"post_id"=>BSON::ObjectId('571f315e5a4e491a6be39e02')}}]
 2016-04-27 13:51:52.150 [DEBUG MONGODB | localhost:27017 | test.find | SUCCEEDED | 0.000492643s]
 => [#<Comment _id: 571f315e5a4e491a6be39e03, text: 'great post' >, #<Comment _id: 571f315e5a4e491a6be39e12, text: 'this!' >]

所以评论从数据库中加载并作为 Mongoid::Relations::Targets::Enumerable class 返回,它看起来像一个数组,它包含两个评论。

现在,当我打开一个新的 IRB 控制台,并使用 Mongoid::Relations::Targets::Enumerable class 实例 [=20] 的 criteria 属性查看用于加载这些评论的标准=],我得到这个输出:

post.comments.criteria #=>
 => #<Mongoid::Criteria
 selector: {"post_id"=>BSON::ObjectId('571f315e5a4e491a6be39e02')}
 options:  {}
 class:    Comment
 embedded: false>

这个例子怎么没有DB请求?这不是缓存问题,因为我打开了一个新的 IRB 控制台。

如何将 criteria 链接到 post.comments 上来改变 .comments 方法的作用? 我查看了 Mongoid 对 Mongoid::Relations::Targets::Enumerable class (source on Github) 的实现,但找不到有关其工作原理的任何线索。


编辑

澄清问题:

这段代码,不查询数据库:

post.comments.criteria

但是这段代码可以:

foo = post.comments
post.comments.criteria

怎么会?

post.comments 就是您所说的 "Query object"。换句话说,它包含从数据库中获取所需数据的所有必要信息,但不包含数据本身。当您调用 post.comments.criteria 时,它只是显示查询对象为了发出请求而存储的相关参数。对象 ID 在这里可用,因为 post 已经存在于内存中。

如果您使用的是 sql 数据库,同样的原则也适用于 post.comments.to_sql

将评论转换为答案:

执行Mongoid::Relations::Targets::Enumerable#inspect时,检查方法对所有条目执行:

Inspection will just inspect the entries for nice array-style printing.

如果不使用查询方法,这是无法完成的。

OP 实际提出的问题与 IRB 控制台更相关。 IRB 控制台以触发 #inspect 的方式处理此响应对象,进而触发查询方法。对于 Mongoid(和 ActiveRecord)类,#inspect 方法执行查询以产生预期结果。

例如,如果在 IRB 控制台中运行,这将触发对数据库的查询:

>> foo = posts.comments
>> post.comments.criteria

foo 的响应将在 IRB 控制台尝试输出对象作为响应时触发查询方法。可以通过(至少)以下两种方式之一在 IRB 控制台中抑制对数据库的查询:

方法一:and nil

本质上,您可以在单个命令后缀 and nil;0 或类似的东西,以防止第一个命令被 IRB 的响应输出处理。这是因为 IRB 控制台只查看最后一个对象(如 ruby 方法的 return)。

>> foo = posts.comments and nil
>> post.comments.criteria

上面不会查询数据库,因为第一行IRB处理的输出是nil而不是foo变量

方法二:conf.echo = false

来源:

此方法可防止 IRB 控制台自动处理响应对象。

>> conf.echo = false    
>> foo = posts.comments
>> post.comments.criteria

这不会从控制台中查询数据库。但是,您也不会从最后一行得到响应。您将需要使用 putspp(漂亮的打印)来输出对象。相比之下,如果您在使用此方法时 运行 命令 foo.inspect,您会注意到执行了对数据库的查询以产生所需的结果。