The project is in a healthy, maintained state
Updates ActiveRecord records only when they match a condition in the database, atomically, as part of the UPDATE statement.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

 Project Readme

ActiveRecord::UpdateWhen

Updates ActiveRecord records only when they match a condition in the database.

The condition is part of the UPDATE statement itself, so it can't change between checking it and updating the record (eg. by a concurrent update).

Installation

Add this line to your application's Gemfile:

gem 'active_record-update_when'

And then execute:

$ bundle

Then include ActiveRecord::UpdateWhen in your models (typically in ApplicationRecord, so all models get it):

class ApplicationRecord < ActiveRecord::Base
  primary_abstract_class

  include ActiveRecord::UpdateWhen
end

Usage

update_when updates a record (as update would) if it matches the condition in the database:

order = Order.find(...)

if order.update_when(condition: Order.checked_out, attributes: { status: 'cancelled' })
  # order was cancelled
elsif order.errors.any?
  # order was invalid
else
  # order wasn't checked-out
end
UPDATE "orders" SET "status" = 'cancelled', "updated_at" = ...
WHERE "orders"."id" = 1 AND "orders"."status" = 'checked_out'

It returns false if the record is invalid (with errors), or if it doesn't match the condition (without errors). When the condition isn't met, the update is rolled back, including any changes made by its callbacks (even within another transaction).

update_when! updates the record as update! would. It returns false if the record doesn't match the condition, and raises whenever update! would (eg. ActiveRecord::RecordInvalid):

order.update_when!(condition: Order.checked_out, attributes: { status: 'cancelled' })

Conditions

The condition is a relation of the record's model. Its WHERE clause is added to the UPDATE:

order.update_when(condition: Order.where(status: %w[pending checked_out]), attributes: { status: 'cancelled' })

Conditions can also have joins, GROUP BY and HAVING clauses (including for models with composite primary keys), in which case the record is matched by an EXISTS subquery:

order.update_when(condition: Order.checked_out.joins(:lines), attributes: { status: 'cancelled' })

If the attributes don't change the record, nothing is updated (as with update) and the condition isn't checked.

Development

The specs run against a dummy Rails application in spec/dummy, using SQLite by default:

$ bin/rspec
$ bin/rubocop

PostgreSQL and MySQL are selected with the DATABASE_ADAPTER and DATABASE_URL environment variables (see .github/workflows/ci.yml).

License

The gem is available as open source under the terms of the MIT License.