dh_easy-core 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +7 -0
- data/.yardopts +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.md +20 -0
- data/Rakefile +22 -0
- data/dh_easy-core.gemspec +50 -0
- data/doc/DhEasy.html +117 -0
- data/doc/DhEasy/Core.html +1590 -0
- data/doc/DhEasy/Core/Config.html +311 -0
- data/doc/DhEasy/Core/Exception.html +117 -0
- data/doc/DhEasy/Core/Exception/OutdatedError.html +135 -0
- data/doc/DhEasy/Core/Helper.html +117 -0
- data/doc/DhEasy/Core/Helper/Cookie.html +1070 -0
- data/doc/DhEasy/Core/Mock.html +282 -0
- data/doc/DhEasy/Core/Mock/FakeDb.html +3779 -0
- data/doc/DhEasy/Core/Mock/FakeExecutor.html +3289 -0
- data/doc/DhEasy/Core/Mock/FakeFinisher.html +160 -0
- data/doc/DhEasy/Core/Mock/FakeParser.html +160 -0
- data/doc/DhEasy/Core/Mock/FakeSeeder.html +160 -0
- data/doc/DhEasy/Core/Plugin.html +117 -0
- data/doc/DhEasy/Core/Plugin/CollectionVault.html +299 -0
- data/doc/DhEasy/Core/Plugin/ConfigBehavior.html +541 -0
- data/doc/DhEasy/Core/Plugin/ContextIntegrator.html +445 -0
- data/doc/DhEasy/Core/Plugin/Executor.html +259 -0
- data/doc/DhEasy/Core/Plugin/ExecutorBehavior.html +344 -0
- data/doc/DhEasy/Core/Plugin/Finisher.html +265 -0
- data/doc/DhEasy/Core/Plugin/FinisherBehavior.html +142 -0
- data/doc/DhEasy/Core/Plugin/InitializeHook.html +220 -0
- data/doc/DhEasy/Core/Plugin/Parser.html +270 -0
- data/doc/DhEasy/Core/Plugin/ParserBehavior.html +235 -0
- data/doc/DhEasy/Core/Plugin/Seeder.html +674 -0
- data/doc/DhEasy/Core/Plugin/SeederBehavior.html +142 -0
- data/doc/DhEasy/Core/SmartCollection.html +1087 -0
- data/doc/_index.html +364 -0
- data/doc/class_list.html +51 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +58 -0
- data/doc/css/style.css +496 -0
- data/doc/file.README.html +91 -0
- data/doc/file_list.html +56 -0
- data/doc/frames.html +17 -0
- data/doc/index.html +91 -0
- data/doc/js/app.js +303 -0
- data/doc/js/full_list.js +216 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +939 -0
- data/doc/top-level-namespace.html +110 -0
- data/lib/dh_easy/core.rb +257 -0
- data/lib/dh_easy/core/config.rb +27 -0
- data/lib/dh_easy/core/exception.rb +8 -0
- data/lib/dh_easy/core/exception/outdated_error.rb +9 -0
- data/lib/dh_easy/core/helper.rb +8 -0
- data/lib/dh_easy/core/helper/cookie.rb +209 -0
- data/lib/dh_easy/core/mock.rb +45 -0
- data/lib/dh_easy/core/mock/fake_db.rb +561 -0
- data/lib/dh_easy/core/mock/fake_executor.rb +373 -0
- data/lib/dh_easy/core/mock/fake_finisher.rb +28 -0
- data/lib/dh_easy/core/mock/fake_parser.rb +33 -0
- data/lib/dh_easy/core/mock/fake_seeder.rb +28 -0
- data/lib/dh_easy/core/plugin.rb +19 -0
- data/lib/dh_easy/core/plugin/collection_vault.rb +23 -0
- data/lib/dh_easy/core/plugin/config_behavior.rb +43 -0
- data/lib/dh_easy/core/plugin/context_integrator.rb +60 -0
- data/lib/dh_easy/core/plugin/executor.rb +19 -0
- data/lib/dh_easy/core/plugin/executor_behavior.rb +32 -0
- data/lib/dh_easy/core/plugin/finisher.rb +19 -0
- data/lib/dh_easy/core/plugin/finisher_behavior.rb +9 -0
- data/lib/dh_easy/core/plugin/initialize_hook.rb +17 -0
- data/lib/dh_easy/core/plugin/parser.rb +19 -0
- data/lib/dh_easy/core/plugin/parser_behavior.rb +17 -0
- data/lib/dh_easy/core/plugin/seeder.rb +44 -0
- data/lib/dh_easy/core/plugin/seeder_behavior.rb +9 -0
- data/lib/dh_easy/core/smart_collection.rb +236 -0
- data/lib/dh_easy/core/version.rb +6 -0
- metadata +249 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'dh_easy/core/plugin/initialize_hook'
|
2
|
+
require 'dh_easy/core/plugin/context_integrator'
|
3
|
+
require 'dh_easy/core/plugin/collection_vault'
|
4
|
+
require 'dh_easy/core/plugin/config_behavior'
|
5
|
+
require 'dh_easy/core/plugin/executor_behavior'
|
6
|
+
require 'dh_easy/core/plugin/executor'
|
7
|
+
require 'dh_easy/core/plugin/parser_behavior'
|
8
|
+
require 'dh_easy/core/plugin/parser'
|
9
|
+
require 'dh_easy/core/plugin/seeder_behavior'
|
10
|
+
require 'dh_easy/core/plugin/seeder'
|
11
|
+
require 'dh_easy/core/plugin/finisher_behavior'
|
12
|
+
require 'dh_easy/core/plugin/finisher'
|
13
|
+
|
14
|
+
module DhEasy
|
15
|
+
module Core
|
16
|
+
module Plugin
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module DhEasy
|
2
|
+
module Core
|
3
|
+
module Plugin
|
4
|
+
module CollectionVault
|
5
|
+
# Stored collections info as hash.
|
6
|
+
def collections
|
7
|
+
@collections ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
# Add a new collection
|
11
|
+
#
|
12
|
+
# @param [Symbol] key Collection key used to lookup for collection name.
|
13
|
+
# @param [String] name Collection name used on outputs.
|
14
|
+
def add_collection key, name
|
15
|
+
if collections.has_key? key
|
16
|
+
raise "Can't add \"#{key}\" collection, it already exists!"
|
17
|
+
end
|
18
|
+
collections[key] = name
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module DhEasy
|
2
|
+
module Core
|
3
|
+
module Plugin
|
4
|
+
module ConfigBehavior
|
5
|
+
include DhEasy::Core::Plugin::ContextIntegrator
|
6
|
+
include DhEasy::Core::Plugin::CollectionVault
|
7
|
+
|
8
|
+
attr_reader :config_collection_key
|
9
|
+
|
10
|
+
# Hook to map config behavior on self
|
11
|
+
#
|
12
|
+
# @param [Hash] opts ({}) Configuration options.
|
13
|
+
# @option opts [Array] :config_collection ([:config, 'config']) Key value pair array to se a custom collection.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# initialize_hook_core_config_behavior config_collection: [:my_config, 'abc']
|
17
|
+
# config_collection
|
18
|
+
# # => 'abc'
|
19
|
+
def initialize_hook_core_config_behavior opts = {}
|
20
|
+
@config_collection_key, collection = opts[:config_collection] || [:config, 'config']
|
21
|
+
add_collection config_collection_key, collection
|
22
|
+
end
|
23
|
+
|
24
|
+
# Get config collection name.
|
25
|
+
# @return [String]
|
26
|
+
def config_collection
|
27
|
+
collections[config_collection_key]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Find a configuration value by item key.
|
31
|
+
#
|
32
|
+
# @param [Symbol] key Item key to find.
|
33
|
+
#
|
34
|
+
# @note Instance must implement:
|
35
|
+
# * `find_output(collection, query)`
|
36
|
+
def find_config key
|
37
|
+
value = find_output config_collection, '_id' => key
|
38
|
+
value ||= {'_collection' => config_collection, '_id' => key}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module DhEasy
|
2
|
+
module Core
|
3
|
+
module Plugin
|
4
|
+
module ContextIntegrator
|
5
|
+
# Last mocked ontext object.
|
6
|
+
attr_reader :context
|
7
|
+
|
8
|
+
# Mock a context methods into self.
|
9
|
+
#
|
10
|
+
# @param origin Object that represents the context to mock.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# class MyContext
|
14
|
+
# attr_accessor :message
|
15
|
+
# def initialize
|
16
|
+
# message = 'Hello world!'
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# def hello_world
|
20
|
+
# message
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# class Foo
|
25
|
+
# include ContextIntegrator
|
26
|
+
#
|
27
|
+
# def hello_person
|
28
|
+
# 'Hello person!'
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# context = MyContext.new
|
33
|
+
# my_object = Foo.new
|
34
|
+
# my_object.mock_context context
|
35
|
+
#
|
36
|
+
# puts my_object.hello_world
|
37
|
+
# # => 'Hello world!'
|
38
|
+
# puts my_object.hello_person
|
39
|
+
# # => 'Hello person!'
|
40
|
+
#
|
41
|
+
# context.message = 'Hello world again!'
|
42
|
+
# puts my_object.hello_world
|
43
|
+
# # => 'Hello world again!
|
44
|
+
def mock_context origin
|
45
|
+
@context = origin
|
46
|
+
DhEasy::Core.mock_instance_methods context, self
|
47
|
+
end
|
48
|
+
|
49
|
+
# Hook to mock context on initialize.
|
50
|
+
#
|
51
|
+
# @param [Hash] opts ({}) Configuration options.
|
52
|
+
# @option opts :context Object that represents the context to mock.
|
53
|
+
def initialize_hook_core_context_integrator opts = [{}]
|
54
|
+
raise ':context object is required.' if opts[:context].nil?
|
55
|
+
mock_context opts[:context]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module DhEasy
|
2
|
+
module Core
|
3
|
+
module Plugin
|
4
|
+
module Executor
|
5
|
+
include DhEasy::Core::Plugin::InitializeHook
|
6
|
+
include DhEasy::Core::Plugin::ExecutorBehavior
|
7
|
+
|
8
|
+
# Initialize hooks.
|
9
|
+
#
|
10
|
+
# @param [Hash] opts ({}) Configuration options.
|
11
|
+
#
|
12
|
+
# @see DhEasy::Core::Plugin::ContextIntegrator#initialize_hook_core_context_integrator
|
13
|
+
def initialize opts = {}
|
14
|
+
initialize_hooks opts
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module DhEasy
|
2
|
+
module Core
|
3
|
+
module Plugin
|
4
|
+
module ExecutorBehavior
|
5
|
+
include DhEasy::Core::Plugin::ContextIntegrator
|
6
|
+
|
7
|
+
# Enqueue a single/multiple pages for fetch. Analog to `save_pages`.
|
8
|
+
#
|
9
|
+
# @param [Array,Hash] object Pages to save being Hash when single and
|
10
|
+
# Array when many.
|
11
|
+
#
|
12
|
+
# @note Instance must implement:
|
13
|
+
# * `save_pages(pages)`
|
14
|
+
def enqueue object
|
15
|
+
object = [object] unless object.is_a? Array
|
16
|
+
save_pages object
|
17
|
+
end
|
18
|
+
|
19
|
+
# Save a single/multiple outputs. Analog to `save_outputs`.
|
20
|
+
#
|
21
|
+
# @param [Array,Hash] object Outputs to save being Hash when single and Array when many.
|
22
|
+
#
|
23
|
+
# @note Instance must implement:
|
24
|
+
# * `save_outputs(outputs)`
|
25
|
+
def save object
|
26
|
+
object = [object] unless object.is_a? Array
|
27
|
+
save_outputs object
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module DhEasy
|
2
|
+
module Core
|
3
|
+
module Plugin
|
4
|
+
module Finisher
|
5
|
+
include DhEasy::Core::Plugin::InitializeHook
|
6
|
+
include DhEasy::Core::Plugin::FinisherBehavior
|
7
|
+
|
8
|
+
# Initialize finisher and hooks.
|
9
|
+
#
|
10
|
+
# @param [Hash] opts ({}) Configuration options.
|
11
|
+
#
|
12
|
+
# @see DhEasy::Core::Plugin::ContextIntegrator#initialize_hook_core_context_integrator
|
13
|
+
def initialize opts = {}
|
14
|
+
initialize_hooks opts
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module DhEasy
|
2
|
+
module Core
|
3
|
+
module Plugin
|
4
|
+
module InitializeHook
|
5
|
+
# Execute all methods with `initilaize_hook_` prefix (hooks).
|
6
|
+
#
|
7
|
+
# @param [Hash] opts ({}) Configuration options sent to all hooks.
|
8
|
+
def initialize_hooks opts = {}
|
9
|
+
initializers = self.methods.select{|i|i.to_s =~ /^initialize_hook_/}
|
10
|
+
initializers.each do |method|
|
11
|
+
self.send method, opts
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module DhEasy
|
2
|
+
module Core
|
3
|
+
module Plugin
|
4
|
+
module Parser
|
5
|
+
include DhEasy::Core::Plugin::InitializeHook
|
6
|
+
include DhEasy::Core::Plugin::ParserBehavior
|
7
|
+
|
8
|
+
# Initialize parser and hooks.
|
9
|
+
#
|
10
|
+
# @param [Hash] opts ({}) Configuration options.
|
11
|
+
#
|
12
|
+
# @see DhEasy::Core::Plugin::ContextIntegrator#initialize_hook_core_context_integrator
|
13
|
+
def initialize opts = {}
|
14
|
+
initialize_hooks opts
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module DhEasy
|
2
|
+
module Core
|
3
|
+
module Plugin
|
4
|
+
module ParserBehavior
|
5
|
+
include DhEasy::Core::Plugin::ExecutorBehavior
|
6
|
+
|
7
|
+
# Alias to `page['vars']`.
|
8
|
+
#
|
9
|
+
# @note Instance must implement:
|
10
|
+
# * `page`
|
11
|
+
def vars
|
12
|
+
page['vars']
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module DhEasy
|
2
|
+
module Core
|
3
|
+
module Plugin
|
4
|
+
module Seeder
|
5
|
+
include DhEasy::Core::Plugin::InitializeHook
|
6
|
+
include DhEasy::Core::Plugin::SeederBehavior
|
7
|
+
|
8
|
+
# Root input directory path.
|
9
|
+
# @return [String]
|
10
|
+
attr_accessor :root_input_dir
|
11
|
+
|
12
|
+
# Referer to use on page seeding.
|
13
|
+
# @return [String]
|
14
|
+
attr_accessor :referer
|
15
|
+
|
16
|
+
# Cookie to use on page seeing.
|
17
|
+
# @return [String]
|
18
|
+
attr_accessor :cookie
|
19
|
+
|
20
|
+
# Hook to initialize seeder object.
|
21
|
+
#
|
22
|
+
# @param [Hash] opts ({}) Configuration options.
|
23
|
+
# @option opts [String] :root_input_dir (nil) Root directory for inputs.
|
24
|
+
# @option opts [String] :referer (nil) New pages referer, useful to dynamic setups.
|
25
|
+
# @option opts [String] :cookie (nil) Cookie to use on seeded pages fetchs.
|
26
|
+
def initialize_hook_core_seeder opts = {}
|
27
|
+
@root_input_dir = opts[:root_input_dir]
|
28
|
+
@referer = opts[:referer]
|
29
|
+
@cookie = opts[:cookie]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Initialize seeder and hooks.
|
33
|
+
#
|
34
|
+
# @param [Hash] opts ({}) Configuration options.
|
35
|
+
#
|
36
|
+
# @see DhEasy::Core::Plugin::ContextIntegrator#initialize_hook_core_context_integrator
|
37
|
+
# @see #initialize_hook_core_seeder
|
38
|
+
def initialize opts = {}
|
39
|
+
initialize_hooks opts
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
module DhEasy
|
2
|
+
module Core
|
3
|
+
# Smart collection capable to avoid duplicates on insert by matching id
|
4
|
+
# defined fields along events.
|
5
|
+
class SmartCollection < Array
|
6
|
+
# Implemented event list.
|
7
|
+
EVENTS = [
|
8
|
+
:before_defaults,
|
9
|
+
:before_match,
|
10
|
+
:before_insert,
|
11
|
+
:after_insert
|
12
|
+
]
|
13
|
+
|
14
|
+
# Key fields, analog to primary keys.
|
15
|
+
attr_reader :key_fields
|
16
|
+
# Default fields values. Apply to missing fields and null values.
|
17
|
+
attr_reader :defaults
|
18
|
+
|
19
|
+
# Initialize collection
|
20
|
+
#
|
21
|
+
# @param [Array] key_fields Key fields, analog to primary keys.
|
22
|
+
# @param [Hash] opts ({}) Configuration options.
|
23
|
+
# @option opts [Array] :values ([]) Initial values; will avoid duplicates on insert.
|
24
|
+
# @option opts [Hash] :defaults ({}) Default values. `proc` values will be executed to get default value.
|
25
|
+
#
|
26
|
+
# @example With default values.
|
27
|
+
# count = 0
|
28
|
+
# defaults = {
|
29
|
+
# 'id' => lambda{|item| count += 1},
|
30
|
+
# 'aaa' => 111,
|
31
|
+
# 'bbb' => proc{|item| item['ccc'].nil? ? 'No ccc' : 'Has ccc'}
|
32
|
+
# }
|
33
|
+
# values = [
|
34
|
+
# {'aaa' => 'Defaults apply on nil values only', 'bbb' => nil},
|
35
|
+
# {'ccc' => 'ddd'},
|
36
|
+
# {'id' => 'abc123'}
|
37
|
+
# ]
|
38
|
+
# new_item = {'bbb' => 'Look mom! no ccc'}
|
39
|
+
# collection = SmartCollection.new ['id'], defaults: defaults
|
40
|
+
# collection << new_item
|
41
|
+
# collection
|
42
|
+
# # => [
|
43
|
+
# # {'id' => 1, 'aaa' => 'Defaults apply on nil values only', 'bbb' => 'No ccc'},
|
44
|
+
# # {'id' => 2, 'aaa' => 111, 'bbb' => 'Has ccc', 'ccc' => 'ddd'},
|
45
|
+
# # {'id' => 'abc123', 'aaa' => 111, 'bbb' => 'No ccc'},
|
46
|
+
# # {'id' => 3, 'aaa' => 111, 'bbb' => 'Look mom! no ccc'}
|
47
|
+
# # ]
|
48
|
+
#
|
49
|
+
# @note Defaults will apply to missing fields and null values only.
|
50
|
+
def initialize key_fields, opts = {}
|
51
|
+
@key_fields = key_fields || []
|
52
|
+
@defaults = opts[:defaults] || {}
|
53
|
+
super 0
|
54
|
+
(opts[:values] || []).each{|item| self << item}
|
55
|
+
end
|
56
|
+
|
57
|
+
# Asigned events.
|
58
|
+
# @private
|
59
|
+
def events
|
60
|
+
@events ||= {}
|
61
|
+
end
|
62
|
+
|
63
|
+
# Add event binding by key and block.
|
64
|
+
#
|
65
|
+
# @param [Symbol] key Event name.
|
66
|
+
#
|
67
|
+
# @raise [ArgumentError] When unknown event key.
|
68
|
+
#
|
69
|
+
# @example before_defaults
|
70
|
+
# defaults = {'aaa' => 111}
|
71
|
+
# collection = SmartCollection.new [],
|
72
|
+
# defaults: defaults
|
73
|
+
# collection.bind_event(:before_defaults) do |collection, item|
|
74
|
+
# puts collection
|
75
|
+
# # => []
|
76
|
+
# puts item
|
77
|
+
# # => {'bbb' => 222}
|
78
|
+
#
|
79
|
+
# # Sending the item back is required, or a new one
|
80
|
+
# # in case you want to replace item to insert.
|
81
|
+
# item
|
82
|
+
# end
|
83
|
+
# data << {'bbb' => 222}
|
84
|
+
# data
|
85
|
+
# # => [{'aaa' => 111, 'bbb' => 222}]
|
86
|
+
#
|
87
|
+
# @example before_match
|
88
|
+
# keys = ['id']
|
89
|
+
# defaults = {'aaa' => 111}
|
90
|
+
# values = [
|
91
|
+
# {'id' => 1, 'ccc' => 333}
|
92
|
+
# ]
|
93
|
+
# collection = SmartCollection.new keys,
|
94
|
+
# defaults: defaults
|
95
|
+
# values: values
|
96
|
+
# collection.bind_event(:before_match) do |collection, item|
|
97
|
+
# puts collection
|
98
|
+
# # => [{'id' => 1, 'aaa' => 111, 'ccc' => 333}]
|
99
|
+
# puts item
|
100
|
+
# # => {'id' => 1, 'aaa' => 111, 'bbb' => 222}
|
101
|
+
#
|
102
|
+
# # Sending the item back is required, or a new one
|
103
|
+
# # in case you want to replace item to insert.
|
104
|
+
# item
|
105
|
+
# end
|
106
|
+
# data << {'id' => 1, 'bbb' => 222}
|
107
|
+
# data
|
108
|
+
# # => [{'id' => 1, 'aaa' => 111, 'bbb' => 222}]
|
109
|
+
#
|
110
|
+
# @example before_insert
|
111
|
+
# keys = ['id']
|
112
|
+
# defaults = {'aaa' => 111}
|
113
|
+
# values = [
|
114
|
+
# {'id' => 1, 'ccc' => 333}
|
115
|
+
# ]
|
116
|
+
# collection = SmartCollection.new keys,
|
117
|
+
# defaults: defaults
|
118
|
+
# values: values
|
119
|
+
# collection.bind_event(:before_insert) do |collection, item, match|
|
120
|
+
# puts collection
|
121
|
+
# # => [{'id' => 1, 'aaa' => 111, 'ccc' => 333}]
|
122
|
+
# puts item
|
123
|
+
# # => {'id' => 1, 'aaa' => 111, 'bbb' => 222}
|
124
|
+
# puts match
|
125
|
+
# # => {'id' => 1, 'aaa' => 111, 'ccc' => 333}
|
126
|
+
#
|
127
|
+
# # Sending the item back is required, or a new one
|
128
|
+
# # in case you want to replace item to insert.
|
129
|
+
# item
|
130
|
+
# end
|
131
|
+
# data << {'id' => 1, 'bbb' => 222}
|
132
|
+
# data
|
133
|
+
# # => [{'id' => 1, 'aaa' => 111, 'bbb' => 222}]
|
134
|
+
#
|
135
|
+
# @example after_insert
|
136
|
+
# keys = ['id']
|
137
|
+
# defaults = {'aaa' => 111}
|
138
|
+
# values = [
|
139
|
+
# {'id' => 1, 'ccc' => 333}
|
140
|
+
# ]
|
141
|
+
# collection = SmartCollection.new keys,
|
142
|
+
# defaults: defaults
|
143
|
+
# values: values
|
144
|
+
# collection.bind_event(:after_insert) do |collection, item, match|
|
145
|
+
# puts collection
|
146
|
+
# # => [{'id' => 1, 'aaa' => 111, 'bbb' => 222}]
|
147
|
+
# puts item
|
148
|
+
# # => {'id' => 1, 'aaa' => 111, 'bbb' => 222}
|
149
|
+
# puts match
|
150
|
+
# # => {'id' => 1, 'aaa' => 111, 'ccc' => 333}
|
151
|
+
# # No need to send item back since it is already inserted
|
152
|
+
# end
|
153
|
+
# data << {'id' => 1, 'bbb' => 222}
|
154
|
+
# data
|
155
|
+
# # => [{'id' => 1, 'aaa' => 111, 'bbb' => 222}]
|
156
|
+
#
|
157
|
+
# @note Some events will expect a return value to replace item on insertion:
|
158
|
+
# * `before_match`
|
159
|
+
# * `before_defaults`
|
160
|
+
# * `before_insert`
|
161
|
+
def bind_event key, &block
|
162
|
+
unless EVENTS.include? key
|
163
|
+
raise ArgumentError.new("Unknown event '#{key}'")
|
164
|
+
end
|
165
|
+
(events[key] ||= []) << block
|
166
|
+
end
|
167
|
+
|
168
|
+
# Call an event
|
169
|
+
# @private
|
170
|
+
#
|
171
|
+
# @param [Symbol] key Event name.
|
172
|
+
# @param default Detault return value when event's return nil.
|
173
|
+
# @param args event arguments.
|
174
|
+
def call_event key, default = nil, *args
|
175
|
+
return default if events[key].nil?
|
176
|
+
result = nil
|
177
|
+
events[key].each{|event| result = event.call self, *args}
|
178
|
+
result.nil? ? default : result
|
179
|
+
end
|
180
|
+
|
181
|
+
# Check whenever two items keys match.
|
182
|
+
#
|
183
|
+
# @param [Hash] item_a Item to match.
|
184
|
+
# @param [Hash] item_b Item to match.
|
185
|
+
#
|
186
|
+
# @return [Boolean]
|
187
|
+
def match_keys? item_a, item_b
|
188
|
+
return false if key_fields.nil? || key_fields.count < 1
|
189
|
+
return true if item_a.nil? && item_b.nil?
|
190
|
+
return false if item_a.nil? || item_b.nil?
|
191
|
+
key_fields.each do |key|
|
192
|
+
return false if item_a[key] != item_b[key]
|
193
|
+
end
|
194
|
+
true
|
195
|
+
end
|
196
|
+
|
197
|
+
# Apply default values into item.
|
198
|
+
# @private
|
199
|
+
#
|
200
|
+
# @param [Hash] item Item to apply defaults.
|
201
|
+
#
|
202
|
+
# @return [Hash] Item
|
203
|
+
def apply_defaults item
|
204
|
+
defaults.each do |key, value|
|
205
|
+
next unless item[key].nil?
|
206
|
+
item[key] = value.respond_to?(:call) ? value.call(item) : value
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Find an item by matching filter keys
|
211
|
+
#
|
212
|
+
# @param [Hash] filter
|
213
|
+
#
|
214
|
+
# @return [Hash,nil] First existing item match or nil when no match.
|
215
|
+
#
|
216
|
+
# @note _Warning:_ It uses table scan to filter and will be slow.
|
217
|
+
def find_match filter
|
218
|
+
self.find do |item|
|
219
|
+
match_keys? item, filter
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Add/remplace an item avoiding duplicates
|
224
|
+
def << item
|
225
|
+
item = call_event :before_defaults, item, item
|
226
|
+
apply_defaults item
|
227
|
+
item = call_event :before_match, item, item
|
228
|
+
match = find_match item
|
229
|
+
item = call_event :before_insert, item, item, match
|
230
|
+
delete match unless match.nil?
|
231
|
+
result = super(item)
|
232
|
+
call_event :after_insert, result, item, match
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|