Rails 5 Pluck query changes over Rails 4

Rails 5 has improved the way query was done on database when pluck was called on a model. Before Rails 5, when pluck was called, it used to query database even if the activerecord collection is queried before. There are some posts explaining don’t pluck unnecessarily In this post we will discuss how Rails 5 has improved the way pluck used to work.

Before Rails 5

The Rails used to query database without checking if the query is already done on the database. This behavior and the difference between Pluck and Map/collect is discussed in the article here.

Let’s say we have a model called Product with following attributes.

  • id
  • name
  • price

We want fetch all the products having price > 20.

We will write query something like,

products = Product.where('price > ?', 20)

Once we have this data, we can use products which is Advertisement::ActiveRecord_Relation

The query is sent to the database whenever operations are performed over this ActiveRecord_Relation object. The operations can be each, map, all etc.

Let’s say we perform each operation on the products ActiveRecord_Relation object.

products.each do |product|
  puts "The product details are: #{product.inspect}"
end

Now, if we try to pluck on products, then it will query again on database to get the required result instead of just fetching the data from the records loaded in the memory.

products.pluck(:price)
# SELECT "products."id" FROM "products" WHERE (price < 20)

This behavior is shown in the script given below.

If we run the above given script, it will give output as shown below.

-- create_table(:products, {:force=>true})

Calling products.each -> First Query is done here
D, [2017-07-12T12:07:17.777584 #22312] DEBUG -- :   Product Load (0.1ms)  SELECT "products".* FROM "products" WHERE (price > 20)

Calling products.pluck(:price) -> Second Query is done here
D, [2017-07-12T12:07:17.781785 #22312] DEBUG -- :    (0.1ms)  SELECT "products"."price" FROM "products" WHERE (price > 20)

Here we can clearly see two queries being done on products table.

Thus, we had to keep an eye on when exactly the query is sent to the database. Usually, we should try to avoid querying as much as possible. Thus, this behavior posed some concerns on freely using pluck in Rails.

After Rails 5

Rails 5 has introduced a fix for the issue discussed above. Now, Rails tries to detect if the records are already loaded by usage of any of the operations such as each, map, all and others.

E.g. Taking the same example as above. Let’s say we have ActieRecord_Relation as follows.

products = Product.where('price > ?', 20)

And the query is sent because we perform some operation on products as given below.

product.each do |product|
  puts "Product details are: #{product.inspect}"
end

Now, if we try to use pluck over the products then, it will not result into a separate query to the database as records are already loaded into memory.

product.pluck(:price)

Once we run the above script, it will give output as shown below.

-- create_table(:products, {:force=>true})

Calling products.each -> First Query is done here
D, [2017-07-12T12:14:10.295796 #22780] DEBUG -- :   Product Load (0.1ms)  SELECT "products".* FROM "products" WHERE (price > 20)

Calling products.pluck(:price) -> Second Query is avoided from here

Here we can clearly see that second query is avoided. The products.each call sends the query and records are loaded in the memory. Thus, when products.pluck(:price) is called, query is avoided.

Conclusion

Thus, Rails 5 has improved the way query is done on the database. Developers don’t need to remember when exactly queries are happening. pluck can be used more freely with the release of Rails 5.