Skip to content

Instantly share code, notes, and snippets.

@spickermann
Last active December 17, 2015 21:49
Show Gist options
  • Save spickermann/5677796 to your computer and use it in GitHub Desktop.
Save spickermann/5677796 to your computer and use it in GitHub Desktop.
Adds support for dirty attributes to a couchbase model. Only selected attributes will act dirty...
# Adds support for dirty attributes to a couchbase model.
#
# class Foo < Couchbase::Model
# include CouchbaseExt::Dirty
# acts_as_dirty_attribute :bar, :blub # only selected attributes will be dirty...
module CouchbaseExt
module Dirty
DIRTY_SUFFIXES = ['_changed?', '_change', '_will_change!', '_was']
def self.included(base)
base.send(:include, ActiveModel::AttributeMethods)
base.attribute_method_suffix *DIRTY_SUFFIXES
base.alias_method_chain :write_attribute, :dirty
base.alias_method_chain :create, :dirty
base.alias_method_chain :save, :dirty
base.send(:extend, ClassMethods)
end
# Do any attributes have unsaved changes?
# person.changed? # => false
# person.name = 'bob'
# person.changed? # => true
def changed?
!changed_attributes.empty?
end
# List of attributes with unsaved changes.
# person.changed # => []
# person.name = 'bob'
# person.changed # => ['name']
def changed
changed_attributes.keys
end
# Map of changed attrs => [original value, new value].
# person.changes # => {}
# person.name = 'bob'
# person.changes # => { 'name' => ['bill', 'bob'] }
def changes
changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
end
def create_with_dirty(*args) #:nodoc:
if status = create_without_dirty(*args)
changed_attributes.clear
end
status
end
# Attempts to +save+ the record and clears changed attributes if successful.
def save_with_dirty(*args) #:nodoc:
if status = save_without_dirty(*args)
changed_attributes.clear
end
status
end
private
def reset_changed_attributes!
changed_attributes.clear
end
# Map of change <tt>attr => original value</tt>.
def changed_attributes
@changed_attributes ||= {}.with_indifferent_access
end
# Handle <tt>*_changed?</tt> for +method_missing+.
def attribute_changed?(attr)
changed_attributes.include?(attr)
end
# Handle <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
end
# Handle <tt>*_was</tt> for +method_missing+.
def attribute_was(attr)
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
end
# Handle <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
changed_attributes[attr] = clone_attribute_value(:read_attribute, attr)
end
# Wrap write_attribute to remember original attribute value.
def write_attribute_with_dirty(attr, value)
if dirty_attributes.include?(attr)
# The attribute already has an unsaved change.
if changed_attributes.include?(attr)
old = changed_attributes[attr]
changed_attributes.delete(attr) if old == value
else
old = clone_attribute_value(:read_attribute, attr)
changed_attributes[attr] = old if old != value
end
end
# Carry on.
write_attribute_without_dirty(attr, value)
end
def clone_attribute_value(reader_method, attribute_name)
value = send(reader_method, attribute_name)
value.duplicable? ? value.clone : value
rescue TypeError, NoMethodError
value
end
module ClassMethods
def self.extended(base)
base.class_attribute :dirty_attributes
base.singleton_class.alias_method_chain(:find, :dirty)
base.singleton_class.alias_method_chain(:find_by_id, :dirty)
end
def find_with_dirty(id)
if obj = find_without_dirty(id)
obj.send :reset_changed_attributes!
end
obj
end
def find_by_id_with_dirty(id)
if obj = find_by_id_without_dirty(id)
obj.send :reset_changed_attributes!
end
obj
end
def acts_as_dirty_attribute(*attr_names)
self.dirty_attributes = attr_names.flatten
define_attribute_methods attr_names.flatten
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment