1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
|
# frozen-string-literal: true
module Sequel
module Plugins
# This plugin automatically detects in-place modifications to
# columns as well as direct modifications of the values hash.
#
# class User < Sequel::Model
# plugin :modification_detection
# end
# user = User[1]
# user.a # => 'a'
# user.a << 'b'
# user.save_changes
# # UPDATE users SET a = 'ab' WHERE (id = 1)
#
# Note that for this plugin to work correctly, the column values must
# correctly implement the #hash method, returning the same value if
# the object is equal, and a different value if the object is not equal.
# As this solely uses hash values to check for modification, there may
# be cases where a modification is made and the hash value is the same,
# resulting in a false negative.
#
# Note that this plugin causes a performance hit for all retrieved
# objects, so it shouldn't be used in cases where performance is a
# primary concern.
#
# Usage:
#
# # Make all model subclass automatically detect column modifications
# Sequel::Model.plugin :modification_detection
#
# # Make the Album class automatically detect column modifications
# Album.plugin :modification_detection
module ModificationDetection
module ClassMethods
# Calculate the hashes for all of the column values, so that they
# can be compared later to determine if the column value has changed.
def call(_)
v = super
v.calculate_values_hashes
v
end
end
module InstanceMethods
# Recalculate the column value hashes after updating.
def after_update
super
recalculate_values_hashes
end
# Calculate the column hash values if they haven't been already calculated.
def calculate_values_hashes
@values_hashes || recalculate_values_hashes
end
# Detect which columns have been modified by comparing the cached hash
# value to the hash of the current value.
def changed_columns
changed = super
if vh = @values_hashes
values = @values
changed = changed.dup if frozen?
vh.each do |c, v|
match = values.has_key?(c) && v == values[c].hash
if changed.include?(c)
changed.delete(c) if match
else
changed << c unless match
end
end
end
changed
end
private
# Recalculate the column value hashes after manually refreshing.
def _refresh(dataset)
super
recalculate_values_hashes
end
# Recalculate the column value hashes after refreshing after saving a new object.
def _save_refresh
super
recalculate_values_hashes
end
# Recalculate the column value hashes, caching them for later use.
def recalculate_values_hashes
vh = {}
@values.each do |k,v|
vh[k] = v.hash
end
@values_hashes = vh.freeze
end
end
end
end
end
|