data-provider 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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