2

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?

Community
  • 1
  • 1
caffeinated.tech
  • 6,428
  • 1
  • 21
  • 40
  • 1
    Are you asking how `post.comments.criteria` is showing the ObjectId selector? If so, your `post` is a variable (of the Post class) and it comes from there. Because the `post` variable is a Post class, it understands that #comments is a relationship (has_many). In short, I believe a DB call was made when your `post` variable was initialized. – wes.hysell Apr 27 '16 at 13:27
  • 1
    It isn't going to make a DB call until you use a query method (See: https://docs.mongodb.org/ecosystem/tutorial/mongoid-queries/). – wes.hysell Apr 27 '16 at 13:41
  • @wes.hysell Thank you for the comments. If it shouldn't make a DB call at all, then why is it doing so for me? I'm using `Mongoid` version 5.1.3 and rails `4.2.6` – caffeinated.tech Apr 27 '16 at 14:09
  • 1
    You mention that you are running this in IRB console. The IRB console runs inspect on the returned object (`foo`). If you make the command `foo = post.comments and nil` you should notice that the db is not queried. Then, you can simply run the command `foo` and that should query the database because the IRB console will run #inspect on the returned object. This is the effect of IRB console. – wes.hysell Apr 27 '16 at 14:20
  • 1
    @wes.hysell Off course, that's it. The console prints the output of the command run. As `Mongoid::Relations::Targets::Enumerable`'s inspect method calls inspect on the `entries`, it has to make the query to show them. Thank you! – caffeinated.tech Apr 27 '16 at 14:23
  • 1
    I turned my comments into an answer for you. :) – wes.hysell Apr 27 '16 at 14:39

2 Answers2

1

post.comments is what you would call a "Query object". In other words, it contains all the necessary information to fetch the required data from the database, but not the data itself. When you call post.comments.criteria it's simply displaying the relevant parameters which the query object stores in order to make the request. The object id is available here because post already exists in memory.

The same principle would apply to post.comments.to_sql if you were using a sql database.

Anthony E
  • 11,072
  • 2
  • 24
  • 44
1

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: https://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.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
wes.hysell
  • 1,199
  • 10
  • 14