class AttributeKit::AttributeHash

AttributeHash inherits from and extends Hash, to provide tracking of attribute status (changed/deleted keys).

@example Basic usage

attributes = AttributeKit::AttributeHash.new              #=> {}

attributes.empty?                                         #=> true
attributes.dirty?                                         #=> false
attributes[:foo] = 'bar'                                  #=> 'bar'
attributes.dirty?                                         #=> true
attributes.dirty_keys                                     #=> [:foo]
attributes                                                #=> {:foo=>"bar"}

attributes[:bar] = 5                                      #=> 5
attributes                                                #=> {:foo=>"bar", :bar=>5}
attributes.dirty_keys                                     #=> [:foo, :bar]
attributes.deleted_keys                                   #=> []

attributes.delete(:foo)                                   #=> "bar"
attributes.dirty_keys                                     #=> [:bar, :foo]
attributes.deleted_keys                                   #=> [:foo]

attributes.clean_attributes { |dirty_attrs|               # Deleted: foo    Nil value: true
  dirty_attrs.each_pair do |k,v|                          # Changed: bar    New value: 5
    case v[0]
      when :changed                                       #=> {:foo=>[:deleted, nil], :bar=>[:changed, 5]}
        puts "Changed: #{k}    New value: #{v[1]}"
      when :deleted                                       # NOTE: The lack of a return value in this block
        puts "Deleted: #{k}    Nil value: #{v[1].nil?}"   #       means that dirty_attrs was returned by both
    end                                                   #       the block and the method itself.  You may
  end                                                     #       want to write a block to return true if it
}                                                         #       succeeds, and the dirty_attrs hash if it
                                                          #       fails, so you can pass the hash to a
                                                          #       method that can retry later.

Public Class Methods

new(*args) click to toggle source

Creates a new instance, using identical syntax to Hash @return [AttributeHash] new instance @see Hash#new

Calls superclass method
# File lib/attribute-kit/attribute_hash.rb, line 70
def initialize(*args)
  super(*args)
  @dirty_keys = []
  @deleted_keys = []
end

Private Class Methods

cond_deletion_method(method_name) click to toggle source

@visibility private

Calls superclass method
# File lib/attribute-kit/attribute_hash.rb, line 52
def self.cond_deletion_method(method_name)
  define_method(method_name) { |&block|
    unless block.nil?
      keys = self.keys
      r = super(&block)
      @deleted_keys += keys - self.keys
      r
    else
      super()
    end
  }
end

Public Instance Methods

[]=(k,v) click to toggle source

Assigns a value to a key @param [Object] k key of key-value pair to insert or change in instance @param [Object] v value of key-value pair to insert or change in instance @return [Object] value of key-value pair inserted @see Hash#[]=

Calls superclass method
# File lib/attribute-kit/attribute_hash.rb, line 81
def []=(k,v)
  if self[k].eql? v
    v
  else
    @dirty_keys << k
    super
  end
end
Also aliased as: store
clean_attributes(&block) click to toggle source

Calls a block with a hash of all keys, actions (:changed or :deleted), and current values (if :changed) of keys that have changed since the object was last marked clean. Marks the object as clean when it compiles the list of keys that have been modified. @param [Block] block to execute with hash of modified keys, actions, and values @yield [dirty_attrs] block is executed once, with a hash of dirty attributes @yieldparam [Hash] dirty_attrs the hash of changed/deleted attributes with the modified key as the key and a value

of an array in the format: [ACTION, VALUE] where ACTION is either :changed or :deleted, and VALUE is either the
new value or nil if the attribute is deleted.  A nil value does NOT mean the attribute is deleted if the ACTION
is :changed, it means the value was actually set to nil

@yieldreturn [Object] any value the block returns bubbles up, otherwise @return [Object] the return value of the block is returned

# File lib/attribute-kit/attribute_hash.rb, line 232
def clean_attributes(&block)
  if !@dirty_keys.empty?
    dirty_attrs = {}
    @dirty_keys.uniq!
    dirty = @dirty_keys.dup
    @dirty_keys.clear
    deleted = @deleted_keys.dup
    @deleted_keys.clear

    while dirty.length > 0 do
      key = dirty.shift
      dirty_attrs[key] = [:changed, self[key]]
    end

    while deleted.length > 0 do
      key = deleted.shift
      dirty_attrs[key] = [:deleted, nil]
    end

    block.call(dirty_attrs)
  end
end
clear() click to toggle source

Clear all contents of the object and mark it as dirty. An array of all removed keys is available via deleted_keys. @return [AttributeHash] an empty AttributeHash @see Hash#clear

Calls superclass method
# File lib/attribute-kit/attribute_hash.rb, line 195
def clear
  @deleted_keys += self.keys
  @dirty_keys.clear
  super
end
delete(k) click to toggle source

Delete a key-value pair @param [Object] key key of key-value pair to delete from instance @return [Object] value of key-value pair deleted @see Hash#delete

