configurable 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/History CHANGED
@@ -1,3 +1,10 @@
1
+ == 0.4.0 / 2009-03-05
2
+
3
+ Reworked nesting. Changes are not backward compatible.
4
+
5
+ * Nesting syntax reworked
6
+ * Delegates no longer accept nil reader/writers
7
+
1
8
  == 0.3.0 / 2009-02-17
2
9
 
3
10
  Significant rework of the 0.1.0 release:
data/lib/configurable.rb CHANGED
@@ -111,18 +111,14 @@ require 'configurable/class_methods'
111
111
  # alt.set_sym('two')
112
112
  # alt.config[:sym] # => :two
113
113
  #
114
- # Idiosyncratically, true, false, and nil may also be provided as reader/writer
115
- # options.
114
+ # Idiosyncratically, true and false may also be provided as reader/writer
115
+ # values.
116
116
  #
117
- # true Same as using the defaults, accessors are defined.
118
- #
119
- # false Sets the default reader/writer but does not define
117
+ # true:: Same as using the defaults, accessors are defined.
118
+ # false:: Sets the default reader/writer but does not define
120
119
  # the accessors (think 'define reader/writer' => false).
121
120
  #
122
- # nil Does not define a reader/writer, and does not define
123
- # the accessors. In effect this will define a config
124
- # that does not map to the instance, but will be
125
- # present in instance.config
121
+ # Nil is not allowed as a value.
126
122
  #
127
123
  # ==== Non-reader/writer attributes
128
124
  #
@@ -133,223 +133,224 @@ module Configurable
133
133
  # end
134
134
  # end
135
135
  #
136
+ # === Attributes
137
+ #
138
+ # Several attributes may be specified to modify how a config is constructed.
139
+ # Attribute keys should be specified as symbols.
140
+ #
141
+ # Attribute:: Description
142
+ # reader:: The method used to read the configuration.
143
+ # (default: key)
144
+ # writer:: The method used to write the configuration
145
+ # (default: "#{key}=")
146
+ #
147
+ # Neither attribute may be set to nil, but they may be set to non-default
148
+ # values. In that case, config_attr will register the method names as
149
+ # provided, but it will not define the methods themselves. Specifying true
150
+ # uses and defines the default methods. Specifying false uses the default
151
+ # method name, but does not define the method itself.
152
+ #
153
+ # Any additional attributes are registered with the configuration.
136
154
  def config_attr(key, value=nil, attributes={}, &block)
137
155
  attributes = merge_attributes(block, attributes)
138
156
 
139
- # define the default public reader method
140
- reader = attributes.delete(:reader)
141
-
142
- case reader
143
- when true
144
- reader = key
145
- attr_reader(key)
146
- public(key)
147
- when false
148
- reader = key
157
+ # define the reader
158
+ reader = define_attribute_method(:reader, attributes, key) do |attribute|
159
+ attr_reader(attribute)
160
+ public(attribute)
149
161
  end
150
-
151
- # define the default public writer method
152
- writer = attributes.delete(:writer)
153
-
154
- if block_given? && writer != true
162
+
163
+ # define the writer
164
+ if block_given? && attributes[:writer] != true
155
165
  raise ArgumentError, "a block may not be specified without writer == true"
156
166
  end
157
-
158
- case writer
159
- when true
160
- writer = "#{key}="
161
- block_given? ? define_method(writer, &block) : attr_writer(key)
162
- public writer
163
- when false
164
- writer = "#{key}="
167
+
168
+ writer = define_attribute_method(:writer, attributes, "#{key}=") do |attribute|
169
+ block_given? ? define_method(attribute, &block) : attr_writer(key)
170
+ public(attribute)
165
171
  end
166
-
172
+
167
173
  configurations[key] = Delegate.new(reader, writer, value, attributes)
168
174
  end
169
-
170
- # Adds a configuration to self accessing the configurations for the
171
- # configurable class. Unlike config_attr and config, nest does not
172
- # create accessors; the configurations must be accessed through
173
- # the instance config method.
175
+
176
+ # Adds nested configurations to self. Nest creates a new configurable
177
+ # class using the block, and provides accessors to an instance of the
178
+ # new class. Everything is set up so you can access configs through
179
+ # the instance or through config.
174
180
  #
175
181
  # class A
176
182
  # include Configurable
177
- # config :key, 'value'
178
183
  #
179
- # def initialize(overrides={})
180
- # initialize_config(overrides)
184
+ # config :key, 'one'
185
+ # nest :nest do
186
+ # config :key, 'two'
181
187
  # end
182
188
  # end
183
189
  #
184
- # class B
185
- # include Configurable
186
- # nest :a, A
190
+ # a = A.new
191
+ # a.key # => 'one'
192
+ # a.config[:key] # => 'one'
187
193
  #
188
- # def initialize(overrides={})
189
- # initialize_config(overrides)
190
- # end
191
- # end
194
+ # a.nest.key # => 'two'
195
+ # a.config[:nest][:key] # => 'two'
196
+ #
197
+ # a.nest.key = 'TWO'
198
+ # a.config[:nest][:key] # => 'TWO'
192
199
  #
193
- # b = B.new
194
- # b.config[:a] # => {:key => 'value'}
200
+ # a.config[:nest][:key] = 2
201
+ # a.nest.key # => 2
195
202
  #
196
- # Nest may be provided a block which initializes an instance of
197
- # configurable_class. In this case accessors for the instance
198
- # are created and access becomes quite natural.
203
+ # a.config.to_hash # => {:key => 'one', :nest => {:key => 2}}
204
+ # a.nest.config.to_hash # => {:key => 2}
205
+ # a.nest.class # => A::Nest
199
206
  #
200
- # class C
207
+ # An existing configurable class may be provided instead of using the block
208
+ # to define a new configurable class. Recursive nesting is supported.
209
+ #
210
+ # class B
201
211
  # include Configurable
202
- # nest(:a, A) {|overrides| A.new(overrides) }
203
212
  #
204
- # def initialize(overrides={})
205
- # initialize_config(overrides)
213
+ # config :key, 1, &c.integer
214
+ # nest :nest do
215
+ # config :key, 2, &c.integer
216
+ # nest :nest do
217
+ # config :key, 3, &c.integer
218
+ # end
206
219
  # end
207
220
  # end
208
221
  #
209
- # c = C.new
210
- # c.a.key # => "value"
211
- #
212
- # c.a.key = "one"
213
- # c.config[:a].to_hash # => {:key => 'one'}
214
- #
215
- # c.config[:a][:key] = 'two'
216
- # c.a.key # => "two"
217
- #
218
- # c.config[:a] = {:key => 'three'}
219
- # c.a.key # => "three"
220
- #
221
- # The initialize block executes in class context, much like config.
222
- #
223
- # # An equivalent class to illustrate class-context
224
- # class EquivalentClass
225
- # attr_reader :a, A
226
- #
227
- # INITIALIZE_BLOCK = lambda {|overrides| A.new(overrides) }
228
- #
229
- # def initialize(overrides={})
230
- # @a = INITIALIZE_BLOCK.call(overrides[:a] || {})
231
- # end
222
+ # class C
223
+ # include Configurable
224
+ # nest :a, A
225
+ # nest :b, B
232
226
  # end
233
227
  #
