Rails 6 adds touch_all method to ActiveRecord::Relation

2 minute read

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.2 ms ) SELECT "products" . * FROM "products" WHERE ( price >= 100 ) ( 0.0 ms ) begin transaction Product Update ( 0.4 ms ) UPDATE "products" SET "updated_at" = ? WHERE "products" . "id" = ? [[ "updated_at" , "2019-08-21 12:07:09.428231" ], [ "id" , 1 ]] ( 0.7 ms ) commit transaction ... ( 0.0 ms ) begin transaction Product Update ( 0.2 ms ) UPDATE "products" SET "updated_at" = ? WHERE "products" . "id" = ? [[ "updated_at" , "2019-08-21 12:07:09.433908" ], [ "id" , 4 ]] ( 0.4 ms ) commit transaction ( 0.0 ms ) begin transaction Product Update ( 0.2 ms ) UPDATE "products" SET "updated_at" = ? WHERE "products" . "id" = ? [[ "updated_at" , "2019-08-21 12:07:09.435312" ], [ "id" , 5 ]] ( 0.4 ms ) 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 ways 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.7 ms ) 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.6 ms ) 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.1 ms ) 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.9 ms ) UPDATE "products" SET "updated_at" = ? WHERE ( price >= 100 ) [[ "updated_at" , "2019-08-19 12:15:49.313987" ]] #=> 5

Summary