ActiveRecord methods touch and update_columns no longer work for readonly models


A Rails model has several ActiveRecord methods that make interacting with the database very simple. Models can be created, updated and destroyed very easily.

However, in some situations, one might want to mark a model as “readonly”. Meaning that only read operations are allowed — no updates or deletions. A situation like this may occur in the middle of a big refactoring. A model has been retired but we may not be ready to wipe it out. We might still need to read it.

Before

This can be achieved by adding a readonly? method to the class which returns true?.

class Rocket
  ...
  
  def readonly?
    true
  end
end

Now, when we try to update or delete the model we get an error.

irb(main):001:0> rocket.update name: "A1"
  TRANSACTION (5.4ms)  BEGIN
  User Load (6.2ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  TRANSACTION (1.8ms)  ROLLBACK                                                       
Traceback (most recent call last):                                                    
/Users/swaathi/.rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/bundler/gems/rails-08af60e01dcf/activerecord/lib/active_record/persistence.rb:1147:in `_raise_readonly_record_error': Rocket is marked as readonly (ActiveRecord::ReadOnlyRecord)

irb(main):002:0> rocket.destroy
Traceback (most recent call last):
/Users/swaathi/.rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/bundler/gems/rails-08af60e01dcf/activerecord/lib/active_record/persistence.rb:1147:in `_raise_readonly_record_error': Rocket is marked as readonly (ActiveRecord::ReadOnlyRecord) 

Both options raise the ActiveRecord::ReadOnlyRecord error.

However when we try to touch the model we get no error.

irb(main):002:0> rocket.touch
  TRANSACTION (0.5ms)  BEGIN
  Rocket Update (14.2ms)  UPDATE `rockets` SET `rockets`.`updated_at` = '2022-08-14 06:40:24.291104' WHERE `rockets`.`id` = 1
  TRANSACTION (5.6ms)  COMMIT                       
=> true

While a touch call doesn’t do much damage, the update_columns call does. Unfortunately, marking a model as readonly does not prevent us from updating the model in all possible ways. This corrupts the data with unwarranted updates.

After

People were too quick to point out this discrepancy, however, the Rails team was even quicker to issue a patch!

A simple update to the update_columns and touch models within ActiveRecord fixed this.

activerecord/lib/active_record/persistence.rb

...
module ActiveRecord
  module Persistence
    extend ActiveSupport::Concern

    module ClassMethods
      def update_columns
        ...
        _raise_readonly_record_error if readonly?
        ...
      end
    end
  end
end

Now readonly models are no longer updated when update_columns or touch is called.

Join Our Newsletter