LcLk LcLk - 7 months ago 18
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

has_many
relationship:
A Blog post

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

has_many :comments
end


and it's comments

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

belongs_to :post
end


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
Mongoid::Relations::Targets::Enumerable
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
criteria
attribute of the
Mongoid::Relations::Targets::Enumerable
class instance
post.comments
, 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
criteria
onto
post.comments
change what the
.comments
method does?
I took a look through Mongoid's implementation of the
Mongoid::Relations::Targets::Enumerable
class (source on Github), but couldn't find any clues to how it works.




Edit



To clarify the question:

This code, doesn't query the database:

post.comments.criteria


But this code does:

foo = post.comments
post.comments.criteria


How come?

Answer

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

Source: http://stackoverflow.com/a/13284331/1483788

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.

Comments