234
- # Nest checks for recursive nesting and raises an error if a recursive nest
235
- # is detected.
236
- #
237
- # ==== Attributes
238
- #
239
- # Nesting with an initialization block creates the public accessor for the
240
- # instance, private methods to read and write the instance configurations,
241
- # and a private method to initialize the instance. The default names
242
- # for these methods are listed with the attributes to override them:
243
- #
244
- # :instance_reader key
245
- # :instance_writer "#{key}="
246
- # :instance_initializer "#{key}_initialize"
247
- # :reader "#{key}_config_reader"
248
- # :writer "#{key}_config_writer"
249
- #
250
- # These attributes are ignored if no block is given; true/false/nil
251
- # values are meaningless and will be treated as the default.
252
- #
253
- def nest(key, configurable_class, attributes={}, &block)
228
+ # c = C.new
229
+ # c.b.key = 7
230
+ # c.b.nest.key = "8"
231
+ # c.config[:b][:nest][:nest][:key] = "9"
232
+ #
233
+ # c.config.to_hash
234
+ # # => {
235
+ # # :a => {
236
+ # # :key => 'one',
237
+ # # :nest => {:key => 'two'}
238
+ # # },
239
+ # # :b => {
240
+ # # :key => 7,
241
+ # # :nest => {
242
+ # # :key => 8,
243
+ # # :nest => {:key => 9}
244
+ # # }
245
+ # # }}
246
+ #
247
+ # === Attributes
248
+ #
249
+ # Nest provides a number of attributes that can modify how a nest is
250
+ # constructed. Attribute keys should be specified as symbols.
251
+ #
252
+ # Attribute:: Description
253
+ # const_name:: Determines the constant name of the configurable
254
+ # class within the nesting class. May be nil.
255
+ # (default: key.to_s.capitalize)
256
+ # instance_reader:: The method accessing the nested instance. (default: key)
257
+ # instance_writer:: The method to set the nested instance. (default: "#{key}=")
258
+ # instance_initializer:: The method that initializes the instance.
259
+ # (default: "initialize_#{key}")
260
+ # reader:: The method used to read the instance configuration.
261
+ # (default: "#{key}_config_reader")
262
+ # writer:: The method used to initialize or reconfigure the
263
+ # instance. (default: "#{key}_config_writer")
264
+ #
265
+ # Except for const_name, these attributes are used to define methods
266
+ # required for nesting to work properly. None of the method attributes may
267
+ # be set to nil, but they may be set to non-default values. In that case,
268
+ # nest will register the method names as provided, but it will not define
269
+ # the methods themselves. The user must define methods with the following
270
+ # functionality:
271
+ #
272
+ # Attribute:: Function
273
+ # instance_reader:: Returns the instance of the configurable class
274
+ # instance_writer:: Inputs and sets the instance of the configurable class
275
+ # instance_initializer:: Receives the initial config and return an instance of
276
+ # configurable class
277
+ # reader:: Returns instance.config
278
+ # writer:: Reconfigures instance using the input overrides,
279
+ # or uses instance_initializer and instance_writer to
280
+ # initialize and set the instance.
281
+ #
282
+ # Methods can be public or otherwise. Specifying true uses and defines the
283
+ # default methods. Specifying false uses the default method name, but does
284
+ # not define the method itself.
285
+ #
286
+ # Any additional attributes are registered with the configuration.
287
+ def nest(key, configurable_class=nil, attributes={}, &block)
254
288
  attributes = merge_attributes(block, attributes)
289
+ attributes = {
290
+ :instance_reader => true,
291
+ :instance_writer => true,
292
+ :initializer => true
293
+ }.merge(attributes)
255
294
 
256
- if block_given?
257
- instance_variable = "@#{key}".to_sym
258
- nest_attr(key, configurable_class, attributes) do |input|
259
- instance_variable_set(instance_variable, yield(input))
260
- end
295
+ # define the nested configurable
296
+ if configurable_class
297
+ raise "a block is not allowed when a configurable class is specified" if block_given?
261
298
  else
262
- nest_attr(key, configurable_class, attributes)
299
+ configurable_class = Class.new { include Configurable }
300
+ configurable_class.class_eval(&block) if block_given?
263
301
  end
264
- end
265
-
266
- # Same as nest, except the initialize block executes in instance-context.
267
- #
268
- # class C
269
- # include Configurable
270
- # nest(:a, A) {|overrides| A.new(overrides) }
271
- #
272
- # def initialize(overrides={})
273
- # initialize_config(overrides)
274
- # end
275
- # end
276
- #
277
- # # An equivalent class to illustrate instance-context
278
- # class EquivalentClass
279
- # attr_reader :a, A
280
- #
281
- # def a_initialize(overrides)
282
- # A.new(overrides)
283
- # end
284
- #
285
- # def initialize(overrides={})
286
- # @a = send(:a_initialize, overrides[:a] || {})
287
- # end
288
- # end
289
- #
290
- def nest_attr(key, configurable_class, attributes={}, &block)
291
- unless configurable_class.kind_of?(Configurable::ClassMethods)
292
- raise ArgumentError, "not a Configurable class: #{configurable_class}"
302
+
303
+ # set the new constant
304
+ const_name = if attributes.has_key?(:const_name)
305
+ attributes.delete(:const_name)
306
+ else
307
+ key.to_s.capitalize
293
308
  end
309
+ const_set(const_name, configurable_class) if const_name
294
310
 
295
- attributes = merge_attributes(block, attributes)
311
+ # define instance reader
312
+ instance_reader = define_attribute_method(:instance_reader, attributes, key) do |attribute|
313
+ attr_reader(key)
314
+ public(key)
315
+ end
316
+
317
+ # define instance writer
318
+ instance_writer = define_attribute_method(:instance_writer, attributes, "#{key}=") do |attribute|
319
+ attr_writer(key)
320
+ public(attribute)
321
+ end
296
322
 
297
- # add some tracking attributes
298
- attributes[:receiver] ||= configurable_class
323
+ # define initializer
324
+ initializer = define_attribute_method(:initializer, attributes, "initialize_#{key}") do |attribute|
325
+ define_method(attribute) {|config| configurable_class.new.reconfigure(config) }
326
+ private(attribute)
327
+ end
299
328
 
