data-provider 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 38f6f8ba1418886f65eb5bed06cfba596744dfb8
4
- data.tar.gz: 33d309c91f3cbb341159b20f99129d0ea387a10e
3
+ metadata.gz: 3463cfbe49623868590249a50db750da286c65f2
4
+ data.tar.gz: 9dad29a26d14bbb7fd2f5a38c9d6761dc38e3710
5
5
  SHA512:
6
- metadata.gz: f73b0708c03917f4a58a14bffddbd1357336154b917f0ff610511c0572fb743f12f2824647b36d7bd30972967cc41d6a887de8ae696b6318acd69236a847ec51
7
- data.tar.gz: a19b6508e5c30fe4ad94ee42c0f205f8a539cc9f1311b9828feb407daf66ab99d2651f8268b418ffaf91c922c93006cfc9a03535a7ee14af676b7382ecc76c13
6
+ metadata.gz: 95affc545a8fd0e3d60b32b88a36f692ac1b97093991d3b33f6292559922586ed587650ed511db4a6f7af59a04d7c5a86153f7fc96dfe60f685c648d33c9b1b6
7
+ data.tar.gz: f907443bc27cf6917cf845da401c771377078998562cd897b010468404b8c6de1000bdefd97a4fd5eaa31942987ba25d3a4192fd11c6b892cc55152b7631708b
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- data-provider (0.0.1)
4
+ data-provider (0.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -71,9 +71,9 @@ product_provider.take(:discount_price) # => TypeError (discount data not given,)
71
71
  discounted_provider = product_provider.add_data(discount: 3.0) # returns a new instance of the same provider class
72
72
  discounted_provider.take(:discount_price) # => 14.99
73
73
  product_provider.take(:discount_price) # => TypeError (this instance didn't get the new data)
74
- product_provider.add_date!(discount: 2) # => Updates this instance instead of creating a new one
74
+ product_provider.add_data!(discount: 2) # => Updates this instance instead of creating a new one
75
75
  product_provider.take(:discount_price) # => 15.99
76
- product_provider.add_date!(discount: 4).take(:discount_price) #=> 13.99
76
+ product_provider.add_data!(discount: 4).take(:discount_price) #=> 13.99
77
77
  ```
78
78
 
79
79
  Providers can be defined in a module and added to a provider class using the add class-method (not using include!)
@@ -1,9 +1,6 @@
1
- GEM_NAME="data-provider"
2
- PKG_VERSION='0.1.0'
3
-
4
1
  Gem::Specification.new do |s|
5
- s.name = GEM_NAME
6
- s.version = PKG_VERSION
2
+ s.name = "data-provider"
3
+ s.version = '0.2.0'
7
4
  s.files = `git ls-files`.split($/)
8
5
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
9
6
 
@@ -2,226 +2,229 @@ require 'logger'
2
2
 
3
3
  module DataProvider
4
4
 
5
- class ProviderMissingException < Exception
6
- end
7
-
8
5
  module Base
9
6
 
10
7
  def self.included(base)
11
8
  base.class_eval do
12
9
  include InstanceMethods
10
+ include ProxyMethods
13
11
  extend ClassMethods
12
+ extend ProxyMethods
14
13
  end
15
14
  end
16
15
 
17
- module ClassMethods
18
- # provides, when called with a hash param, will define 'simple providers' (providers
19
- # with a simple, static value). When called without a param (or nil) it returns
20
- # the current cumulative 'simple providers' hash
21
- def provides simple_provides = nil
22
- if simple_provides
23
- @data_provider ||= {}
24
- @data_provider[:provides] ||= {}
25
- @data_provider[:provides].merge!(simple_provides)
26
- return self
27
- end
28
- # no data given? just return existing hash
29
- (@data_provider || {})[:provides] || {}
16
+ # both instance- class-level
17
+ module ProxyMethods
18
+ def provides *args
19
+ return dpc.provides if args.length == 0
20
+ dpc.provides *args
21
+ return self
30
22
  end
31
23
 
32
- # returns the requested provider as a Provider object
33
- def get_provider(id)
34
- args = data_provider_definitions.find{|args| args.first == id}
35
- return args.nil? ? nil : Provider.new(*args)
24
+ def provider_identifiers *args
25
+ dpc.provider_identifiers *args
36
26
  end
37
27
 
38
- def provider_identifiers
39
- (provides.keys + data_provider_definitions.map(&:first)).compact.uniq
28
+ def provider *args, &block
29
+ dpc.provider *args, &block
40
30
  end
41
31
 
42
- # adds a new provider to the class
43
- def provider identifier, opts = {}, &block
44
- add_provider(identifier, opts, block_given? ? block : nil)
32
+ def has_provider? *args
33
+ dpc.has_provider? *args
45
34
  end
46
35
 
47
- # reader method for the raw data of the currently defined providers
48
- def data_provider_definitions
49
- ((@data_provider || {})[:provider_args] || [])
36
+ def has_providers_with_scope?(*args)
37
+ dpc.has_providers_with_scope?(*args)
50
38
  end
51
39
 
52
- # returns wether a provider with the given identifier is available
53
- def has_provider?(identifier)
54
- (provides[identifier] || get_provider(identifier)) != nil
40
+ def fallback_provider?
41
+ dpc.fallback_provider?
55
42
  end
56
43
 
57
- def has_providers_with_scope?(args)
58
- scope = args.is_a?(Array) ? args : [args]
59
- provider_identifiers.find{|id| id.is_a?(Array) && id.length > scope.length && id[0..(scope.length-1)] == scope} != nil
44
+ def provider_missing *args, &block
45
+ dpc.provider_missing *args, &block
60
46
  end
61
47
 
62
- def fallback_provider?
63
- !fallback_provider.nil?
48
+ def take(id, opts = {})
49
+ dpc.take(id, opts.merge(:scope => self))
64
50
  end
65
51
 
66
- # adds all the providers defined in the given module to this class
67
- def add(providers_module)
68
- data = providers_module.instance_variable_get('@data_provider') || {}
69
-
70
- (data[:provider_args] || []).each do |definition|
71
- add_provider(*definition)
72
- end
73
-
74
- self.provides(data[:provides] || {})
52
+ def try_take(id, opts = {})
53
+ dpc.try_take(id, opts.merge(:scope => self))
75
54
  end
76
55
 
77
- # adds all the providers defined in the given module to this class,
78
- # but turns their identifiers into array and prefixes the array with the :scope option
79
- def add_scoped(providers_module, _options = {})
80
- data = providers_module.instance_variable_get('@data_provider') || {}
56
+ def got?(*args)
57
+ dpc.got?(*args)
58
+ end
81
59
 
82
- (data[:provider_args] || []).each do |definition|
83
- definition[0] = [definition[0]].flatten
84
- definition[0] = [_options[:scope]].flatten.compact + definition[0] if _options[:scope]
85
- add_provider(*definition)
86
- end
60
+ alias :has_data? :got?
87
61
 
88
- (data[:provides] || {}).each_pair do |key, value|
89
- provides(([_options[:scope]].flatten.compact + [key].flatten.compact) => value)
90
- end
62
+ def given *args
63
+ dpc.given *args
91
64
  end
92
65
 
93
- def provider_missing &block
94
- raise "DataProvider::Base#provider_missing expects a block as an argument" if !block_given?
95
- @data_provider ||= {}
96
- @data_provider[:provider_missing] = block
97
- end
66
+ alias :get_data :given
98
67
 
99
- def fallback_provider
100
- block = (@data_provider || {})[:provider_missing]
101
- block.nil? ? nil : Provider.new(nil, nil, block)
68
+ def give! *args
69
+ dpc.give! *args
70
+ return self
102
71
  end
103
72
 
73
+ alias :add_scope! :give!
74
+ alias :add_data! :give!
75
+
104
76
  private
105
77
 
106
- def add_provider(identifier, opts = {}, block = nil)
107
- @data_provider ||= {}
108
- @data_provider[:provider_args] ||= []
109
- @data_provider[:provider_args].unshift [identifier, opts, block]
78
+ def missing_provider *args
79
+ dpc.missing_provider *args
110
80
  end
111
- end # module ClassMethods
112
-
113
81
 
114
- module InstanceMethods
82
+ def scoped_take *args
83
+ dpc.scoped_take *args
84
+ end
115
85
 
116
- attr_reader :data
117
- attr_reader :options
86
+ def scope *args
87
+ dpc.scope *args
88
+ end
118
89
 
119
- def initialize(opts = {})
120
- @options = opts.is_a?(Hash) ? opts : {}
121
- @data = options[:data].is_a?(Hash) ? options[:data] : {}
90
+ def scopes *args
91
+ dpc.scopes *args
122
92
  end
123
93
 
124
- def logger
125
- @logger ||= options[:logger] || Logger.new(STDOUT)
94
+ def provider_stack *args
95
+ dpc.provider_stack *args
126
96
  end
127
97
 
128
- def has_provider?(id)
129
- self.class.has_provider?(id)
98
+ def provider_id *args
99
+ dpc.provider_id *args
130
100
  end
101
+ end
131
102
 
132
- def has_providers_with_scope?(scope)
133
- self.class.has_providers_with_scope?(scope)
103
+ module ClassMethods
104
+ def data_provider_container
105
+ @data_provider_container ||= DataProvider::Container.new
134
106
  end
135
107
 
136
- def fallback_provider?
137
- self.class.fallback_provider?
108
+ alias :dpc :data_provider_container
109
+
110
+ # can't copy self on a class-level
111
+ def give *args
112
+ dpc.give! *args
113
+ return self
138
114
  end
139
115
 
140
- def take(id)
141
- # first try the simple providers
142
- if self.class.provides.has_key?(id)
143
- provider = self.class.provides[id]
144
- return provider.is_a?(Proc) ? provider.call : provider
145
- end
146
- # try to get a provider object
147
- provider = self.class.get_provider(id)
148
- if provider
149
- @scope ||= []
150
- @scope << (id.is_a?(Array) ? id[0..-2] : [])
151
- result = instance_eval(&provider.block)
152
- @scope.pop
153
- # execute provider object's block within the scope of self
154
- return result
155
- end
116
+ # alias :give :give!
117
+ alias :add_scope :give
118
+ alias :add_data :give
156
119
 
157
- # couldn't find requested provider, let's see if there's a fallback
158
- if provider = self.class.fallback_provider
159
- # temporarily set the @missing_provider instance variable, so the
160
- # fallback provider can use it through the missing_provider private method
161
- @missing_provider = id
162
- @scope ||= []
163
- @scope << (id.is_a?(Array) ? id[0..-2] : [])
164
- result = instance_eval(&provider.block) # provider.block.call # with the block.call method the provider can't access private methods like missing_provider
165
- @scope = nil
166
- return result
120
+ def add! _module
121
+ if _module.is_a?(DataProvider::Container)
122
+ dpc.add!(_module)
123
+ else
124
+ dpc.add!(_module.dpc)
167
125
  end
168
- # no fallback either? Time for an error
169
- raise ProviderMissingException.new(:message=>"Tried to take data from missing provider.", :provider_id => id)
170
- end
171
126
 
172
- def try_take(id, opts = {})
173
- return take(id) if self.has_provider?(id) || self.fallback_provider?
174
- if opts[:fallback] == true
127
+ include _module
128
+ return self
129
+ end
175
130
 
176
- logger.debug "Try for missing provider: #{id.inspect}"
177
- return nil
131
+ def add_scoped! _module, options = {}
132
+ if _module.is_a?(DataProvider::Container)
133
+ dpc.add_scoped!(_module, options)
134
+ else
135
+ dpc.add_scoped! _module.dpc, options
178
136
  end
137
+
138
+ include _module
139
+ return self
179
140
  end
180
141
 
181
- private
142
+ # classes/modules can't be cloned, so add behave just like add!
143
+ alias :add :add!
144
+ alias :add_scoped :add_scoped!
145
+ end # module ClassMethods
146
+
147
+
148
+ module InstanceMethods
182
149
 
183
- def scoped_take(id)
184
- take(((@scope || []).last || []) + [id].flatten)
150
+ attr_reader :options
151
+
152
+ def initialize(opts = {})
153
+ @options = opts.is_a?(Hash) ? opts : {}
154
+ dpc.give!(options[:data]) if options[:data].is_a?(Hash)
185
155
  end
186
156
 
187
- public
157
+ def logger
158
+ @logger ||= options[:logger] || Logger.new(STDOUT).tap do |lger|
159
+ lger.level = Logger::WARN
160
+ end
161
+ end
188
162
 
189
- def given(param_name)
190
- return data[param_name] if data.has_key?(param_name)
191
- logger.error "Data provider expected missing data with identifier: #{param_name.inspect}"
192
- # TODO: raise?
193
- return nil
163
+ def data_provider_container
164
+ @data_provider_container ||= options[:dpc] || DataProvider::Container.new.tap do |c|
165
+ # automatically adopt all class-level providers/provides/data
166
+ c.add! self.class.dpc
167
+ end
194
168
  end
195
169
 
196
- alias :get_data :given
170
+ alias :dpc :data_provider_container
197
171
 
198
- def give(_data = {})
199
- return self.class.new(options.merge(:data => data.merge(_data)))
172
+ def add _module
173
+ if _module.is_a?(DataProvider::Container)
174
+ _dpc = _module
175
+ else
176
+ _dpc = _module.dpc
177
+ self.class.include _module # todo: make optional?
178
+ end
179
+
180
+ self.class.new(options.merge({
181
+ :data => nil,
182
+ :dpc => dpc.add(_dpc)
183
+ }))
200
184
  end
201
185
 
202
- alias :add_scope :give
203
- alias :add_data :give
186
+ def add_scoped _module, options = {}
187
+ if _module.is_a?(DataProvider::Container)
188
+ _dpc = _module
189
+ else
190
+ _dpc = _module.dpc
191
+ self.class.include _module # todo: make optional?
192
+ end
204
193
 
205
- def give!(_data = {})
206
- @data = data.merge(_data)
207
- return self
194
+ self.class.new(options.merge({
195
+ :data => nil,
196
+ :dpc => dpc.add_scoped(_dpc, :scope => options[:scope])
197
+ }))
208
198
  end
209
199
 
210
- alias :add_scope! :give!
211
- alias :add_data! :give!
200
+ def give *args
201
+ self.class.new(options.merge(:data => nil, :dpc => self.dpc.give(*args)))
202
+ end
212
203
 
213
- private
204
+ alias :add_scope :give
205
+ alias :add_data :give
214
206
 
215
- def missing_provider
216
- # byebug
217
- @missing_provider
207
+ def add! _module
208
+ if _module.is_a?(DataProvider::Container)
209
+ dpc.add!(_module)
210
+ else
211
+ dpc.add!(_module.dpc)
212
+ self.class.include _module
213
+ end
214
+
215
+ return self
218
216
  end
219
217
 
220
- def scope
221
- @scope || []
218
+ def add_scoped! _module, options = {}
219
+ if _module.is_a?(DataProvider::Container)
220
+ dpc.add_scoped!(_module, options)
221
+ else
222
+ dpc.add_scoped! _module.dpc, options
223
+ self.class.include _module
224
+ end
225
+
226
+ return self
222
227
  end
223
228
  end # module InstanceMethods
224
-
225
229
  end # module Base
226
-
227
230
  end # module DataProvider
@@ -0,0 +1,277 @@
1
+ module DataProvider
2
+
3
+ class ProviderMissingException < Exception
4
+ attr_reader :params
5
+
6
+ def initialize(_params = {})
7
+ @params = _params || {}
8
+ super(params[:message] || "Tried to take data from missing provider: #{provider_id.inspect}")
9
+ end
10
+
11
+ def provider_id
12
+ params[:provider_id]
13
+ end
14
+ end
15
+
16
+
17
+ class Container
18
+ attr_reader :options
19
+
20
+ def initialize _opts = {}
21
+ @options = _opts.is_a?(Hash) ? _opts : {}
22
+ end
23
+
24
+ def logger
25
+ @logger ||= options[:logger] || Logger.new(STDOUT).tap do |lger|
26
+ lger.level = Logger::WARN
27
+ end
28
+ end
29
+
30
+ def provider identifier, opts = {}, &block
31
+ add_provider(identifier, opts, block_given? ? block : nil)
32
+ end
33
+
34
+ def has_provider?(identifier)
35
+ (provides.keys.find{|k| k == identifier} || get_provider(identifier)) != nil
36
+ end
37
+
38
+ def has_providers_with_scope?(args)
39
+ scope = args.is_a?(Array) ? args : [args]
40
+ provider_identifiers.find{|id| id.is_a?(Array) && id.length > scope.length && id[0..(scope.length-1)] == scope} != nil
41
+ end
42
+
43
+ def provider_identifiers
44
+ (provides.keys + providers.map(&:first)).compact.uniq
45
+ end
46
+
47
+ # provides, when called with a hash param, will define 'simple providers' (providers
48
+ # with a simple, static value). When called without a param (or nil) it returns
49
+ # the current cumulative 'simple providers' hash
50
+ def provides _provides = nil
51
+ return @provides || {} if _provides.nil?
52
+ add_provides _provides
53
+ return self
54
+ end
55
+
56
+ def providers
57
+ @providers || []
58
+ end
59
+
60
+ def provider_missing &block
61
+ raise "DataProvider::Base#provider_missing expects a block as an argument" if !block_given?
62
+ @fallback_provider = block
63
+ end
64
+
65
+ def fallback_provider
66
+ block = @fallback_provider
67
+ block.nil? ? nil : Provider.new(nil, nil, block)
68
+ end
69
+
70
+ def fallback_provider?
71
+ !fallback_provider.nil?
72
+ end
73
+
74
+ def take(id, opts = {})
75
+ logger.debug "DataProvider::Container#take with id: #{id.inspect}"
76
+
77
+ # first try the simple providers
78
+ if provides.has_key?(id)
79
+ provider = provides[id]
80
+ return provider.is_a?(Proc) ? provider.call : provider
81
+ end
82
+
83
+ # try to get a provider object
84
+ provider = get_provider(id)
85
+ if provider
86
+ @stack = (@stack || []) + [id]
87
+ result = (opts[:scope] || self).instance_eval(&provider.block)
88
+ @stack.pop
89
+ # execute provider object's block within the scope of self
90
+ return result
91
+ end
92
+
93
+ # try to get a scoped provider object
94
+ if scope.length > 0
95
+ scoped_id = [scope, id].flatten
96
+ provider = get_provider(scoped_id)
97
+ if provider
98
+ @stack = (@stack || []) + [scoped_id]
99
+ result = (opts[:scope] || self).instance_eval(&provider.block)
100
+ @stack.pop
101
+ # execute provider object's block within the scope of self
102
+ return result
103
+ end
104
+ end
105
+
106
+ # couldn't find requested provider, let's see if there's a fallback
107
+ if provider = fallback_provider
108
+ # temporarily set the @missing_provider instance variable, so the
109
+ # fallback provider can use it through the missing_provider private method
110
+ @missing_provider = id
111
+ @stack = (@stack || []) + [id]
112
+ result = (opts[:scope] || self).instance_eval(&provider.block) # provider.block.call # with the block.call method the provider can't access private methods like missing_provider
113
+ @stack.pop # = nil
114
+ @missing_provider = nil
115
+ return result
116
+ end
117
+
118
+ # no fallback either? Time for an error
119
+ raise ProviderMissingException.new(:provider_id => id)
120
+ end
121
+
122
+ def try_take(id, opts = {})
123
+ return take(id, opts) if self.has_provider?(id) || self.fallback_provider?
124
+ logger.debug "Try for missing provider: #{id.inspect}"
125
+ return nil
126
+ end
127
+
128
+ #
129
+ # "adding existing containers"-related methods
130
+ #
131
+
132
+ # adds all the providers defined in the given module to this class
133
+ def add!(container)
134
+ ### add container's providers ###
135
+ # internally providers are added in reverse order (last one first)
136
+ # so at runtime you it's easy and fast to grab the latest provider
137
+ # so when adding now, we have to reverse the providers to get them in the original order
138
+ container.providers.reverse.each do |definition|
139
+ add_provider(*definition)
140
+ end
141
+
142
+ ### add container's provides (simple providers) ###
143
+ self.provides(container.provides)
144
+
145
+ ### fallback provider ###
146
+ @fallback_provider = container.fallback_provider.block if container.fallback_provider
147
+
148
+ ### add container's data ###
149
+ give!(container.data)
150
+ end
151
+
152
+ def add(container)
153
+ # make a copy and add the container to that
154
+ give({}).add!(container)
155
+ end
156
+
157
+ # adds all the providers defined in the given module to this class,
158
+ # but turns their identifiers into array and prefixes the array with the :scope option
159
+ def add_scoped! container, _options = {}
160
+ ### add container's providers ###
161
+ container.providers.reverse.each do |definition|
162
+ identifier = [definition[0]].flatten
163
+ identifier = [_options[:scope]].flatten.compact + identifier if _options[:scope]
164
+ add_provider(*([identifier]+definition[1..-1]))
165
+ end
166
+
167
+ ### add container's provides (simple providers) ###
168
+ container.provides.each_pair do |key, value|
169
+ provides(([_options[:scope]].flatten.compact + [key].flatten.compact) => value)
170
+ end
171
+
172
+ ### fallback provider ###
173
+ @fallback_provider = container.fallback_provider.block if container.fallback_provider
174
+
175
+ ### add container's data ###
176
+ give!(container.data)
177
+ end
178
+
179
+ # adds all the providers defined in the given module to this class,
180
+ # but turns their identifiers into array and prefixes the array with the :scope option
181
+ def add_scoped container, _options = {}
182
+ copy.add_scoped!(container, _options)
183
+ end
184
+
185
+ #
186
+ # Data-related methods
187
+ #
188
+
189
+ def copy
190
+ c = self.class.new
191
+ c.add!(self)
192
+ end
193
+
194
+ def data
195
+ @data || {}
196
+ end
197
+
198
+ def give(_data = {})
199
+ copy.give!(_data)
200
+ end
201
+
202
+ alias :add_scope :give
203
+ alias :add_data :give
204
+
205
+ def give!(_data = {})
206
+ @data ||= {}
207
+ @data.merge!(_data)
208
+ return self
209
+ end
210
+
211
+ alias :add_scope! :give!
212
+ alias :add_data! :give!
213
+
214
+ def given(param_name)
215
+ return data[param_name] if got?(param_name)
216
+ logger.debug "Data provider expected missing data with identifier: #{param_name.inspect}"
217
+ # TODO: raise?
218
+ return nil
219
+ end
220
+
221
+ alias :get_data :given
222
+
223
+
224
+ def got?(param_name)
225
+ data.has_key?(param_name)
226
+ end
227
+
228
+ alias :has_data? :got?
229
+
230
+ def missing_provider
231
+ @missing_provider
232
+ end
233
+
234
+ def scoped_take(id)
235
+ take(scope + [id].flatten)
236
+ end
237
+
238
+ def provider_stack
239
+ (@stack || []).clone
240
+ end
241
+
242
+ def provider_id
243
+ provider_stack.last
244
+ end
245
+
246
+ def scopes
247
+ provider_stack.map{|provider_id| provider_id.is_a?(Array) ? provider_id[0..-2] : []}
248
+ end
249
+
250
+ def scope
251
+ scopes.last || []
252
+ end
253
+
254
+ private
255
+
256
+ def add_provider(identifier, opts = {}, block = nil)
257
+ @providers ||= []
258
+ @providers.unshift [identifier, opts, block]
259
+ end
260
+
261
+ def add_provides _provides = {}
262
+ if _provides.is_a?(Hash) != true
263
+ logger.error 'DataProvider::Container#add_provides received non-hash param'
264
+ return @provides
265
+ end
266
+
267
+ @provides ||= {}
268
+ @provides.merge! _provides
269
+ end
270
+
271
+ # returns the requested provider as a Provider object
272
+ def get_provider(id)
273
+ args = providers.find{|args| args.first == id}
274
+ return args.nil? ? nil : Provider.new(*args)
275
+ end
276
+ end # class Container
277
+ end # module DataProvider