'Is it possible to reliably execute a callback on an ActiveRecord jsonb column assignment or change?

I'm trying to execute some additional functionality in an ActiveRecord model when a jsonb column value is assigned to or when it changes.

My model is simple, it just contains a single jsonb column called payload. My current thinking is to override the column setter, but this doesn't seem to work as expected:

class Entity < ActiveRecord::Base
  def payload=(jsonb)
    pp 'Setter called...'
    super(jsonb)
  end
end

With the above model the following works:

ent = Entity.new
ent.payload = {}
#>> Setter called...
ent.save!

but subsequently the following syntax does not:

ent.payload["attribute"] = "value"
#>> ...

Is there any way to trigger code or fire a callback that works for both methods of assignment (inline and explict)?

I am aware that it is possible to hook into the ActiveModel dirty attributes to check if the jsonb value has changed at other points such as before_save, but these are reliant on active model callbacks.

I am specifically looking for a way to react to the change when it occurs, on assignment, not on subsequent events in the model life-cycle.



Solution 1:[1]

When you assign attribute / value, you call other method under the hood -- Hash#[]=

So if you decide to change its behavior you need to override like this

class Hash
  def []=(key, val)
    pp 'Setter called...'
    super(key, val)
  end
end

But keep in mind that such monkeypatch will affect not only this model, but everywhere when you will assign hash key

According to the comment you can define your own class

class TalkingHash < Hash
  def []=(key, val)
    pp 'Setter called...'
    super(key, val)
  end
end

ent = Entity.new

ent.payload = TalkingHash.new
# Setter called...

ent.payload[:nested] = TalkingHash.new
# Setter called...

ent.payload[:nested][:attribute] = :value
# Setter called...

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1