300
- # remove method attributes
301
- instance_reader = attributes.delete(:instance_reader)
302
- instance_writer = attributes.delete(:instance_writer)
303
- initializer = attributes.delete(:instance_initializer)
304
- reader = attributes.delete(:reader)
305
- writer = attributes.delete(:writer)
329
+ # define the reader
330
+ reader = define_attribute_method(:reader, attributes, "#{key}_config_reader") do |attribute|
331
+ define_method(attribute) { send(instance_reader).config }
332
+ private(attribute)
333
+ end
306
334
 
307
- if block_given?
308
- # define instance accessor methods
309
- instance_reader = boolean_select(instance_reader, key)
310
- instance_writer = boolean_select(instance_writer, "#{key}=")
311
- instance_var = "@#{instance_reader}".to_sym
312
-
313
- initializer = boolean_select(reader, "#{key}_initialize")
314
- reader = boolean_select(reader, "#{key}_config_reader")
315
- writer = boolean_select(writer, "#{key}_config_writer")
316
-
317
- # the public accessor
318
- attr_reader instance_reader
319
-
320
- define_method(instance_writer) do |value|
321
- instance_variable_set(instance_var, value)
322
- end
323
- public(instance_reader, instance_writer)
324
-
325
- # the initializer
326
- define_method(initializer, &block)
327
-
328
- # the reader returns the config for the instance
329
- define_method(reader) do
330
- instance_variable_get(instance_var).config
331
- end
332
-
333
- # the writer initializes the instance if necessary,
334
- # or reconfigures the instance if it already exists
335
- define_method(writer) do |value|
336
- if instance_variable_defined?(instance_var)
337
- instance_variable_get(instance_var).reconfigure(value)
335
+ # define the writer
336
+ writer = define_attribute_method(:writer, attributes, "#{key}_config_writer") do |attribute|
337
+ define_method(attribute) do |value|
338
+ if instance = send(instance_reader)
339
+ instance.reconfigure(value)
338
340
  else
339
- instance_variable_set(instance_var, send(initializer, value))
341
+ send(instance_writer, send(initializer, value))
340
342
  end
341
343
  end
342
- private(reader, writer)
343
- else
344
- reader = writer = nil
344
+ private(attribute)
345
345
  end
346
346
 
347
- value = DelegateHash.new(configurable_class.configurations)
348
- configurations[key] = Delegate.new(reader, writer, value, attributes)
349
-
347
+ # define the configuration
348
+ nested_config = DelegateHash.new(configurable_class.configurations)
349
+ configurations[key] = Delegate.new(reader, writer, nested_config, attributes)
350
+
350
351
  check_infinite_nest(configurable_class.configurations)
351
- end
352
-
352
+ end
353
+
353
354
  # Alias for Validation
354
355
  def c
355
356
  Validation
@@ -357,13 +358,30 @@ module Configurable
357
358
 
358
359
  private
359
360
 
360
- # a helper to select a value or the default, if the default is true,
361
- # false, or nil. used by nest_attr to handle attributes
362
- def boolean_select(value, default) # :nodoc:
363
- case value
364
- when true, false, nil then default
365
- else value
361
+ # a helper to define methods that may be overridden in attributes.
362
+ # yields the default to the block if the default is supposed to
363
+ # be defined. returns the symbolized method name.
364
+ def define_attribute_method(name, attributes, default) # :nodoc:
365
+ attribute = attributes.delete(name)
366
+
367
+ case attribute
368
+ when true
369
+ # true means use the default and define the method
370
+ attribute = default
371
+ yield(attribute)
372
+
373
+ when false
374
+ # false means use the default, but let the user define the method
375
+ attribute = default
376
+
377
+ when nil
378
+ # nil is not allowed
379
+ raise "#{name.inspect} attribute cannot be nil"
366
380
  end
381
+ # ... all other values specify what the method should be,
382
+ # and lets the user define the method.
383
+
384
+ attribute.to_sym
367
385
  end
368
386
 
369
387
  # a helper to initialize configurations for the first time,
@@ -27,8 +27,7 @@ module Configurable
27
27
  # line, in a web form, or a desktop app).
28
28
  attr_reader :attributes
29
29
 
30
- # Initializes a new Delegate with the specified key
31
- # and default value.
30
+ # Initializes a new Delegate with the specified key and default value.
32
31
  def initialize(reader, writer="#{reader}=", default=nil, attributes={})