Calls superclass method
# File lib/attribute-kit/attribute_hash.rb, line 96
def delete(k)
  @deleted_keys << k
  @dirty_keys.delete(k)
  super
end
deleted_keys() click to toggle source

Returns the set of keys that have been deleted since the AttributeHash was last marked clean. @return [Array] all of the deleted keys

# File lib/attribute-kit/attribute_hash.rb, line 216
def deleted_keys
  @deleted_keys.uniq!
  @deleted_keys
end
dirty?() click to toggle source

Check whether the contents of the object have changed since the last time clean_attributes was run. @return [Boolean] value indicating whether or not key-value pairs have been added, changed, or deleted

# File lib/attribute-kit/attribute_hash.rb, line 203
def dirty?
  !(@dirty_keys.empty? && @deleted_keys.empty?)
end
dirty_keys() click to toggle source

Returns the set of keys that have been modified since the AttributeHash was last marked clean. @return [Array] all of the changed keys

# File lib/attribute-kit/attribute_hash.rb, line 209
def dirty_keys
  @dirty_keys.uniq!
  @dirty_keys + self.deleted_keys
end
merge!(other_hash, &block) click to toggle source

Combine the contents of this object with the contents of the supplied hash, calling an optional supplied block to determine what value is used when there are duplicate keys. Without the block, values from the supplied hash will be used in the case of duplicate keys @param [Hash] other_hash hash of values to merge in to the instance @yield [key, oldval, newval] block is executed for every duplicate key between the instance and other_hash @yieldparam [Object] key the key being evaluated @yieldparam [Object] oldval the value from the value from the instance @yieldparam [Object] newval the value from the value from other_hash @yieldreturn [Object] the value to store for the key in question @return [AttributeHash] self with changes applied @see Hash#merge!

Calls superclass method
# File lib/attribute-kit/attribute_hash.rb, line 168
def merge!(other_hash, &block)
  old_keys = self.keys
  overlapping_keys = old_keys.dup.keep_if {|v| other_hash.keys.include?(v)}
  r = super
  if block.nil?
    @dirty_keys += (self.keys - old_keys) + overlapping_keys
  else
    new_values = other_hash.keep_if {|k,v| overlapping_keys.include?(k)}
    @dirty_keys += (self.keys - old_keys) + (new_values.keep_if {|k,v| !self[k].eql?(v) }).keys
  end
  r
end
Also aliased as: update
method_missing(method, *args, &block) click to toggle source

@overload KEY_dirty?()

Check whether a particular key is dirty - KEY is a string representation of the key name for the key-value pair being queried
@note There can be conflicts if you have multiple keys that are similar, i.e. :blue and 'blue', so only use this
  when you can guarantee homogenous keys and are using either strings or symbols for the key (the only cases where
  it will work)
@return [Boolean] value indicating key-value pair state
@note Uses method_missing to implement the check

@overload KEY_deleted?()

Check whether a particular key has been deleted - KEY is a string representation of the key name for the key-value pair being queried
@note There can be conflicts if you have multiple keys that are similar, i.e. :blue and 'blue', so only use this
  when you can guarantee homogenous keys and are using either strings or symbols for the key (the only cases where
  it will work)
@return [Boolean] value indicating key-value pair state
@note Uses method_missing to implement the check
Calls superclass method
# File lib/attribute-kit/attribute_hash.rb, line 270
def method_missing(method, *args, &block)
  method_name = method.to_s
  case method_name
    when /(.*)_dirty?/
      return @dirty_keys.include?($1) if self.has_key?($1)
      return @dirty_keys.include?($1.to_sym) if self.has_key?($1.to_sym)
      @deleted_keys.include?($1) || @deleted_keys.include?($1.to_sym)
    when /(.*)_deleted?/
      @deleted_keys.include?($1) || @deleted_keys.include?($1.to_sym)
    else
      if self.class.superclass.instance_methods.map(&:to_sym).include?(:method_mising)
        super(method, *args, &block)
      else
        raise NoMethodError.new("undefined method '#{method_name}' for #{self}")
      end
  end
end
replace(other_hash) click to toggle source

Replace the contents of this object with the contents of the supplied hash @param [Hash] other_hash hash of values to replace instance contents with @return [AttributeHash] self with changes applied @see Hash#replace

Calls superclass method
# File lib/attribute-kit/attribute_hash.rb, line 148
def replace(other_hash)
  old_keys = self.keys
  r = super
  new_keys = self.keys
  @dirty_keys = new_keys
  @deleted_keys += (old_keys - new_keys)
  r
end
shift() click to toggle source

Returns a key-value pair from the instance and deletes it. @return [Array] key-value pair @see Hash#shift

Calls superclass method
# File lib/attribute-kit/attribute_hash.rb, line 186
def shift
  (k,v) = super
  @deleted_keys << k
  [k,v]
end
store(k,v)
Alias for: []=
update(other_hash, &block)
Alias for: merge!