LcLk LcLk - 2 years ago 66
Ruby Question

How can chaining one method onto another change the original method

The easiest way to explain this conundrum is with an example:

Say I have two Mongoid models which are related via a

A Blog post

class Post
include Mongoid::Document
field :body, type: String

has_many :comments

and it's comments

class Comment
include Mongoid::Document
field :text, type: String

belongs_to :post

Now I create a Post which has two comments in IRB, and I attempt to load them via the relationship. I have some DB logging enabled so I can see when the query is made:

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!' >]

So the comments are loaded from the DB and returned as a
class, which looks like an array, and it contains the two comments.

Now when I open a fresh IRB console, and take a look at the criteria used to load these comments using the
attribute of the
class instance
, I get this output:

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

How come no DB requests is made in this example? It's not a caching problem as I opened a new IRB console.

How can chaining
change what the
method does?
I took a look through Mongoid's implementation of the
class (source on Github), but couldn't find any clues to how it works.


To clarify the question:

This code, doesn't query the database:


But this code does:

foo = post.comments

How come?

Answer Source

Converting comments to an answer:

When executing Mongoid::Relations::Targets::Enumerable#inspect, the inspect method is executed on all of the entries:

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

This cannot be done without the use of a query method.

The question the OP is actually having is more related with the IRB console. The IRB console processes this response object in a way that triggers #inspect and, in turn, a query method. For Mongoid (and ActiveRecord) classes, the #inspect method executesa query in order to produce the expected result.

By example, this will trigger a query to the database if ran in the IRB console:

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

The response of foo will trigger a query method when the IRB console attempts to output the object as a response. The query to the db can be suppressed in the IRB console in (at least) one of two ways:

Method 1: and nil

Essentially, you can suffix a single command with and nil or ;0 or something similar to prevent the first command from being processed by IRB's response output. This is because the IRB console only looks at the last object (like a ruby method's return).

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

The above will not query the database, because the output processed by IRB on the first line is nil instead of the foo variable.

Method 2: conf.echo = false


This method prevents the IRB console from processing response objects automatically.

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

This will not query the database from within the console. However, you will not get a response from the last line, either. You will need to use puts or pp (pretty print) to output the object. By comparison, if you run the command foo.inspect while using this method, you will notice a query to the db is executed in order to the produce the desired result.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download