Checking if a record is about to be destroyed in `readonly?`

I have a model with a custom readonly? method, that checks certain attributes to determine if modifying the record should be allowed. This works well, but now I have the need to also check if we are trying to destroy the record, and only allow this in specific cases. I know about the before_destroy callback but I would prefer to keep all logic relating to record modification and destruction permission in a single location, since they are all very similar and check the same attribute(s). The best I’ve been able to come up with is to do caller_locations(1,1).first.label == “destroy”, which returns true if readonly? was called from the destroy (or destroy!) method, but I’m hoping there’s a built-in way to check this hiding somewhere in ActiveRecord? Something like the marked_for_destruction? method but for the parent record.

From activerecord/lib/active_record/persistence.rb:

    def destroy
      _raise_readonly_record_error if readonly?
      destroy_associations
      @_trigger_destroy_callback ||= persisted? && destroy_row > 0
      @destroyed = true
      @previously_new_record = false
      freeze
    end
...
    def destroy!
      destroy || _raise_record_not_destroyed
    end

This might be a silly question, but would it work to extract the readonly? logic into a new method, that both readonly? and before_destroy could call?

The extract method approach suggested above is the right pattern here. Your logic can still live in one place without needing any new Rails API:

def readonly?
  cannot_modify?
end

before_destroy { throw :abort if cannot_modify? }

private

def cannot_modify?
  # all your logic here
end

The stack inspection workaround is definitely something to avoid. readonly? and destroy protection are separate concerns and Rails intentionally keeps them that way, so I don’t think a core addition is the right call here. The pattern above handles this cleanly with just a couple of lines of glue code.

1 Like