Class: AeEasy::Core::SmartCollection

Inherits:
Array
  • Object
show all
Defined in:
lib/ae_easy/core/smart_collection.rb

Overview

Smart collection capable to avoid duplicates on insert by matching id

defined fields along events.

Constant Summary collapse

EVENTS =

Implemented event list.

[
  :before_defaults,
  :before_match,
  :before_insert,
  :after_insert
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key_fields, opts = {}) ⇒ SmartCollection

Note:

Defaults will apply to missing fields and null values only.

Initialize collection

Examples:

With default values.

count = 0
defaults = {
  'id' => lambda{|item| count += 1},
  'aaa' => 111,
  'bbb' => proc{|item| item['ccc'].nil? ? 'No ccc' : 'Has ccc'}
}
values = [
  {'aaa' => 'Defaults apply on nil values only', 'bbb' => nil},
  {'ccc' => 'ddd'},
  {'id' => 'abc123'}
]
new_item = {'bbb' => 'Look mom! no ccc'}
collection = SmartCollection.new ['id'], defaults: defaults
collection << new_item
collection
# => [
#   {'id' => 1, 'aaa' => 'Defaults apply on nil values only', 'bbb' => 'No ccc'},
#   {'id' => 2, 'aaa' => 111, 'bbb' => 'Has ccc', 'ccc' => 'ddd'},
#   {'id' => 'abc123', 'aaa' => 111, 'bbb' => 'No ccc'},
#   {'id' => 3, 'aaa' => 111, 'bbb' => 'Look mom! no ccc'}
# ]

Parameters:

  • key_fields (Array)

    Key fields, analog to primary keys.

  • opts (Hash) (defaults to: {})

    ({}) Configuration options.

Options Hash (opts):

  • :values (Array) — default: []

    Initial values; will avoid duplicates on insert.

  • :defaults (Hash) — default: {}

    Default values. `proc` values will be executed to get default value.



50
51
52
53
54
55
# File 'lib/ae_easy/core/smart_collection.rb', line 50

def initialize key_fields, opts = {}
  @key_fields = key_fields || []
  @defaults = opts[:defaults] || {}
  super 0
  (opts[:values] || []).each{|item| self << item}
end

Instance Attribute Details

#defaultsObject (readonly)

Default fields values. Apply to missing fields and null values.



17
18
19
# File 'lib/ae_easy/core/smart_collection.rb', line 17

def defaults
  @defaults
end

#key_fieldsObject (readonly)

Key fields, analog to primary keys.



15
16
17
# File 'lib/ae_easy/core/smart_collection.rb', line 15

def key_fields
  @key_fields
end

Instance Method Details

#<<(item) ⇒ Object

Add/remplace an item avoiding duplicates



224
225
226
227
228
229
230
231
232
233
# File 'lib/ae_easy/core/smart_collection.rb', line 224

def << item
  item = call_event :before_defaults, item, item
  apply_defaults item
  item = call_event :before_match, item, item
  match = find_match item
  item = call_event :before_insert, item, item, match
  delete match unless match.nil?
  result = super(item)
  call_event :after_insert, result, item, match
end

#apply_defaults(item) ⇒ Hash

Apply default values into item.

Parameters:

  • item (Hash)

    Item to apply defaults.

Returns:

  • (Hash)

    Item



203
204
205
206
207
208
# File 'lib/ae_easy/core/smart_collection.rb', line 203

def apply_defaults item
  defaults.each do |key, value|
    next unless item[key].nil?
    item[key] = value.respond_to?(:call) ? value.call(item) : value
  end
end

#bind_event(key, &block) ⇒ Object

Note:

Some events will expect a return value to replace item on insertion:

  • `before_match`

  • `before_defaults`

  • `before_insert`

Add event binding by key and block.

Examples:

before_defaults

defaults = {'aaa' => 111}
collection = SmartCollection.new [],
  defaults: defaults
collection.bind_event(:before_defaults) do |collection, item|
  puts collection
  # => []
  puts item
  # => {'bbb' => 222}

  # Sending the item back is required, or a new one
  #   in case you want to replace item to insert.
  item
end
data << {'bbb' => 222}
data
# => [{'aaa' => 111, 'bbb' => 222}]

before_match

keys = ['id']
defaults = {'aaa' => 111}
values = [
  {'id' => 1, 'ccc' => 333}
]
collection = SmartCollection.new keys,
  defaults: defaults
  values: values
collection.bind_event(:before_match) do |collection, item|
  puts collection
  # => [{'id' => 1, 'aaa' => 111, 'ccc' => 333}]
  puts item
  # => {'id' => 1, 'aaa' => 111, 'bbb' => 222}

  # Sending the item back is required, or a new one
  #   in case you want to replace item to insert.
  item
end
data << {'id' => 1, 'bbb' => 222}
data
# => [{'id' => 1, 'aaa' => 111, 'bbb' => 222}]

before_insert

keys = ['id']
defaults = {'aaa' => 111}
values = [
  {'id' => 1, 'ccc' => 333}
]
collection = SmartCollection.new keys,
  defaults: defaults
  values: values
collection.bind_event(:before_insert) do |collection, item, match|
  puts collection
  # => [{'id' => 1, 'aaa' => 111, 'ccc' => 333}]
  puts item
  # => {'id' => 1, 'aaa' => 111, 'bbb' => 222}
  puts match
  # => {'id' => 1, 'aaa' => 111, 'ccc' => 333}

  # Sending the item back is required, or a new one
  #   in case you want to replace item to insert.
  item
end
data << {'id' => 1, 'bbb' => 222}
data
# => [{'id' => 1, 'aaa' => 111, 'bbb' => 222}]

after_insert

keys = ['id']
defaults = {'aaa' => 111}
values = [
  {'id' => 1, 'ccc' => 333}
]
collection = SmartCollection.new keys,
  defaults: defaults
  values: values
collection.bind_event(:after_insert) do |collection, item, match|
  puts collection
  # => [{'id' => 1, 'aaa' => 111, 'bbb' => 222}]
  puts item
  # => {'id' => 1, 'aaa' => 111, 'bbb' => 222}
  puts match
  # => {'id' => 1, 'aaa' => 111, 'ccc' => 333}
  # No need to send item back since it is already inserted
end
data << {'id' => 1, 'bbb' => 222}
data
# => [{'id' => 1, 'aaa' => 111, 'bbb' => 222}]

Parameters:

  • key (Symbol)

    Event name.

Raises:

  • (ArgumentError)

    When unknown event key.



161
162
163
164
165
166
# File 'lib/ae_easy/core/smart_collection.rb', line 161

def bind_event key, &block
  unless EVENTS.include? key
    raise ArgumentError.new("Unknown event '#{key}'")
  end
  (events[key] ||= []) << block
end

#call_event(key, default = nil, *args) ⇒ Object

Call an event

Parameters:

  • key (Symbol)

    Event name.

  • default (defaults to: nil)

    Detault return value when event's return nil.

  • args

    event arguments.



174
175
176
177
178
179
# File 'lib/ae_easy/core/smart_collection.rb', line 174

def call_event key, default = nil, *args
  return default if events[key].nil?
  result = nil
  events[key].each{|event| result = event.call self, *args}
  result.nil? ? default : result
end

#eventsObject

Asigned events.



59
60
61
# File 'lib/ae_easy/core/smart_collection.rb', line 59

def events
  @events ||= {}
end

#find_match(filter) ⇒ Hash|nil

Note:

Warning: It uses table scan to filter and will be slow.

Find an item by matching filter keys

Parameters:

  • filter (Hash)

Returns:

  • (Hash|nil)

    First existing item match or nil when no match.



217
218
219
220
221
# File 'lib/ae_easy/core/smart_collection.rb', line 217

def find_match filter
  self.find do |item|
    match_keys? item, filter
  end
end

#match_keys?(item_a, item_b) ⇒ Boolean

Check whenever two items keys match.

Parameters:

  • item_a (Hash)

    Item to match.

  • item_b (Hash)

    Item to match.

Returns:

  • (Boolean)


187
188
189
190
191
192
193
194
195
# File 'lib/ae_easy/core/smart_collection.rb', line 187

def match_keys? item_a, item_b
  return false if key_fields.nil? || key_fields.count < 1
  return true if item_a.nil? && item_b.nil?
  return false if item_a.nil? || item_b.nil?
  key_fields.each do |key|
    return false if item_a[key] != item_b[key]
  end
  true
end