Class: AttributeKit::AttributeHash

Inherits:
Hash
  • Object
show all
Defined in:
lib/attribute-kit/attribute_hash.rb

Overview

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

Examples:

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.

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ AttributeHash

Creates a new instance, using identical syntax to Hash

See Also:

  • Hash#new


70
71
72
73
74
# File 'lib/attribute-kit/attribute_hash.rb', line 70

def initialize(*args)
  super(*args)
  @dirty_keys = []
  @deleted_keys = []
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#KEY_dirty?Boolean #KEY_deleted?Boolean

Overloads:

  • #KEY_dirty?Boolean
    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)

    Note:

    Uses method_missing to implement the check

    Check whether a particular key is dirty - KEY is a string representation of the key name for the key-value pair being queried

    Returns:

    • (Boolean)

      value indicating key-value pair state

  • #KEY_deleted?Boolean
    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)

    Note:

    Uses method_missing to implement the check

    Check whether a particular key has been deleted - KEY is a string representation of the key name for the key-value pair being queried

    Returns:

    • (Boolean)

      value indicating key-value pair state



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# 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

Instance Method Details

#[]=(k, v) ⇒ Object Also known as: store

Assigns a value to a key

Parameters:

  • k (Object)

    key of key-value pair to insert or change in instance

  • v (Object)

    value of key-value pair to insert or change in instance

Returns:

  • (Object)

    value of key-value pair inserted

See Also:

  • Hash#[]=


81
82
83
84
85
86
87
88
# 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

#clean_attributes(&block) {|dirty_attrs| ... } ⇒ Object

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.

Parameters:

  • block (Block)

    to execute with hash of modified keys, actions, and values

Yields:

  • (dirty_attrs)

    block is executed once, with a hash of dirty attributes

Yield Parameters:

  • dirty_attrs (Hash)

    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

Yield Returns:

  • (Object)

    any value the block returns bubbles up, otherwise

Returns:

  • (Object)

    the return value of the block is returned



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# 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

#clearAttributeHash

Clear all contents of the object and mark it as dirty. An array of all removed keys is available via #deleted_keys.

Returns:

See Also:

  • Hash#clear


195
196
197
198
199
# File 'lib/attribute-kit/attribute_hash.rb', line 195

def clear
  @deleted_keys += self.keys
  @dirty_keys.clear
  super
end

#delete(k) ⇒ Object

Delete a key-value pair

Parameters:

  • key (Object)

    key of key-value pair to delete from instance

Returns:

  • (Object)

    value of key-value pair deleted

See Also:

  • Hash#delete


96
97
98
99
100
# File 'lib/attribute-kit/attribute_hash.rb', line 96

def delete(k)
  @deleted_keys << k
  @dirty_keys.delete(k)
  super
end

#delete_if {|key, value| ... } ⇒ AttributeHash

Delete keys matching an expression in the provided block

Yields:

  • (key, value)

    block is executed for every key-value pair stored in the instance

Yield Parameters:

  • key (Object)

    the key from the key-value pair being evaluated

  • value (Object)

    the value from the key-value pair being evaluated

Yield Returns:

  • (Boolean)

    whether or not to delete a particular stored key-value pair from the instance

Returns:

See Also:

  • Hash#delete_if


142
# File 'lib/attribute-kit/attribute_hash.rb', line 142

cond_deletion_method(:delete_if)

#deleted_keysArray

Returns the set of keys that have been deleted since the AttributeHash was last marked clean.

Returns:

  • (Array)

    all of the deleted keys



216
217
218
219
# File 'lib/attribute-kit/attribute_hash.rb', line 216

def deleted_keys
  @deleted_keys.uniq!
  @deleted_keys
end

#dirty?Boolean

Check whether the contents of the object have changed since the last time clean_attributes was run.

Returns:

  • (Boolean)

    value indicating whether or not key-value pairs have been added, changed, or deleted



203
204
205
# File 'lib/attribute-kit/attribute_hash.rb', line 203

def dirty?
  !(@dirty_keys.empty? && @deleted_keys.empty?)
end

#dirty_keysArray

Returns the set of keys that have been modified since the AttributeHash was last marked clean.

Returns:

  • (Array)

    all of the changed keys



209
210
211
212
# File 'lib/attribute-kit/attribute_hash.rb', line 209

def dirty_keys
  @dirty_keys.uniq!
  @dirty_keys + self.deleted_keys
end

#keep_if {|key, value| ... } ⇒ AttributeHash

Delete keys not matching an expression in the provided block

Yields:

  • (key, value)

    block is executed for every key-value pair stored in the instance

Yield Parameters:

  • key (Object)

    the key from the key-value pair being evaluated

  • value (Object)

    the value from the key-value pair being evaluated

Yield Returns:

  • (Boolean)

    whether or not to keep a particular stored key-value pair in the instance

Returns:

See Also:

  • Hash#keep_if


132
# File 'lib/attribute-kit/attribute_hash.rb', line 132

cond_deletion_method(:keep_if)

#merge!(other_hash) {|key, oldval, newval| ... } ⇒ AttributeHash Also known as: update

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

Parameters:

  • other_hash (Hash)

    hash of values to merge in to the instance

Yields:

  • (key, oldval, newval)

    block is executed for every duplicate key between the instance and other_hash

Yield Parameters:

  • key (Object)

    the key being evaluated

  • oldval (Object)

    the value from the value from the instance

  • newval (Object)

    the value from the value from other_hash

Yield Returns:

  • (Object)

    the value to store for the key in question

Returns:

See Also:

  • Hash#merge!


168
169
170
171
172
173
174
175
176
177
178
179
# 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

#reject! {|key, value| ... } ⇒ AttributeHash, Nil

Delete keys matching an expression in the provided block

Yields:

  • (key, value)

    block is executed for every key-value pair stored in the instance

Yield Parameters:

  • key (Object)

    the key from the key-value pair being evaluated

  • value (Object)

    the value from the key-value pair being evaluated

Yield Returns:

  • (Boolean)

    whether or not to delete a particular stored key-value pair from the instance

Returns:

  • (AttributeHash)

    if changes are made

  • (Nil)

    if no changes are made

See Also:

  • Hash#reject!


111
# File 'lib/attribute-kit/attribute_hash.rb', line 111

cond_deletion_method(:reject!)

#replace(other_hash) ⇒ AttributeHash

Replace the contents of this object with the contents of the supplied hash

Parameters:

  • other_hash (Hash)

    hash of values to replace instance contents with

Returns:

See Also:

  • Hash#replace


148
149
150
151
152
153
154
155
# 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

#select! {|key, value| ... } ⇒ AttributeHash, Nil

Delete keys not matching an expression in the provided block

Yields:

  • (key, value)

    block is executed for every key-value pair stored in the instance

Yield Parameters:

  • key (Object)

    the key from the key-value pair being evaluated

  • value (Object)

    the value from the key-value pair being evaluated

Yield Returns:

  • (Boolean)

    whether or not to keep a particular stored key-value pair in the instance

Returns:

  • (AttributeHash)

    if changes are made

  • (Nil)

    if no changes are made

See Also:

  • Hash#select!


122
# File 'lib/attribute-kit/attribute_hash.rb', line 122

cond_deletion_method(:select!)

#shiftArray

Returns a key-value pair from the instance and deletes it.

Returns:

  • (Array)

    key-value pair

See Also:

  • Hash#shift


186
187
188
189
190
# File 'lib/attribute-kit/attribute_hash.rb', line 186

def shift
  (k,v) = super
  @deleted_keys << k
  [k,v]
end