Last active
May 22, 2017 23:05
-
-
Save rob-mcgrail/fa6c2371fe8cab890b7503baffb1c784 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module EntityChangeCallbacks | |
extend ActiveSupport::Concern | |
# This concern allows us to treat lists of entity | |
# attributes as a unit, with their own create and update | |
# callbacks. | |
# | |
# | |
# create_change_callback name: :profile, | |
# attributes: [ | |
# :business_name, | |
# :company_name, | |
# :website_url | |
# ] | |
# | |
# The above code will: | |
# | |
# * Trigger a before/after_update_profile callback for any | |
# after_save-triggering update involving those attributes. | |
# | |
# If there is a profile_updated_at DateTime | |
# column on Entity, it will also: | |
# | |
# * Update the profile_updated_at attribute on every change to | |
# the listed attributes. | |
# | |
# * Provide a profile_status method, returning true if any | |
# of those attribtues has ever been set. (Return null if no column set). | |
# | |
# * Trigger before/after_create_profile callback first time any | |
# of the columns is updated. | |
# | |
# | |
# If all you want is the update_profile callback, there's no need to | |
# have the updated_at column. | |
# | |
# You can track columns in multiple overlapping groupings. | |
module SharedMethods | |
def status_method_symbol(name) | |
"#{name}_status".to_sym | |
end | |
def update_callback_symbol(name) | |
"update_#{name}".to_sym | |
end | |
def create_callback_symbol(name) | |
"create_#{name}".to_sym | |
end | |
def timestamp_symbol(name) | |
"#{name}_updated_at".to_sym | |
end | |
end | |
module ClassMethods | |
include SharedMethods | |
def create_change_callback(name:, attributes:) | |
set_tracked_attributes name, attributes | |
define_change_callbacks name | |
define_status_method name | |
end | |
def set_tracked_attributes(name, attributes) | |
@tracked_attributes = {} unless @tracked_attributes | |
@tracked_attributes[name] = attributes | |
end | |
def tracked_attributes | |
@tracked_attributes || {} | |
end | |
def define_status_method(name) | |
define_method status_method_symbol(name) do | |
try(timestamp_symbol(name)).present? | |
end | |
end | |
def define_change_callbacks(name) | |
define_model_callbacks update_callback_symbol(name), create_callback_symbol(name) | |
end | |
def has_timestamp_column?(name) | |
column_names.include? timestamp_symbol(name).to_s | |
end | |
end | |
included do | |
include SharedMethods | |
after_save :check_for_attribute_changes | |
end | |
def check_for_attribute_changes | |
self.class.tracked_attributes.each do |name, attrs| | |
trigger_callback_if_changed(name, attrs) | |
end | |
end | |
def trigger_callback_if_changed(name, attributes) | |
if attribues_changes_exist?(attributes) | |
trigger_callbacks(name) | |
end | |
end | |
def attribues_changes_exist?(attributes) | |
(attributes & changes.keys.map(&:to_sym)).any? | |
end | |
def trigger_callbacks(name) | |
run_callbacks update_or_create_callback(name) do | |
attempt_timestamp_update(name) | |
end | |
end | |
def update_or_create_callback(name) | |
if self.class.has_timestamp_column?(name) | |
if send(timestamp_symbol(name)) | |
return update_callback_symbol(name) | |
else | |
return create_callback_symbol(name) | |
end | |
end | |
# When there's no column to track we always | |
# consider it an update... | |
update_callback_symbol(name) | |
end | |
def attempt_timestamp_update(name) | |
return nil unless self.class.has_timestamp_column?(name) | |
update_column( | |
timestamp_symbol(name), | |
Time.now | |
) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment