doodle 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +18 -0
- data/examples/parent.rb +1 -1
- data/lib/doodle.rb +332 -241
- data/lib/doodle/datatypes.rb +0 -34
- data/lib/doodle/version.rb +1 -1
- data/spec/has_spec.rb +36 -0
- metadata +3 -2
data/History.txt
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
== 0.1.7 / 2008-05-10
|
2
|
+
- Features
|
3
|
+
- #has now accepts class constant in name position and generates name
|
4
|
+
and kind from that, e.g.
|
5
|
+
has Person --> has :person, :kind => Person
|
6
|
+
has AudioClip --> has :audio_clip, :kind => AudioClip
|
7
|
+
- moved datatypes code into main doodle.rb
|
8
|
+
- use doodle.parent instead of doodle_parent
|
9
|
+
|
10
|
+
- Bug fixes:
|
11
|
+
- fixed handling of :init blocks so they can refer to user supplied
|
12
|
+
attribute values which don't have defaults
|
13
|
+
- note: attributes referred to in :init blocks ~must~ be supplied as
|
14
|
+
keyword arguments - by the time evaluation gets to the
|
15
|
+
initialization block, the :init block has already been evaluated
|
16
|
+
so will raise an error if it cannot find the value it's looking for
|
17
|
+
- moved more methods into DoodleInfo from Core
|
18
|
+
|
1
19
|
== 0.1.6 / 2008-05-08
|
2
20
|
- Features:
|
3
21
|
- short cut syntax for #must - can now specify constraints like
|
data/examples/parent.rb
CHANGED
data/lib/doodle.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
# 2007-11-24 first version
|
4
4
|
# 2008-04-18 latest release 0.0.12
|
5
5
|
# 2008-05-07 0.1.6
|
6
|
+
# 2008-05-12 0.1.7
|
6
7
|
$:.unshift(File.dirname(__FILE__)) unless
|
7
8
|
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
8
9
|
|
@@ -57,6 +58,7 @@ class Doodle
|
|
57
58
|
def snake_case(camel_cased_word)
|
58
59
|
camel_cased_word.gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
|
59
60
|
end
|
61
|
+
# resolve a constant of the form Some::Class::Or::Module
|
60
62
|
def const_resolve(constant)
|
61
63
|
constant.to_s.split(/::/).reject{|x| x.empty?}.inject(Object) { |prev, this| prev.const_get(this) }
|
62
64
|
end
|
@@ -109,46 +111,6 @@ class Doodle
|
|
109
111
|
end
|
110
112
|
end
|
111
113
|
|
112
|
-
# provide an alternative inheritance chain that works for singleton
|
113
|
-
# classes as well as modules, classes and instances
|
114
|
-
module Inherited
|
115
|
-
|
116
|
-
# doodle_parents returns the set of parent classes of an object
|
117
|
-
def doodle_parents
|
118
|
-
anc = if respond_to?(:ancestors)
|
119
|
-
if ancestors.include?(self)
|
120
|
-
ancestors[1..-1]
|
121
|
-
else
|
122
|
-
# singletons have no doodle_parents (they're orphans)
|
123
|
-
[]
|
124
|
-
end
|
125
|
-
else
|
126
|
-
self.class.ancestors
|
127
|
-
end
|
128
|
-
anc.select{|x| x.kind_of?(Class)}
|
129
|
-
end
|
130
|
-
|
131
|
-
# need concepts of
|
132
|
-
# - attributes
|
133
|
-
# - instance_attributes
|
134
|
-
# - singleton_attributes
|
135
|
-
# - class_attributes
|
136
|
-
|
137
|
-
# send message to all doodle_parents and collect results
|
138
|
-
def doodle_collect_inherited(message)
|
139
|
-
result = []
|
140
|
-
doodle_parents.each do |klass|
|
141
|
-
if klass.respond_to?(message)
|
142
|
-
result.unshift(*klass.__send__(message))
|
143
|
-
else
|
144
|
-
break
|
145
|
-
end
|
146
|
-
end
|
147
|
-
result
|
148
|
-
end
|
149
|
-
private :doodle_collect_inherited
|
150
|
-
end
|
151
|
-
|
152
114
|
# = embrace
|
153
115
|
# the intent of embrace is to provide a way to create directives
|
154
116
|
# that affect all members of a class 'family' without having to
|
@@ -208,26 +170,134 @@ class Doodle
|
|
208
170
|
|
209
171
|
# place to stash bookkeeping info
|
210
172
|
class DoodleInfo
|
211
|
-
attr_accessor :
|
212
|
-
attr_accessor :
|
213
|
-
attr_accessor :
|
173
|
+
attr_accessor :local_attributes
|
174
|
+
attr_accessor :local_validations
|
175
|
+
attr_accessor :local_conversions
|
214
176
|
attr_accessor :validation_on
|
215
177
|
attr_accessor :arg_order
|
216
178
|
attr_accessor :errors
|
217
|
-
attr_accessor :
|
179
|
+
attr_accessor :parent
|
218
180
|
|
219
181
|
def initialize(object)
|
220
|
-
@
|
221
|
-
@
|
182
|
+
@this = object
|
183
|
+
@local_attributes = OrderedHash.new
|
184
|
+
@local_validations = []
|
222
185
|
@validation_on = true
|
223
|
-
@
|
186
|
+
@local_conversions = {}
|
224
187
|
@arg_order = []
|
225
188
|
@errors = []
|
226
|
-
@
|
189
|
+
@parent = nil
|
227
190
|
end
|
191
|
+
# hide from inspect
|
228
192
|
def inspect
|
229
193
|
''
|
230
194
|
end
|
195
|
+
|
196
|
+
# handle errors either by collecting in :errors or raising an exception
|
197
|
+
def handle_error(name, *args)
|
198
|
+
# don't include duplicates (FIXME: hacky - shouldn't have duplicates in the first place)
|
199
|
+
if !errors.include?([name, *args])
|
200
|
+
errors << [name, *args]
|
201
|
+
end
|
202
|
+
if Doodle.raise_exception_on_error
|
203
|
+
raise(*args)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# provide an alternative inheritance chain that works for singleton
|
208
|
+
# classes as well as modules, classes and instances
|
209
|
+
def parents
|
210
|
+
anc = if @this.respond_to?(:ancestors)
|
211
|
+
if @this.ancestors.include?(@this)
|
212
|
+
@this.ancestors[1..-1]
|
213
|
+
else
|
214
|
+
# singletons have no doodle_parents (they're orphans)
|
215
|
+
[]
|
216
|
+
end
|
217
|
+
else
|
218
|
+
@this.class.ancestors
|
219
|
+
end
|
220
|
+
anc.select{|x| x.kind_of?(Class)}
|
221
|
+
end
|
222
|
+
|
223
|
+
# send message to all doodle_parents and collect results
|
224
|
+
def collect_inherited(message)
|
225
|
+
result = []
|
226
|
+
parents.each do |klass|
|
227
|
+
if klass.respond_to?(message)
|
228
|
+
result.unshift(*klass.__send__(message))
|
229
|
+
else
|
230
|
+
break
|
231
|
+
end
|
232
|
+
end
|
233
|
+
result
|
234
|
+
end
|
235
|
+
|
236
|
+
def handle_inherited_hash(tf, method)
|
237
|
+
if tf
|
238
|
+
collect_inherited(method).inject(OrderedHash.new){ |hash, item|
|
239
|
+
hash.merge(OrderedHash[*item])
|
240
|
+
}.merge(@this.__send__(method))
|
241
|
+
else
|
242
|
+
@this.__send__(method)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# returns array of Attributes
|
247
|
+
# - if tf == true, returns all inherited attributes
|
248
|
+
# - if tf == false, returns only those attributes defined in the current object/class
|
249
|
+
def attributes(tf = true)
|
250
|
+
results = handle_inherited_hash(tf, :doodle_local_attributes)
|
251
|
+
# if an instance, include the singleton_class attributes
|
252
|
+
if !@this.kind_of?(Class) && @this.singleton_class.doodle.respond_to?(:attributes)
|
253
|
+
results = results.merge(@this.singleton_class.doodle_attributes)
|
254
|
+
end
|
255
|
+
results
|
256
|
+
end
|
257
|
+
|
258
|
+
def class_attributes
|
259
|
+
attrs = OrderedHash.new
|
260
|
+
if @this.kind_of?(Class)
|
261
|
+
attrs = collect_inherited(:class_attributes).inject(OrderedHash.new){ |hash, item|
|
262
|
+
hash.merge(OrderedHash[*item])
|
263
|
+
}.merge(@this.singleton_class.respond_to?(:doodle_attributes) ? @this.singleton_class.doodle_attributes : { })
|
264
|
+
attrs
|
265
|
+
else
|
266
|
+
@this.class.class_attributes
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def validations(tf = true)
|
271
|
+
if tf
|
272
|
+
# note: validations are handled differently to attributes and
|
273
|
+
# conversions because ~all~ validations apply (so are stored
|
274
|
+
# as an array), whereas attributes and conversions are keyed
|
275
|
+
# by name and kind respectively, so only the most recent
|
276
|
+
# applies
|
277
|
+
|
278
|
+
local_validations + collect_inherited(:doodle_local_validations)
|
279
|
+
else
|
280
|
+
local_validations
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def lookup_attribute(name)
|
285
|
+
# (look at singleton attributes first)
|
286
|
+
# fixme[this smells like a hack to me]
|
287
|
+
if @this.class == Class
|
288
|
+
class_attributes[name]
|
289
|
+
else
|
290
|
+
attributes[name]
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# returns hash of conversions
|
295
|
+
# - if tf == true, returns all inherited conversions
|
296
|
+
# - if tf == false, returns only those conversions defined in the current object/class
|
297
|
+
def conversions(tf = true)
|
298
|
+
handle_inherited_hash(tf, :doodle_local_conversions)
|
299
|
+
end
|
300
|
+
|
231
301
|
end
|
232
302
|
|
233
303
|
# what it says on the tin :) various hacks to hide @__doodle__ variable
|
@@ -238,19 +308,36 @@ class Doodle
|
|
238
308
|
define_method :instance_variables do
|
239
309
|
meth.bind(self).call.reject{ |x| x.to_s =~ /@__doodle__/}
|
240
310
|
end
|
241
|
-
|
242
311
|
# hide @__doodle__ from inspect
|
243
312
|
def inspect
|
244
313
|
super.gsub(/\s*@__doodle__=,/,'').gsub(/,?\s*@__doodle__=/,'')
|
245
314
|
end
|
315
|
+
# fix for pp
|
316
|
+
def pretty_print(q)
|
317
|
+
q.pp_object(self)
|
318
|
+
end
|
246
319
|
end
|
247
320
|
|
321
|
+
class DataTypeHolder
|
322
|
+
attr_accessor :klass
|
323
|
+
def initialize(klass, &block)
|
324
|
+
@klass = klass
|
325
|
+
instance_eval(&block) if block_given?
|
326
|
+
end
|
327
|
+
def define(name, params, block, type_params, &type_block)
|
328
|
+
@klass.class_eval {
|
329
|
+
td = has(name, type_params.merge(params), &type_block)
|
330
|
+
td.instance_eval(&block) if block
|
331
|
+
td
|
332
|
+
}
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
248
336
|
# the core module of Doodle - however, to get most facilities
|
249
337
|
# provided by Doodle without inheriting from Doodle, include
|
250
338
|
# Doodle::Core, not this module
|
251
339
|
module BaseMethods
|
252
340
|
include SelfClass
|
253
|
-
include Inherited
|
254
341
|
include SmokeAndMirrors
|
255
342
|
|
256
343
|
# this is the only way to get at internal values. Note: this is
|
@@ -259,8 +346,30 @@ class Doodle
|
|
259
346
|
def __doodle__
|
260
347
|
@__doodle__ ||= DoodleInfo.new(self)
|
261
348
|
end
|
262
|
-
|
349
|
+
protected :__doodle__
|
263
350
|
|
351
|
+
# set up global datatypes
|
352
|
+
def datatypes(*mods)
|
353
|
+
mods.each do |mod|
|
354
|
+
DataTypeHolder.class_eval { include mod }
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# vector through this method to get to doodle info or enable global
|
359
|
+
# datatypes and provide an interface that allows you to add your own
|
360
|
+
# datatypes to this declaration
|
361
|
+
def doodle(*mods, &block)
|
362
|
+
if mods.size == 0 && !block_given?
|
363
|
+
__doodle__
|
364
|
+
else
|
365
|
+
dh = Doodle::DataTypeHolder.new(self)
|
366
|
+
mods.each do |mod|
|
367
|
+
dh.extend(mod)
|
368
|
+
end
|
369
|
+
dh.instance_eval(&block)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
264
373
|
# helper for Marshal.dump
|
265
374
|
def marshal_dump
|
266
375
|
# note: perhaps should also dump singleton attribute definitions?
|
@@ -273,46 +382,17 @@ class Doodle
|
|
273
382
|
end
|
274
383
|
end
|
275
384
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
def errors
|
280
|
-
__doodle__.errors
|
281
|
-
end
|
282
|
-
|
283
|
-
# clear out the errors collection
|
284
|
-
def clear_errors
|
285
|
-
#pp [:clear_errors, self, caller]
|
286
|
-
__doodle__.errors.clear
|
287
|
-
end
|
288
|
-
|
289
|
-
# handle errors either by collecting in :errors or raising an exception
|
290
|
-
def handle_error(name, *args)
|
291
|
-
# don't include duplicates (FIXME: hacky - shouldn't have duplicates in the first place)
|
292
|
-
if !self.errors.include?([name, *args])
|
293
|
-
self.errors << [name, *args]
|
294
|
-
end
|
295
|
-
if Doodle.raise_exception_on_error
|
296
|
-
raise(*args)
|
297
|
-
end
|
385
|
+
# deprecated methods
|
386
|
+
def doodle_collect_inherited(message)
|
387
|
+
__doodle__.collect_inherited(message)
|
298
388
|
end
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
def _handle_inherited_hash(tf, method)
|
303
|
-
if tf
|
304
|
-
doodle_collect_inherited(method).inject(OrderedHash.new){ |hash, item|
|
305
|
-
hash.merge(OrderedHash[*item])
|
306
|
-
}.merge(__send__(method))
|
307
|
-
else
|
308
|
-
__send__(method)
|
309
|
-
end
|
389
|
+
def doodle_parents
|
390
|
+
__doodle__.parents
|
310
391
|
end
|
311
|
-
private :_handle_inherited_hash
|
312
392
|
|
313
393
|
# return attributes defined in instance
|
314
394
|
def doodle_local_attributes
|
315
|
-
__doodle__.
|
395
|
+
__doodle__.local_attributes
|
316
396
|
end
|
317
397
|
protected :doodle_local_attributes
|
318
398
|
|
@@ -320,77 +400,47 @@ class Doodle
|
|
320
400
|
# - if tf == true, returns all inherited attributes
|
321
401
|
# - if tf == false, returns only those attributes defined in the current object/class
|
322
402
|
def doodle_attributes(tf = true)
|
323
|
-
|
324
|
-
# if an instance, include the singleton_class attributes
|
325
|
-
if !kind_of?(Class) && singleton_class.respond_to?(:doodle_attributes)
|
326
|
-
results = results.merge(singleton_class.doodle_attributes)
|
327
|
-
end
|
328
|
-
results
|
403
|
+
__doodle__.attributes(tf)
|
329
404
|
end
|
330
405
|
|
331
406
|
# return attributes for class
|
332
|
-
def class_attributes
|
333
|
-
|
334
|
-
if self.kind_of?(Class)
|
335
|
-
attrs = doodle_collect_inherited(:class_attributes).inject(OrderedHash.new){ |hash, item|
|
336
|
-
hash.merge(OrderedHash[*item])
|
337
|
-
}.merge(singleton_class.respond_to?(:doodle_attributes) ? singleton_class.doodle_attributes : { })
|
338
|
-
attrs
|
339
|
-
else
|
340
|
-
self.class.class_attributes
|
341
|
-
end
|
407
|
+
def class_attributes
|
408
|
+
__doodle__.class_attributes
|
342
409
|
end
|
343
410
|
|
344
411
|
# the set of conversions defined in the current class (i.e. without inheritance)
|
412
|
+
# deprecated
|
345
413
|
def doodle_local_conversions
|
346
|
-
__doodle__.
|
414
|
+
__doodle__.local_conversions
|
347
415
|
end
|
348
416
|
protected :doodle_local_conversions
|
349
417
|
|
350
418
|
# returns hash of conversions
|
351
419
|
# - if tf == true, returns all inherited conversions
|
352
420
|
# - if tf == false, returns only those conversions defined in the current object/class
|
421
|
+
# deprecated
|
353
422
|
def doodle_conversions(tf = true)
|
354
|
-
|
423
|
+
__doodle__.conversions(tf)
|
355
424
|
end
|
356
425
|
|
357
426
|
# the set of validations defined in the current class (i.e. without inheritance)
|
427
|
+
# deprecated
|
358
428
|
def doodle_local_validations
|
359
|
-
__doodle__.
|
429
|
+
__doodle__.local_validations
|
360
430
|
end
|
361
431
|
protected :doodle_local_validations
|
362
432
|
|
363
433
|
# returns array of Validations
|
364
434
|
# - if tf == true, returns all inherited validations
|
365
435
|
# - if tf == false, returns only those validations defined in the current object/class
|
436
|
+
# deprecated
|
366
437
|
def doodle_validations(tf = true)
|
367
|
-
|
368
|
-
# note: validations are handled differently to attributes and
|
369
|
-
# conversions because ~all~ validations apply (so are stored
|
370
|
-
# as an array), whereas attributes and conversions are keyed
|
371
|
-
# by name and kind respectively, so only the most recent
|
372
|
-
# applies
|
373
|
-
|
374
|
-
doodle_local_validations + doodle_collect_inherited(:doodle_local_validations)
|
375
|
-
else
|
376
|
-
doodle_local_validations
|
377
|
-
end
|
438
|
+
__doodle__.validations(tf)
|
378
439
|
end
|
379
440
|
|
380
|
-
# lookup a single attribute by name, searching the singleton class first
|
381
|
-
def lookup_attribute(name)
|
382
|
-
# (look at singleton attributes first)
|
383
|
-
# fixme[this smells like a hack to me]
|
384
|
-
if self.class == Class
|
385
|
-
class_attributes[name]
|
386
|
-
else
|
387
|
-
doodle_attributes[name]
|
388
|
-
end
|
389
|
-
end
|
390
|
-
private :lookup_attribute
|
391
|
-
|
392
441
|
# either get an attribute value (if no args given) or set it
|
393
442
|
# (using args and/or block)
|
443
|
+
# fixme: move
|
394
444
|
def getter_setter(name, *args, &block)
|
395
445
|
name = name.to_sym
|
396
446
|
if block_given? || args.size > 0
|
@@ -404,52 +454,60 @@ class Doodle
|
|
404
454
|
private :getter_setter
|
405
455
|
|
406
456
|
# get an attribute by name - return default if not otherwise defined
|
457
|
+
# fixme: init deferred blocks are not getting resolved in all cases
|
407
458
|
def _getter(name, &block)
|
459
|
+
#p [:_getter, name]
|
408
460
|
ivar = "@#{name}"
|
409
461
|
if instance_variable_defined?(ivar)
|
410
|
-
|
462
|
+
#p [:_getter, :instance_variable_defined, name, ivar, instance_variable_get(ivar)]
|
411
463
|
instance_variable_get(ivar)
|
412
464
|
else
|
413
465
|
# handle default
|
414
466
|
# Note: use :init => value to cover cases where defaults don't work
|
415
467
|
# (e.g. arrays that disappear when you go out of scope)
|
416
|
-
att = lookup_attribute(name)
|
468
|
+
att = __doodle__.lookup_attribute(name)
|
417
469
|
# special case for class/singleton :init
|
418
|
-
if att.
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
case att.default
|
470
|
+
if att.optional?
|
471
|
+
optional_value = att.init_defined? ? att.init : att.default
|
472
|
+
#p [:optional_value, optional_value]
|
473
|
+
case optional_value
|
423
474
|
when DeferredBlock
|
424
|
-
|
475
|
+
#p [:deferred_block]
|
476
|
+
v = instance_eval(&optional_value.block)
|
425
477
|
when Proc
|
426
|
-
instance_eval(&
|
478
|
+
v = instance_eval(&optional_value)
|
427
479
|
else
|
428
|
-
|
480
|
+
v = optional_value
|
429
481
|
end
|
482
|
+
if att.init_defined?
|
483
|
+
_setter(name, v)
|
484
|
+
end
|
485
|
+
v
|
430
486
|
else
|
431
487
|
# This is an internal error (i.e. shouldn't happen)
|
432
|
-
handle_error name, NoDefaultError, "'#{name}' has no default defined", [caller[-1]]
|
488
|
+
__doodle__.handle_error name, NoDefaultError, "'#{name}' has no default defined", [caller[-1]]
|
433
489
|
end
|
434
490
|
end
|
435
491
|
end
|
436
492
|
private :_getter
|
437
493
|
|
438
494
|
# set an attribute by name - apply validation if defined
|
495
|
+
# fixme: move
|
439
496
|
def _setter(name, *args, &block)
|
440
497
|
##DBG: Doodle::Debug.d { [:_setter, name, args] }
|
498
|
+
#p [:_setter, name, *args]
|
441
499
|
ivar = "@#{name}"
|
442
500
|
if block_given?
|
443
501
|
args.unshift(DeferredBlock.new(block))
|
444
502
|
end
|
445
|
-
if att = lookup_attribute(name)
|
503
|
+
if att = __doodle__.lookup_attribute(name)
|
446
504
|
##DBG: Doodle::Debug.d { [:_setter, name, args] }
|
447
|
-
|
505
|
+
#p [:_setter, :got_att1, name, ivar, *args]
|
448
506
|
v = instance_variable_set(ivar, att.validate(self, *args))
|
449
|
-
|
507
|
+
#p [:_setter, :got_att2, name, ivar, :value, v]
|
450
508
|
#v = instance_variable_set(ivar, *args)
|
451
509
|
else
|
452
|
-
|
510
|
+
#p [:_setter, :no_att, name, *args]
|
453
511
|
##DBG: Doodle::Debug.d { [:_setter, "no attribute"] }
|
454
512
|
v = instance_variable_set(ivar, *args)
|
455
513
|
end
|
@@ -464,7 +522,7 @@ class Doodle
|
|
464
522
|
if block_given?
|
465
523
|
# set the rule for each arg given
|
466
524
|
args.each do |arg|
|
467
|
-
|
525
|
+
__doodle__.local_conversions[arg] = block
|
468
526
|
end
|
469
527
|
else
|
470
528
|
convert(self, *args)
|
@@ -475,9 +533,9 @@ class Doodle
|
|
475
533
|
def must(constraint = 'be valid', &block)
|
476
534
|
if block.nil?
|
477
535
|
# is this really useful? do I really want it?
|
478
|
-
|
536
|
+
__doodle__.local_validations << Validation.new(constraint, &proc { |v| v.instance_eval(constraint) })
|
479
537
|
else
|
480
|
-
|
538
|
+
__doodle__.local_validations << Validation.new(constraint, &block)
|
481
539
|
end
|
482
540
|
end
|
483
541
|
|
@@ -486,19 +544,20 @@ class Doodle
|
|
486
544
|
if args.size > 0
|
487
545
|
# todo[figure out how to handle kind being specified twice?]
|
488
546
|
@kind = args.first
|
489
|
-
|
547
|
+
__doodle__.local_validations << (Validation.new("be #{@kind}") { |x| x.class <= @kind })
|
490
548
|
else
|
491
549
|
@kind
|
492
550
|
end
|
493
551
|
end
|
494
552
|
|
495
553
|
# convert a value according to conversion rules
|
554
|
+
# fixme: move
|
496
555
|
def convert(owner, *args)
|
497
556
|
#!p [:convert, 1, owner, args]
|
498
557
|
begin
|
499
558
|
args = args.map do |value|
|
500
559
|
#!p [:convert, 2, value]
|
501
|
-
if (converter =
|
560
|
+
if (converter = __doodle__.conversions[value.class])
|
502
561
|
#!p [:convert, 3, value]
|
503
562
|
value = converter[*args]
|
504
563
|
#!p [:convert, 4, value]
|
@@ -507,7 +566,7 @@ class Doodle
|
|
507
566
|
# try to find nearest ancestor
|
508
567
|
ancestors = value.class.ancestors
|
509
568
|
#!p [:convert, 6, ancestors]
|
510
|
-
matches = ancestors &
|
569
|
+
matches = ancestors & __doodle__.conversions.keys
|
511
570
|
#!p [:convert, 7, matches]
|
512
571
|
indexed_matches = matches.map{ |x| ancestors.index(x)}
|
513
572
|
#!p [:convert, 8, indexed_matches]
|
@@ -515,7 +574,7 @@ class Doodle
|
|
515
574
|
#!p [:convert, 9]
|
516
575
|
converter_class = ancestors[indexed_matches.min]
|
517
576
|
#!p [:convert, 10, converter_class]
|
518
|
-
if converter =
|
577
|
+
if converter = __doodle__.conversions[converter_class]
|
519
578
|
#!p [:convert, 11, converter]
|
520
579
|
value = converter[*args]
|
521
580
|
#!p [:convert, 12, value]
|
@@ -525,7 +584,7 @@ class Doodle
|
|
525
584
|
value
|
526
585
|
end
|
527
586
|
rescue Exception => e
|
528
|
-
owner.handle_error name, ConversionError, "#{owner.kind_of?(Class) ? owner : owner.class} - #{e.to_s}", [caller[-1]]
|
587
|
+
owner.__doodle__.handle_error name, ConversionError, "#{owner.kind_of?(Class) ? owner : owner.class} - #{e.to_s}", [caller[-1]]
|
529
588
|
end
|
530
589
|
if args.size > 1
|
531
590
|
args
|
@@ -535,21 +594,23 @@ class Doodle
|
|
535
594
|
end
|
536
595
|
|
537
596
|
# validate that args meet rules defined with +must+
|
597
|
+
# fixme: move
|
538
598
|
def validate(owner, *args)
|
539
599
|
##DBG: Doodle::Debug.d { [:validate, self, :owner, owner, :args, args ] }
|
540
600
|
#!p [:validate, :before_conversion, args]
|
541
601
|
value = convert(owner, *args)
|
542
602
|
#!p [:validate, :after_conversion, args, :becomes, value]
|
543
|
-
|
603
|
+
__doodle__.validations.each do |v|
|
544
604
|
##DBG: Doodle::Debug.d { [:validate, self, v, args, value] }
|
545
605
|
if !v.block[value]
|
546
|
-
owner.handle_error name, ValidationError, "#{owner.kind_of?(Class) ? owner : owner.class}.#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })", [caller[-1]]
|
606
|
+
owner.__doodle__.handle_error name, ValidationError, "#{owner.kind_of?(Class) ? owner : owner.class}.#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })", [caller[-1]]
|
547
607
|
end
|
548
608
|
end
|
549
609
|
value
|
550
610
|
end
|
551
611
|
|
552
612
|
# define a getter_setter
|
613
|
+
# fixme: move
|
553
614
|
def define_getter_setter(name, *args, &block)
|
554
615
|
# need to use string eval because passing block
|
555
616
|
sc_eval "def #{name}(*args, &block); getter_setter(:#{name}, *args, &block); end", __FILE__, __LINE__
|
@@ -591,70 +652,17 @@ class Doodle
|
|
591
652
|
#
|
592
653
|
def has(*args, &block)
|
593
654
|
#DBG: Doodle::Debug.d { [:has, self, self.class, args] }
|
594
|
-
# d { [:has2, name, args] }
|
595
|
-
|
596
|
-
# fixme: this should be in generic Attribute - perhaps class method
|
597
|
-
# how much of this can be handled by initialize_from_hash?
|
598
|
-
# or at least in DoodleAttribute.from(params)
|
599
|
-
|
600
|
-
key_values, positional_args = args.partition{ |x| x.kind_of?(Hash)}
|
601
|
-
if positional_args.size > 0
|
602
|
-
name = positional_args.shift.to_sym
|
603
|
-
params = { :name => name }
|
604
|
-
else
|
605
|
-
params = { }
|
606
|
-
end
|
607
|
-
params = key_values.inject(params){ |acc, item| acc.merge(item)}
|
608
|
-
#DBG: Doodle::Debug.d { [:has, self, self.class, params] }
|
609
|
-
if !params.key?(:name)
|
610
|
-
handle_error name, ArgumentError, "#{self.class} must have a name"
|
611
|
-
else
|
612
|
-
name = params[:name].to_sym
|
613
|
-
end
|
614
|
-
handle_error name, ArgumentError, "#{self.class} has too many arguments" if positional_args.size > 0
|
615
|
-
|
616
|
-
if params.key?(:collect) && !params.key?(:using)
|
617
|
-
if params.key?(:key)
|
618
|
-
params[:using] = KeyedAttribute
|
619
|
-
else
|
620
|
-
params[:using] = AppendableAttribute
|
621
|
-
end
|
622
|
-
end
|
623
655
|
|
624
|
-
|
625
|
-
# this in generic CollectorAttribute class
|
626
|
-
# collector from(Hash)
|
627
|
-
if collector.kind_of?(Hash)
|
628
|
-
collector_name, collector_class = collector.to_a[0]
|
629
|
-
else
|
630
|
-
# if Capitalized word given, treat as classname
|
631
|
-
# and create collector for specific class
|
632
|
-
collector_class = collector.to_s
|
633
|
-
#p [:collector_klass, collector_klass]
|
634
|
-
collector_name = Utils.snake_case(collector_class.split(/::/).last)
|
635
|
-
#p [:collector_name, collector_name]
|
636
|
-
if collector_class !~ /^[A-Z]/
|
637
|
-
collector_class = nil
|
638
|
-
end
|
639
|
-
#!p [:collector_klass, collector_klass, params[:init]]
|
640
|
-
end
|
641
|
-
params[:collector_class] = collector_class
|
642
|
-
params[:collector_name] = collector_name
|
643
|
-
end
|
644
|
-
|
656
|
+
params = DoodleAttribute.params_from_args(self, *args)
|
645
657
|
# get specialized attribute class or use default
|
646
658
|
attribute_class = params.delete(:using) || DoodleAttribute
|
647
659
|
|
648
660
|
# could this be handled in DoodleAttribute?
|
649
661
|
# define getter setter before setting up attribute
|
650
|
-
define_getter_setter name, *args, &block
|
651
|
-
params[:doodle_owner] = self
|
662
|
+
define_getter_setter params[:name], *args, &block
|
652
663
|
#p [:attribute, attribute_class, params]
|
653
|
-
|
654
|
-
|
655
|
-
attribute
|
664
|
+
__doodle__.local_attributes[params[:name]] = attribute_class.new(params, &block)
|
656
665
|
end
|
657
|
-
|
658
666
|
|
659
667
|
# define order for positional arguments
|
660
668
|
def arg_order(*args)
|
@@ -662,32 +670,44 @@ class Doodle
|
|
662
670
|
begin
|
663
671
|
args = args.uniq
|
664
672
|
args.each do |x|
|
665
|
-
handle_error :arg_order, ArgumentError, "#{x} not a Symbol" if !(x.class <= Symbol)
|
666
|
-
handle_error :arg_order, NameError, "#{x} not an attribute name" if !doodle_attributes.keys.include?(x)
|
673
|
+
__doodle__.handle_error :arg_order, ArgumentError, "#{x} not a Symbol", [caller[-1]] if !(x.class <= Symbol)
|
674
|
+
__doodle__.handle_error :arg_order, NameError, "#{x} not an attribute name", [caller[-1]] if !doodle_attributes.keys.include?(x)
|
667
675
|
end
|
668
676
|
__doodle__.arg_order = args
|
669
677
|
rescue Exception => e
|
670
|
-
handle_error :arg_order, InvalidOrderError, e.to_s, [caller[-1]]
|
678
|
+
__doodle__.handle_error :arg_order, InvalidOrderError, e.to_s, [caller[-1]]
|
671
679
|
end
|
672
680
|
else
|
673
|
-
__doodle__.arg_order + (
|
681
|
+
__doodle__.arg_order + (__doodle__.attributes.keys - __doodle__.arg_order)
|
674
682
|
end
|
675
683
|
end
|
676
684
|
|
685
|
+
# fixme: move
|
677
686
|
def get_init_values(tf = true)
|
678
|
-
|
679
|
-
|
680
|
-
hash[n] =
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
687
|
+
__doodle__.attributes(tf).select{|n, a| a.init_defined? }.inject({}) {|hash, (n, a)|
|
688
|
+
#p [:get_init_values, a.name]
|
689
|
+
hash[n] = case a.init
|
690
|
+
when NilClass, TrueClass, FalseClass, Fixnum, Float, Bignum
|
691
|
+
# uncloneable values
|
692
|
+
#p [:get_init_values, :special, a.name, a.init]
|
693
|
+
a.init
|
694
|
+
when DeferredBlock
|
695
|
+
#p [:get_init_values, self, DeferredBlock, a.name]
|
696
|
+
begin
|
685
697
|
instance_eval(&a.init.block)
|
686
|
-
|
687
|
-
|
698
|
+
rescue Object => e
|
699
|
+
#p [:exception_in_deferred_block, e]
|
700
|
+
raise
|
701
|
+
end
|
702
|
+
else
|
703
|
+
#p [:get_init_values, :clone, a.name]
|
704
|
+
begin
|
705
|
+
a.init.clone
|
706
|
+
rescue Exception => e
|
707
|
+
warn "tried to clone #{a.init.class} in :init option"
|
708
|
+
#p [:get_init_values, :exception, a.name, e]
|
709
|
+
a.init
|
688
710
|
end
|
689
|
-
rescue Exception => e
|
690
|
-
a.init
|
691
711
|
end
|
692
712
|
hash
|
693
713
|
}
|
@@ -695,6 +715,7 @@ class Doodle
|
|
695
715
|
private :get_init_values
|
696
716
|
|
697
717
|
# return true if instance variable +name+ defined
|
718
|
+
# fixme: move
|
698
719
|
def ivar_defined?(name)
|
699
720
|
instance_variable_defined?("@#{name}")
|
700
721
|
end
|
@@ -705,14 +726,14 @@ class Doodle
|
|
705
726
|
def validate!(all = true)
|
706
727
|
##DBG: Doodle::Debug.d { [:validate!, all, caller] }
|
707
728
|
if all
|
708
|
-
|
729
|
+
__doodle__.errors.clear
|
709
730
|
end
|
710
731
|
if __doodle__.validation_on
|
711
732
|
if self.class == Class
|
712
|
-
attribs = class_attributes
|
733
|
+
attribs = __doodle__.class_attributes
|
713
734
|
##DBG: Doodle::Debug.d { [:validate!, "using class_attributes", class_attributes] }
|
714
735
|
else
|
715
|
-
attribs =
|
736
|
+
attribs = __doodle__.attributes
|
716
737
|
##DBG: Doodle::Debug.d { [:validate!, "using instance_attributes", doodle_attributes] }
|
717
738
|
end
|
718
739
|
attribs.each do |name, att|
|
@@ -729,21 +750,21 @@ class Doodle
|
|
729
750
|
##DBG: Doodle::Debug.d { [:validate!, :optional, name ]}
|
730
751
|
break
|
731
752
|
elsif self.class != Class
|
732
|
-
handle_error name, Doodle::ValidationError, "#{self} missing required attribute '#{name}'", [caller[-1]]
|
753
|
+
__doodle__.handle_error name, Doodle::ValidationError, "#{self} missing required attribute '#{name}'", [caller[-1]]
|
733
754
|
end
|
734
755
|
end
|
735
756
|
|
736
757
|
# now apply instance level validations
|
737
758
|
|
738
759
|
##DBG: Doodle::Debug.d { [:validate!, "validations", doodle_validations ]}
|
739
|
-
|
760
|
+
__doodle__.validations.each do |v|
|
740
761
|
##DBG: Doodle::Debug.d { [:validate!, self, v ] }
|
741
762
|
begin
|
742
763
|
if !instance_eval(&v.block)
|
743
|
-
handle_error self, ValidationError, "#{ self.class } must #{ v.message }", [caller[-1]]
|
764
|
+
__doodle__.handle_error self, ValidationError, "#{ self.class } must #{ v.message }", [caller[-1]]
|
744
765
|
end
|
745
766
|
rescue Exception => e
|
746
|
-
handle_error self, ValidationError, e.to_s, [caller[-1]]
|
767
|
+
__doodle__.handle_error self, ValidationError, e.to_s, [caller[-1]]
|
747
768
|
end
|
748
769
|
end
|
749
770
|
end
|
@@ -753,6 +774,7 @@ class Doodle
|
|
753
774
|
|
754
775
|
# turn off validation, execute block, then set validation to same
|
755
776
|
# state as it was before +defer_validation+ was called - can be nested
|
777
|
+
# fixme: move
|
756
778
|
def defer_validation(&block)
|
757
779
|
old_validation = __doodle__.validation_on
|
758
780
|
__doodle__.validation_on = false
|
@@ -769,6 +791,7 @@ class Doodle
|
|
769
791
|
# helper function to initialize from hash - this is safe to use
|
770
792
|
# after initialization (validate! is called if this method is
|
771
793
|
# called after initialization)
|
794
|
+
# fixme?
|
772
795
|
def doodle_initialize_from_hash(*args)
|
773
796
|
#!p [:doodle_initialize_from_hash, :args, *args]
|
774
797
|
defer_validation do
|
@@ -779,11 +802,12 @@ class Doodle
|
|
779
802
|
#!p [self.class, :doodle_initialize_from_hash, :key_values, key_values, :args, args]
|
780
803
|
|
781
804
|
# set up initial values with ~clones~ of specified values (so not shared between instances)
|
782
|
-
init_values = get_init_values
|
805
|
+
#init_values = get_init_values
|
783
806
|
#!p [:init_values, init_values]
|
784
807
|
|
785
808
|
# match up positional args with attribute names (from arg_order) using idiom to create hash from array of assocs
|
786
|
-
arg_keywords = init_values.merge(Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))])
|
809
|
+
#arg_keywords = init_values.merge(Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))])
|
810
|
+
arg_keywords = Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))]
|
787
811
|
#!p [self.class, :doodle_initialize_from_hash, :arg_keywords, arg_keywords]
|
788
812
|
|
789
813
|
# merge all hash args into one
|
@@ -805,12 +829,20 @@ class Doodle
|
|
805
829
|
# create attributes
|
806
830
|
key_values.keys.each do |key|
|
807
831
|
#DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :setting, key, key_values[key]] }
|
808
|
-
|
832
|
+
#p [self.class, :doodle_initialize_from_hash, :setting, key, key_values[key]]
|
809
833
|
if respond_to?(key)
|
810
834
|
__send__(key, key_values[key])
|
811
835
|
else
|
812
836
|
# raise error if not defined
|
813
|
-
handle_error key, Doodle::UnknownAttributeError, "unknown attribute '#{key}' #{key_values[key].inspect}"
|
837
|
+
__doodle__.handle_error key, Doodle::UnknownAttributeError, "unknown attribute '#{key}' #{key_values[key].inspect}", [caller[-1]]
|
838
|
+
end
|
839
|
+
end
|
840
|
+
# do init_values after user supplied values so init blocks can depend on user supplied values
|
841
|
+
#p [:getting_init_values, instance_variables]
|
842
|
+
init_values = get_init_values
|
843
|
+
init_values.each do |key, value|
|
844
|
+
if !key_values.key?(key) && respond_to?(key)
|
845
|
+
__send__(key, value)
|
814
846
|
end
|
815
847
|
end
|
816
848
|
end
|
@@ -819,8 +851,9 @@ class Doodle
|
|
819
851
|
|
820
852
|
# return containing object (set during initialization)
|
821
853
|
# (named doodle_parent to avoid clash with ActiveSupport)
|
854
|
+
# fixme: move
|
822
855
|
def doodle_parent
|
823
|
-
__doodle__.
|
856
|
+
__doodle__.parent
|
824
857
|
end
|
825
858
|
|
826
859
|
# object can be initialized from a mixture of positional arguments,
|
@@ -831,7 +864,7 @@ class Doodle
|
|
831
864
|
super
|
832
865
|
end
|
833
866
|
__doodle__.validation_on = true
|
834
|
-
__doodle__.
|
867
|
+
__doodle__.parent = Doodle.context[-1]
|
835
868
|
Doodle.context.push(self)
|
836
869
|
defer_validation do
|
837
870
|
doodle_initialize_from_hash(*args)
|
@@ -871,10 +904,10 @@ class Doodle
|
|
871
904
|
method_defined = begin
|
872
905
|
method(name)
|
873
906
|
true
|
874
|
-
rescue
|
907
|
+
rescue Object
|
875
908
|
false
|
876
909
|
end
|
877
|
-
|
910
|
+
|
878
911
|
if name =~ Factory::RX_IDENTIFIER && !method_defined && !klass.respond_to?(name) && !eval("respond_to?(:#{name})", TOPLEVEL_BINDING)
|
879
912
|
eval("def #{ name }(*args, &block); ::#{name}.new(*args, &block); end", ::TOPLEVEL_BINDING, __FILE__, __LINE__)
|
880
913
|
end
|
@@ -926,7 +959,65 @@ class Doodle
|
|
926
959
|
# It is used to provide a context for defining #must and #from rules
|
927
960
|
#
|
928
961
|
class DoodleAttribute < Doodle
|
929
|
-
#
|
962
|
+
# note: using extend with a module causes an infinite loop in 1.9
|
963
|
+
# hence the inline
|
964
|
+
class << self
|
965
|
+
# rewrite rules for the argument list to #has
|
966
|
+
def params_from_args(owner, *args)
|
967
|
+
key_values, positional_args = args.partition{ |x| x.kind_of?(Hash)}
|
968
|
+
params = { }
|
969
|
+
if positional_args.size > 0
|
970
|
+
name = positional_args.shift
|
971
|
+
case name
|
972
|
+
# has Person --> has :person, :kind => Person
|
973
|
+
when Class
|
974
|
+
params[:name] = Utils.snake_case(name.to_s.split(/::/).last)
|
975
|
+
params[:kind] = name
|
976
|
+
else
|
977
|
+
params[:name] = name.to_s.to_sym
|
978
|
+
end
|
979
|
+
end
|
980
|
+
params = key_values.inject(params){ |acc, item| acc.merge(item)}
|
981
|
+
#DBG: Doodle::Debug.d { [:has, self, self.class, params] }
|
982
|
+
if !params.key?(:name)
|
983
|
+
__doodle__.handle_error name, ArgumentError, "#{self.class} must have a name", [caller[-1]]
|
984
|
+
else
|
985
|
+
name = params[:name].to_sym
|
986
|
+
end
|
987
|
+
__doodle__.handle_error name, ArgumentError, "#{self.class} has too many arguments", [caller[-1]] if positional_args.size > 0
|
988
|
+
|
989
|
+
if collector = params.delete(:collect)
|
990
|
+
if !params.key?(:using)
|
991
|
+
if params.key?(:key)
|
992
|
+
params[:using] = KeyedAttribute
|
993
|
+
else
|
994
|
+
params[:using] = AppendableAttribute
|
995
|
+
end
|
996
|
+
end
|
997
|
+
# this in generic CollectorAttribute class
|
998
|
+
# collector from(Hash)
|
999
|
+
if collector.kind_of?(Hash)
|
1000
|
+
collector_name, collector_class = collector.to_a[0]
|
1001
|
+
else
|
1002
|
+
# if Capitalized word given, treat as classname
|
1003
|
+
# and create collector for specific class
|
1004
|
+
collector_class = collector.to_s
|
1005
|
+
#p [:collector_klass, collector_klass]
|
1006
|
+
collector_name = Utils.snake_case(collector_class.split(/::/).last)
|
1007
|
+
#p [:collector_name, collector_name]
|
1008
|
+
if collector_class !~ /^[A-Z]/
|
1009
|
+
collector_class = nil
|
1010
|
+
end
|
1011
|
+
#!p [:collector_klass, collector_klass, params[:init]]
|
1012
|
+
end
|
1013
|
+
params[:collector_class] = collector_class
|
1014
|
+
params[:collector_name] = collector_name
|
1015
|
+
end
|
1016
|
+
params[:doodle_owner] = owner
|
1017
|
+
params
|
1018
|
+
end
|
1019
|
+
end
|
1020
|
+
|
930
1021
|
# must define these methods before using them in #has below
|
931
1022
|
|
932
1023
|
# hack: bump off +validate!+ for Attributes - maybe better way of doing
|
@@ -995,7 +1086,7 @@ class Doodle
|
|
995
1086
|
def resolve_value(value)
|
996
1087
|
if value.kind_of?(collector_class)
|
997
1088
|
value
|
998
|
-
elsif collector_class.
|
1089
|
+
elsif collector_class.__doodle__.conversions.key?(value.class)
|
999
1090
|
collector_class.from(value)
|
1000
1091
|
else
|
1001
1092
|
collector_class.new(value)
|
data/lib/doodle/datatypes.rb
CHANGED
@@ -3,40 +3,6 @@ $:.unshift(File.join(File.dirname(__FILE__), '.'))
|
|
3
3
|
|
4
4
|
require 'doodle'
|
5
5
|
|
6
|
-
class Doodle
|
7
|
-
class DataTypeHolder
|
8
|
-
attr_accessor :klass
|
9
|
-
def initialize(klass, &block)
|
10
|
-
@klass = klass
|
11
|
-
instance_eval(&block) if block_given?
|
12
|
-
end
|
13
|
-
def define(name, params, block, type_params, &type_block)
|
14
|
-
@klass.class_eval {
|
15
|
-
td = has(name, type_params.merge(params), &type_block)
|
16
|
-
td.instance_eval(&block) if block
|
17
|
-
td
|
18
|
-
}
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
# set up global datatypes
|
23
|
-
def self.datatypes(*mods)
|
24
|
-
mods.each do |mod|
|
25
|
-
DataTypeHolder.class_eval { include mod }
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# enable global datatypes and provide an interface that allows you
|
30
|
-
# to add your own datatypes to this declaration
|
31
|
-
def self.doodle(*mods, &block)
|
32
|
-
dh = Doodle::DataTypeHolder.new(self)
|
33
|
-
mods.each do |mod|
|
34
|
-
dh.extend(mod)
|
35
|
-
end
|
36
|
-
dh.instance_eval(&block)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
6
|
### user code
|
41
7
|
require 'date'
|
42
8
|
require 'uri'
|
data/lib/doodle/version.rb
CHANGED
data/spec/has_spec.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe Doodle, 'has Class' do
|
4
|
+
temporary_constant :Foo, :Bar do
|
5
|
+
it "should convert 'has Bar' into 'has :bar, :kind => Bar'" do
|
6
|
+
class Bar
|
7
|
+
end
|
8
|
+
class Foo < Doodle
|
9
|
+
has Bar
|
10
|
+
end
|
11
|
+
att = Foo.doodle_attributes.values.first
|
12
|
+
att.name.should_be :bar
|
13
|
+
att.kind.should_be Bar
|
14
|
+
end
|
15
|
+
it "should allow overriding name of attribute when using 'has Bar'" do
|
16
|
+
class Bar
|
17
|
+
end
|
18
|
+
class Foo < Doodle
|
19
|
+
has Bar, :name => :baz
|
20
|
+
end
|
21
|
+
att = Foo.doodle_attributes.values.first
|
22
|
+
att.name.should_be :baz
|
23
|
+
att.kind.should_be Bar
|
24
|
+
end
|
25
|
+
it "should convert class name to snakecase when using CamelCase class constant" do
|
26
|
+
class AudioClip
|
27
|
+
end
|
28
|
+
class Foo < Doodle
|
29
|
+
has AudioClip
|
30
|
+
end
|
31
|
+
att = Foo.doodle_attributes.values.first
|
32
|
+
att.name.should_be :audio_clip
|
33
|
+
att.kind.should_be AudioClip
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: doodle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean O'Halpin
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-05-
|
12
|
+
date: 2008-05-12 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -145,4 +145,5 @@ test_files:
|
|
145
145
|
- spec/conversion_spec.rb
|
146
146
|
- spec/class_validation_spec.rb
|
147
147
|
- spec/doodle_spec.rb
|
148
|
+
- spec/has_spec.rb
|
148
149
|
- spec/doodle_context_spec.rb
|