Search code examples
rubypostgresqlsequeljsonb

Ruby Sequel dirty status on json column not working using Postgresql


Using sequel 4.29.0.

I'm planning to use the before_save sequel hook to trigger an action, In this case I need to make sure if an specific column changed, in previous implementations I used the @changed_columns sequel instance variable to get the list of the columns that changed and some something like:

class MyModel < Sequel::Model
  def before_save
    special_method if @changed_columns.include? :properties

    super
  end

  def special_method
    ...
  end
end

This time I'm using the Postgresql jsonb type column (very handy btw). Every time I use modify that column the @changed_columns never hold the column, I know I can specify a column as "dirty", however that means I have to change all the places this column is changed and sometimes is not that simple.

An example of how this is not working:

irb(main):001:0> MyModel.columns
=> [:id, :foo, :properties]     # :foo is a string, :properties is jsonb

irb(main):002:0> my_model = MyModel.last
=> #<MyModel @values={:id=>37, :foo=>"bar", :properties=>{"foo"=>true}}>

irb(main):003:0> my_model.properties['foo'] = false
=> false

irb(main):004:0> my_model
=> #<MyModel @values={:id=>37, :foo=>"bar", :properties=>{"foo"=>false}}>

irb(main):005:0> my_model.modified?
=> false                        # this should be true since properties changed a value inside the hash

irb(main):006:0> my_model.foo = 'foo'
=> "foo"

irb(main):007:0> my_model
=> #<MyModel @values={:id=>37, :foo=>"foo", :properties=>{"foo"=>false}}>

irb(main):008:0> my_model.modified?
=> true

irb(main):009:0> my_model.changed_columns
=> [:foo]                       # should include the :properties column

Solution

  • This is Sequel's expected default behavior. If you want to detect mutation to column values, you need to use the modification_detection plugin:

    DB.extension :pg_json
    DB.create_table!(:bs){primary_key :id; jsonb :properties}
    class B < Sequel::Model; end
    B.plugin :modification_detection
    B.create(:properties=>{'a'=>1})
    b = B.first
    b.properties['a'] = 2
    b.modified?
    # => true