33
32
  self.default = default
34
33
  self.reader = reader
@@ -61,16 +60,16 @@ module Configurable
61
60
  duplicate && @duplicable ? @default.dup : @default
62
61
  end
63
62
 
64
- # Sets the reader for self. The reader is symbolized,
65
- # but may also be set to nil.
63
+ # Sets the reader for self.
66
64
  def reader=(value)
67
- @reader = value == nil ? value : value.to_sym
65
+ raise ArgumentError, "reader may not be nil" if value == nil
66
+ @reader = value.to_sym
68
67
  end
69
68
 
70
- # Sets the writer for self. The writer is symbolized,
71
- # but may also be set to nil.
69
+ # Sets the writer for self.
72
70
  def writer=(value)
73
- @writer = value == nil ? value : value.to_sym
71
+ raise ArgumentError, "writer may not be nil" if value == nil
72
+ @writer = value.to_sym
74
73
  end
75
74
 
76
75
  # Returns true if the default value is a kind of DelegateHash.
@@ -58,8 +58,8 @@ module Configurable
58
58
  end
59
59
 
60
60
  # Binds self to the specified receiver. Delegate values are removed from
61
- # store and sent to their writer method on receiver. If the store has no
62
- # value for a delegate key, the delegate default value will be used.
61
+ # store and sent to their writer on receiver. If the store has no value
62
+ # for a delegate key, the delegate default value will be used.
63
63
  def bind(receiver)
64
64
  raise ArgumentError, "receiver cannot be nil" if receiver == nil
65
65
 
@@ -89,15 +89,15 @@ module Configurable
89
89
  self
90
90
  end
91
91
 
92
- # Retrieves the value corresponding to the key. When bound, delegates with
93
- # readers pull values from the receiver; otherwise the value in store will
94
- # be returned. When unbound, if the store has no value for a delegate, the
95
- # delgate default value will be returned.
92
+ # Retrieves the value corresponding to the key. When bound, delegates pull
93
+ # values from the receiver using the delegate.reader method; otherwise the
94
+ # value in store will be returned. When unbound, if the store has no value
95
+ # for a delegate, the delgate default value will be returned.
96
96
  def [](key)
97
97
  return store[key] unless delegate = delegates[key]
98
98
 
99
99
  case
100
- when bound? && delegate.reader
100
+ when bound?
101
101
  receiver.send(delegate.reader)
102
102
  when store.has_key?(key)
103
103
  store[key]
@@ -106,17 +106,15 @@ module Configurable
106
106
  end
107
107
  end
108
108
 
109
- # Stores a value for the key. When bound, delegates with writers send the
110
- # value to the receiver; otherwise values are stored in store.
109
+ # Stores a value for the key. When bound, delegates set the value in the
110
+ # receiver using the delegate.writer method; otherwise values are stored in
111
+ # store.
111
112
  def []=(key, value)
112
113
  if bound? && delegate = delegates[key]
113
- if delegate.writer
114
- receiver.send(delegate.writer, value)
115
- return
116
- end
114
+ receiver.send(delegate.writer, value)
115
+ else
116
+ store[key] = value
117
117
  end
118
-
119
- store[key] = value
120
118
  end
121
119
 
122
120
  # Returns the union of delegate and store keys.
@@ -181,8 +179,6 @@ module Configurable
181
179
  # helper to map delegate values from source to the receiver
182
180
  def map(source) # :nodoc:
183
181
  delegates.each_pair do |key, delegate|
184
- next unless writer = delegate.writer
185
-
186
182
  # map the value; if no value is set in the source then use the
187
183
  # delegate default. if map_default is false, then simply skip...
188
184
  # this ensures each config is initialized to a value when bound
@@ -193,15 +189,14 @@ module Configurable
193
189
  else next
194
190
  end
195
191
 
196
- receiver.send(writer, value)
192
+ receiver.send(delegate.writer, value)
197
193
  end
198
194
  end
199
195
 
200
196
  # helper to unmap delegates from the receiver to a target hash
201
197
  def unmap(target) # :nodoc:
202
198
  delegates.each_pair do |key, delegate|
203
- next unless reader = delegate.reader
204
- target[key] = receiver.send(reader)
199
+ target[key] = receiver.send(delegate.reader)
205
200
  end
206
201
  end
207
202
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: configurable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Chiang
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-02-17 00:00:00 -07:00
12
+ date: 2009-03-05 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency