Add dirty methods for store accessors

This commit is contained in:
palkan 2015-03-14 19:50:02 +03:00 committed by Vladimir Dementyev
parent 6b5130720e
commit 61a39ffcc6
No known key found for this signature in database
GPG Key ID: 8E0A19D3D1EDF5EB
3 changed files with 101 additions and 0 deletions

@ -11,6 +11,10 @@ module ActiveRecord
# of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's
# already built around just accessing attributes on the model.
#
# Every accessor comes with dirty tracking methods (+key_changed?+, +key_was+ and +key_change+).
#
# NOTE: There is no +key_will_change!+ method for accessors, use +store_will_change!+ instead.
#
# Make sure that you declare the database column used for the serialized store as a text, so there's
# plenty of room.
#
@ -49,6 +53,12 @@ module ActiveRecord
# u.settings[:country] # => 'Denmark'
# u.settings['country'] # => 'Denmark'
#
# # Dirty tracking
# u.color = 'green'
# u.color_changed? # => true
# u.color_was # => 'black'
# u.color_change # => ['black', 'red']
#
# # Add additional accessors to an existing store through store_accessor
# class SuperUser < User
# store_accessor :settings, :privileges, :servants
@ -127,6 +137,24 @@ def store_accessor(store_attribute, *keys, prefix: nil, suffix: nil)
define_method(accessor_key) do
read_store_attribute(store_attribute, key)
end
define_method("#{accessor_key}_changed?") do
return false unless attribute_changed?(store_attribute)
prev_store, new_store = changes[store_attribute]
prev_store&.dig(key) != new_store&.dig(key)
end
define_method("#{accessor_key}_change") do
return unless attribute_changed?(store_attribute)
prev_store, new_store = changes[store_attribute]
[prev_store&.dig(key), new_store&.dig(key)]
end
define_method("#{accessor_key}_was") do
return unless attribute_changed?(store_attribute)
prev_store, _new_store = changes[store_attribute]
prev_store&.dig(key)
end
end
end

@ -153,6 +153,22 @@ def test_yaml_round_trip_with_store_accessors
assert_equal "GMT", y.timezone
end
def test_changes_with_store_accessors
x = Hstore.new(language: "de")
assert x.language_changed?
assert_nil x.language_was
assert_equal [nil, "de"], x.language_change
x.save!
assert_not x.language_changed?
x.reload
x.settings = nil
assert x.language_changed?
assert_equal "de", x.language_was
assert_equal ["de", nil], x.language_change
end
def test_changes_in_place
hstore = Hstore.create!(settings: { "one" => "two" })
hstore.settings["three"] = "four"

@ -79,6 +79,63 @@ class StoreTest < ActiveRecord::TestCase
assert_not_predicate @john, :settings_changed?
end
test "updating the store will mark accessor as changed" do
@john.color = "red"
assert @john.color_changed?
end
test "new record and no accessors changes" do
user = Admin::User.new
assert_not user.color_changed?
assert_nil user.color_was
assert_nil user.color_change
user.color = "red"
assert user.color_changed?
assert_nil user.color_was
assert_equal "red", user.color_change[1]
end
test "updating the store won't mark accessor as changed if the whole store was updated" do
@john.settings = { color: @john.color, some: "thing" }
assert @john.settings_changed?
assert_not @john.color_changed?
end
test "updating the store populates the accessor changed array correctly" do
@john.color = "red"
assert_equal "black", @john.color_was
assert_equal "black", @john.color_change[0]
assert_equal "red", @john.color_change[1]
end
test "updating the store won't mark accessor as changed if the value isn't changed" do
@john.color = @john.color
assert_not @john.color_changed?
end
test "nullifying the store mark accessor as changed" do
color = @john.color
@john.settings = nil
assert @john.color_changed?
assert_equal color, @john.color_was
assert_equal [color, nil], @john.color_change
end
test "dirty methods for suffixed accessors" do
@john.configs[:two_factor_auth] = true
assert @john.two_factor_auth_configs_changed?
assert_nil @john.two_factor_auth_configs_was
assert_equal [nil, true], @john.two_factor_auth_configs_change
end
test "dirty methods for prefixed accessors" do
@john.spouse[:name] = "Lena"
assert @john.partner_name_changed?
assert_equal "Dallas", @john.partner_name_was
assert_equal ["Dallas", "Lena"], @john.partner_name_change
end
test "object initialization with not nullable column" do
assert_equal true, @john.remember_login
end