Rails 6 adds touch_all method to ActiveRecord::Relation


touch is used to update the updated_at timestamp.

Rails 6 has added touch_all method to ActiveRecord::Relation in order to touch multiple records at once.

Before

Before Rails 6, we needed to loop over all the records and call touch for each record.

# Finding all the products whose price is 200 and calling touch for each product
Product.where("price >= ?", 100).find_each { |p| p.touch }
Product Load (0.2ms)  SELECT "products".* FROM "products" WHERE (price >= 100)
(0.0ms)  begin transaction
Product Update (0.4ms)  UPDATE "products" SET "updated_at" = ? WHERE "products"."id" = ?  [["updated_at", "2019-08-21 12:07:09.428231"], ["id", 1]]
(0.7ms)  commit transaction
...
(0.0ms)  begin transaction
Product Update (0.2ms)  UPDATE "products" SET "updated_at" = ? WHERE "products"."id" = ?  [["updated_at", "2019-08-21 12:07:09.433908"], ["id", 4]]
(0.4ms)  commit transaction
(0.0ms)  begin transaction
Product Update (0.2ms)  UPDATE "products" SET "updated_at" = ? WHERE "products"."id" = ?  [["updated_at", "2019-08-21 12:07:09.435312"], ["id", 5]]
(0.4ms)  commit transaction
#=> [#<Product id: 1, price: 200, name: "Item 1", created_at: "2019-08-18 11:17:29", updated_at: "2019-08-21 12:07:09">, ...]

This results N +1 queries.

There are multitude of wasy in which touch could be used:

# Passing column names as created_at
Product.where(price: 200).find_each { |product| product.touch(:created_at) }

# Passing column names as created_at and time as 2 days before time
Product.where(price: 200).find_each { |product| product.touch(:created_at, time: Time.current - 2.days) }

# Passing time as 2 days before time
Product.where(price: 200).find_each { |product| product.touch(time: Time.current - 2.days) }

touch_all

We can now use touch_all to overcome this looping operation.

touch_all accepts two arguments:

  • column names
  • time (optional)

It updates updated_at column by default, same as touch. Default value for time is current time.

# Finding all the products whose price is 200 and calling touch_all
Product.where("price >= ?", 100).touch_all
Product Update All (2.7ms)  UPDATE "products" SET "updated_at" = ? WHERE (price >= 100)  [["updated_at", "2019-08-21 12:06:06.625813"]]
#=> 5

In comparison to touch, this results in a single query instead of N+1 queries.

Similarly, we can utilize touch_all method in mulitple ways:

# Passing column names as created_at
Product.where("price >= ?", 100).touch_all(:created_at)
Product Update All (1.6ms)  UPDATE "products" SET "updated_at" = ?, "created_at" = ? WHERE (price >= 100)  [["updated_at", "2019-08-21 12:14:34.124214"], ["created_at", "2019-08-21 12:14:34.124214"]]
#=> 5

Time.current
#=> Wed, 21 Aug 2019 12:16:17 UTC +00:00

# Passing column names as created_at and time as 2 days before time
Product.where("price >= ?", 100).touch_all(:created_at, time: Time.current - 2.days)
Product Update All (3.1ms)  UPDATE "products" SET "updated_at" = ?, "created_at" = ? WHERE (price >= 100)  [["updated_at", "2019-08-19 12:15:25.697535"], ["created_at", "2019-08-19 12:15:25.697535"]]
#=> 5

# Passing time as 2 days before time
Product.where("price >= ?", 100).touch_all(time: Time.current - 2.days)
Product Update All (10.9ms)  UPDATE "products" SET "updated_at" = ? WHERE (price >= 100)  [["updated_at", "2019-08-19 12:15:49.313987"]]
#=> 5

Summary

touch_all provides us with a faster and simpler way to touch a collection of objects.