doodle 0.0.10 → 0.1.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.
Files changed (67) hide show
  1. data/CREDITS +22 -0
  2. data/{ChangeLog → History.txt} +22 -3
  3. data/License.txt +20 -0
  4. data/Manifest.txt +61 -0
  5. data/README.txt +166 -0
  6. data/Rakefile +4 -0
  7. data/config/hoe.rb +77 -0
  8. data/config/requirements.rb +15 -0
  9. data/examples/doodle-errors.rb +25 -0
  10. data/examples/event-location.rb +3 -7
  11. data/examples/example-01.rb +1 -1
  12. data/examples/example-01.rdoc +3 -3
  13. data/examples/example-02.rb +15 -4
  14. data/examples/example-02.rdoc +17 -5
  15. data/examples/mail-datatypes.rb +104 -0
  16. data/examples/mail.rb +85 -0
  17. data/examples/parent.rb +40 -0
  18. data/examples/profile-options.rb +67 -0
  19. data/examples/smtp_tls.rb +65 -0
  20. data/examples/test-datatypes.rb +55 -0
  21. data/examples/yaml-example.rb +40 -0
  22. data/examples/yaml-example2.rb +42 -0
  23. data/lib/doodle.rb +364 -301
  24. data/lib/doodle/datatypes.rb +148 -0
  25. data/lib/doodle/rfc822.rb +31 -0
  26. data/lib/doodle/utils.rb +13 -0
  27. data/lib/doodle/version.rb +9 -0
  28. data/log/debug.log +0 -0
  29. data/script/console +10 -0
  30. data/script/destroy +14 -0
  31. data/script/generate +14 -0
  32. data/script/txt2html +82 -0
  33. data/setup.rb +1585 -0
  34. data/spec/arg_order_spec.rb +5 -5
  35. data/spec/attributes_spec.rb +66 -24
  36. data/spec/bugs_spec.rb +109 -6
  37. data/spec/class_spec.rb +7 -4
  38. data/spec/class_validation_spec.rb +46 -0
  39. data/spec/class_var_spec.rb +76 -0
  40. data/spec/collector_spec.rb +16 -30
  41. data/spec/conversion_spec.rb +8 -3
  42. data/spec/defaults_spec.rb +4 -4
  43. data/spec/doodle_context_spec.rb +3 -4
  44. data/spec/doodle_spec.rb +25 -15
  45. data/spec/extra_args_spec.rb +1 -1
  46. data/spec/factory_spec.rb +3 -3
  47. data/spec/init_spec.rb +11 -11
  48. data/spec/new_doodle_spec.rb +19 -0
  49. data/spec/required_spec.rb +1 -1
  50. data/spec/serialization_spec.rb +3 -6
  51. data/spec/singleton_spec.rb +5 -5
  52. data/spec/spec.opts +1 -0
  53. data/spec/spec_helper.rb +44 -0
  54. data/spec/superclass_spec.rb +6 -5
  55. data/spec/validation2_spec.rb +248 -0
  56. data/spec/validation_spec.rb +26 -37
  57. data/tasks/deployment.rake +34 -0
  58. data/tasks/environment.rake +7 -0
  59. data/tasks/rspec.rake +21 -0
  60. data/tasks/website.rake +17 -0
  61. metadata +76 -20
  62. data/COPYING +0 -18
  63. data/README +0 -57
  64. data/examples/event.rb +0 -39
  65. data/examples/example-03.rb +0 -45
  66. data/examples/example-03.rdoc +0 -55
  67. data/lib/semantic.cache +0 -8
@@ -0,0 +1,55 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $:.unshift(File.join(File.dirname(__FILE__), '.'))
3
+
4
+ require 'doodle/datatypes'
5
+ require 'doodle/utils'
6
+
7
+ class DateRange < Doodle
8
+ doodle do
9
+ date :start
10
+ date :end do
11
+ default { start + 1 }
12
+ end
13
+ version :version, :default => "0.0.1"
14
+ end
15
+ end
16
+
17
+ #pp DateRange.instance_methods(false)
18
+
19
+ module UserTypes
20
+ # include Doodle::DataTypes
21
+ def printable(name, params = { }, &block)
22
+ string(name, params, &block).instance_eval do
23
+ must "not contain non-printing characters" do |s|
24
+ s !~ /[\x00-\x1F]/
25
+ end
26
+ end
27
+ end
28
+ def name(name, params = { }, &block)
29
+ printable(name, { :size => 1..255 }.merge(params), &block)
30
+ end
31
+ end
32
+
33
+ class Person < Doodle
34
+ doodle UserTypes do
35
+ # string :name, :max => 10
36
+ name :name, :size => 3..10
37
+ integer :age
38
+ email :email, :default => ''
39
+ end
40
+ end
41
+
42
+ pp try { DateRange "2007-01-18", :version => [0,0,9] }
43
+ pp try { Person 'Sean', '45', 'someone@example.com' }
44
+ pp try { Person 'Sean', '45' }
45
+ pp try { Person 'Sean', 'old' }
46
+ pp try { Person 'Sean', 45, 'this is not an email address' }
47
+ pp try { Person 'This name is too long', 45 }
48
+ pp try { Person 'Sean', 45, 42 }
49
+ pp try { Person 'A', 45 }
50
+ pp try { Person '123', 45 }
51
+ pp try { Person '', 45 }
52
+ # pp try {
53
+ # person = Person 'Sean', 45
54
+ # person.name.silly
55
+ # }
@@ -0,0 +1,40 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'doodle'
3
+ require 'doodle/utils'
4
+ require 'yaml'
5
+
6
+ class Foo < Doodle
7
+ has :name, :kind => String
8
+ end
9
+
10
+ # load valid data
11
+ str = %[
12
+ --- !ruby/object:Foo
13
+ name: Stimpy
14
+ ]
15
+ bar = nil
16
+ rv = try do
17
+ bar = YAML::load(str).validate!
18
+ end
19
+ rv # => #<Foo:0xb7b02cc0 @name="Stimpy">
20
+ bar # => #<Foo:0xb7b02cc0 @name="Stimpy">
21
+
22
+ str = %[
23
+ --- !ruby/object:Foo
24
+ name: 1
25
+ ]
26
+
27
+ # load invalid data
28
+ baz = nil
29
+ rv = try do
30
+ baz = YAML::load(str).validate!
31
+ end
32
+ rv # => #<Doodle::ValidationError: name must be String - got Fixnum(1)>
33
+ baz # => nil
34
+
35
+ # load from hash
36
+ str = %[
37
+ name: Qux
38
+ ]
39
+
40
+ qux = Foo(YAML::load(str)) # => #<Foo:0xb7aff520 @name="Qux">
@@ -0,0 +1,42 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $:.unshift(File.join(File.dirname(__FILE__), '.'))
3
+
4
+ require 'rubygems'
5
+ require 'yaml'
6
+ require 'doodle'
7
+ require 'pp'
8
+
9
+ class AddressLine < Doodle
10
+ has :text, :kind => String
11
+ end
12
+
13
+ class Person < Doodle
14
+ has :name, :kind => String
15
+ has :address, :collect => { :line => AddressLine }
16
+ end
17
+
18
+ yaml = %[
19
+ ---
20
+ :address:
21
+ - Henry Wood House
22
+ - London
23
+ :name: Sean
24
+ ]
25
+
26
+ person = Person(YAML.load(yaml))
27
+ pp person
28
+ yaml = person.to_yaml
29
+ puts yaml
30
+
31
+ yaml = %[
32
+ --- !ruby/object:Person
33
+ address:
34
+ - !ruby/object:AddressLine
35
+ text: Henry Wood House
36
+ - !ruby/object:AddressLine
37
+ text: London
38
+ name: Sean
39
+ ]
40
+ person = YAML.load(yaml).validate!
41
+ pp person
42
+
@@ -1,46 +1,37 @@
1
1
  # doodle
2
- # Copyright (C) 2007 by Sean O'Halpin, 2007-11-24
2
+ # Copyright (C) 2007-2008 by Sean O'Halpin
3
+ # 2007-11-24 first version
4
+ # 2008-04-18 latest release 0.0.12
5
+ $:.unshift(File.dirname(__FILE__)) unless
6
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
7
 
4
8
  require 'molic_orderedhash' # todo[replace this with own (required functions only) version]
5
- require 'pp'
6
9
  #require 'bleak_house' if ENV['BLEAK_HOUSE']
7
10
 
11
+ # require Ruby 1.8.6 or higher
12
+ if RUBY_VERSION < '1.8.6'
13
+ raise Exception, "Sorry - doodle does not work with versions of Ruby below 1.8.6"
14
+ end
15
+
8
16
  # *doodle* is my attempt at an eco-friendly metaprogramming framework that does not
9
17
  # have pollute core Ruby objects such as Object, Class and Module.
10
-
18
+ #
11
19
  # While doodle itself is useful for defining classes, my main goal is to
12
20
  # come up with a useful DSL notation for class definitions which can be
13
21
  # reused in many contexts.
14
-
22
+ #
15
23
  # Docs at http://doodle.rubyforge.org
16
-
17
- # patch 1.8.5 to add instance_variable_defined?
18
- # note: this does not seem to work so well with singletons
19
- if RUBY_VERSION < '1.8.6'
20
- if !Object.method_defined?(:instance_variable_defined?)
21
- class Object
22
- __doodle__inspect = instance_method(:inspect)
23
- define_method :instance_variable_defined? do |name|
24
- res = __doodle__inspect.bind(self).call
25
- rx = /\B#{name}=/
26
- rx =~ res ? true : false
27
- end
28
- end
29
- end
30
- end
31
-
32
- module Doodle
33
- VERSION = '0.0.10'
34
- # where are we?
24
+ #
25
+ class Doodle
35
26
  class << self
27
+ # provide somewhere to hold thread-specific context information
28
+ # (I'm claiming the :doodle_xxx namespace)
36
29
  def context
37
30
  Thread.current[:doodle_context] ||= []
38
31
  end
39
- def parent
40
- context[-2]
41
- end
42
32
  end
43
33
 
34
+ # debugging utilities
44
35
  module Debug
45
36
  class << self
46
37
  # output result of block if ENV['DEBUG_DOODLE'] set
@@ -50,15 +41,40 @@ module Doodle
50
41
  end
51
42
  end
52
43
 
53
- # Set of utility functions to avoid changing base classes
54
- module Utils
55
- # Unnest arrays by one level of nesting, e.g. [1, [[2], 3]] => [1, [2], 3].
56
- def self.flatten_first_level(enum)
57
- enum.inject([]) {|arr, i| if i.kind_of? Array then arr.push(*i) else arr.push(i) end }
58
- end
59
- # from facets/string/case.rb, line 80
60
- def self.snake_case(camel_cased_word)
61
- camel_cased_word.gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
44
+ # Place to hold ref to built-in classes that need special handling
45
+ module BuiltIns
46
+ BUILTINS = [String, Hash, Array]
47
+ end
48
+
49
+ # Set of utility functions to avoid monkeypatching base classes
50
+ module Utils
51
+ class << self
52
+ # Unnest arrays by one level of nesting, e.g. [1, [[2], 3]] => [1, [2], 3].
53
+ def flatten_first_level(enum)
54
+ enum.inject([]) {|arr, i| if i.kind_of? Array then arr.push(*i) else arr.push(i) end }
55
+ end
56
+ # from facets/string/case.rb, line 80
57
+ def snake_case(camel_cased_word)
58
+ camel_cased_word.gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
59
+ end
60
+ # what kind of object are we dealing with?
61
+ def doodle_category(obj)
62
+ # note[this uses regex match on object's inspect string - kludgy
63
+ # - is there a better way?]
64
+ return :nil if obj.class == NilClass
65
+ case obj.real_inspect
66
+ when /#<Class:#<.*0x[a-z0-9]+>+$/
67
+ :instance_singleton_class
68
+ when /#<Class:[A-Z]/
69
+ :class_singleton_class
70
+ else
71
+ if obj.kind_of?(Module)
72
+ :class
73
+ else
74
+ :instance
75
+ end
76
+ end
77
+ end
62
78
  end
63
79
  end
64
80
 
@@ -70,14 +86,14 @@ module Doodle
70
86
  def self.raise_exception_on_error=(tf)
71
87
  @@raise_exception_on_error = tf
72
88
  end
73
-
89
+
74
90
  # internal error raised when a default was expected but not found
75
91
  class NoDefaultError < Exception
76
92
  end
77
93
  # raised when a validation rule returns false
78
94
  class ValidationError < Exception
79
95
  end
80
- # raised when a validation rule returns false
96
+ # raised when an unknown parameter is passed to initialize
81
97
  class UnknownAttributeError < Exception
82
98
  end
83
99
  # raised when a conversion fails
@@ -90,7 +106,6 @@ module Doodle
90
106
  # provides more direct access to the singleton class and a way to
91
107
  # treat Modules and Classes equally in a meta context
92
108
  module SelfClass
93
-
94
109
  # return the 'singleton class' of an object, optionally executing
95
110
  # a block argument in the (module/class) context of that object
96
111
  def singleton_class(&block)
@@ -98,15 +113,14 @@ module Doodle
98
113
  sc.module_eval(&block) if block_given?
99
114
  sc
100
115
  end
101
-
102
- def is_class_self_defn?
103
- defined?(ancestors) && ancestors.include?(Class)
104
- end
105
- def is_singleton_defn?
106
- defined?(superclass) && superclass.ancestors.include?(Class) && !is_class_self_defn?
107
- end
108
- def is_instance_defn?
109
- !is_class_self_defn? && !is_singleton_defn?
116
+ # evaluate in class context of self, whether Class, Module or singleton
117
+ def sc_eval(*args, &block)
118
+ if self.kind_of?(Module)
119
+ klass = self
120
+ else
121
+ klass = self.singleton_class
122
+ end
123
+ klass.module_eval(*args, &block)
110
124
  end
111
125
  end
112
126
 
@@ -114,67 +128,40 @@ module Doodle
114
128
  # classes as well as modules, classes and instances
115
129
  module Inherited
116
130
 
117
- # parents returns the set of parent classes of an object.
118
- # note[this is horribly complicated and kludgy - is there a better way?
119
- # could do with refactoring]
120
-
121
- # this function is a ~mess~ - refactor!!!
131
+ # parents returns the set of parent classes of an object
122
132
  def parents
123
- # d { [:parents, self.to_s, defined?(superclass)] }
124
- klasses = []
125
- if defined?(superclass)
126
- klass = superclass
127
- if self != superclass
128
- #
129
- # fixme[any other way to do this? seems really clunky to have to hack strings]
130
- #
131
- # What's this doing? Finding the class of which this is the singleton class
132
- regexen = [/Class:(?:#<)?([A-Z_][A-Za-z_]+)/, /Class:(([A-Z_][A-Za-z_]+))/]
133
- regexen.each do |regex|
134
- if cap = self.to_s.match(regex)
135
- if cap.captures.size > 0
136
- k = const_get(cap[1])
137
- # push onto front of array
138
- if k.respond_to?(:superclass) && k.superclass.respond_to?(:singleton_class)
139
- klasses.unshift k.superclass.singleton_class
140
- end
141
- end
142
- until klass.nil?
143
- klasses.unshift klass
144
- if klass == klass.superclass
145
- return klasses # oof
146
- end
147
- klass = klass.superclass
148
- end
149
- else
150
- until klass.nil?
151
- klasses << klass
152
- klass = klass.superclass
153
- end
154
- end
155
- end
156
- end
157
- else
133
+ # if singleton class (e.g. class << [Ff]oo; self; end) then it has
134
+ # no parents
135
+ case Doodle::Utils.doodle_category(self)
136
+ when :instance
158
137
  klass = self.class
159
- until klass.nil?
160
- klasses << klass
161
- klass = klass.superclass
162
- end
138
+ when :instance_singleton_class
139
+ klass = nil
140
+ when :class
141
+ klass = superclass
142
+ when :class_singleton_class
143
+ klass = nil
144
+ end
145
+ klasses = []
146
+ until klass.nil? || klass == klass.superclass
147
+ klasses << klass
148
+ klass = klass.superclass
163
149
  end
164
150
  klasses
165
151
  end
166
-
152
+
153
+ # need concepts of
154
+ # - attributes
155
+ # - instance_attributes
156
+ # - singleton_attributes
157
+ # - class_attributes
158
+
167
159
  # send message to all parents and collect results
168
160
  def collect_inherited(message)
169
161
  result = []
170
- klasses = parents
171
- #p [:parents, parents]
172
- # d { [:collect_inherited, :parents, message, klasses] }
173
- klasses.each do |klass|
174
- #p [:testing, klass]
162
+ parents.each do |klass|
175
163
  if klass.respond_to?(message)
176
- # d { [:collect_inherited, :responded, message, klass] }
177
- result.unshift(*klass.send(message))
164
+ result.unshift(*klass.__send__(message))
178
165
  else
179
166
  break
180
167
  end
@@ -184,9 +171,11 @@ module Doodle
184
171
  private :collect_inherited
185
172
  end
186
173
 
187
- # the intent of embrace is to provide a way to create directives that
188
- # affect all members of a class 'family' without having to modify
189
- # Module, Class or Object - in some ways, it's similar to Ara Howard's mixable[http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/197296]
174
+ # the intent of embrace is to provide a way to create directives
175
+ # that affect all members of a class 'family' without having to
176
+ # modify Module, Class or Object - in some ways, it's similar to Ara
177
+ # Howard's mixable[http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/197296]
178
+ # though not as tidy :S
190
179
  #
191
180
  # this works down to third level <tt>class << self</tt> - in practice, this is
192
181
  # perfectly good - it would be great to have a completely general
@@ -197,9 +186,8 @@ module Doodle
197
186
  def embrace(other, &block)
198
187
  # include in instance method chain
199
188
  include other
200
- #extend other
201
189
  sc = class << self; self; end
202
- sc.class_eval {
190
+ sc.module_eval {
203
191
  # class method chain
204
192
  include other
205
193
  # singleton method chain
@@ -207,16 +195,17 @@ module Doodle
207
195
  # ensure that subclasses are also embraced
208
196
  define_method :inherited do |klass|
209
197
  #p [:embrace, :inherited, klass]
210
- klass.send(:embrace, other) # n.b. closure
211
- klass.send(:include, Factory) # is there another way to do this? i.e. not in embrace
198
+ klass.__send__(:embrace, other) # n.b. closure
199
+ klass.__send__(:include, Factory) # is there another way to do this? i.e. not in embrace
212
200
  super(klass) if defined?(super)
213
201
  end
214
202
  }
215
- sc.class_eval(&block) if block_given?
203
+ sc.module_eval(&block) if block_given?
216
204
  end
217
205
  end
218
206
 
219
- class SaveBlock
207
+ # save a block for later execution
208
+ class DeferredBlock
220
209
  attr_accessor :block
221
210
  def initialize(arg_block = nil, &block)
222
211
  arg_block = block if block_given?
@@ -238,6 +227,7 @@ module Doodle
238
227
  end
239
228
  end
240
229
 
230
+ # place to stash bookkeeping info
241
231
  class DoodleInfo
242
232
  attr_accessor :local_attributes
243
233
  attr_accessor :local_validations
@@ -245,6 +235,7 @@ module Doodle
245
235
  attr_accessor :validation_on
246
236
  attr_accessor :arg_order
247
237
  attr_accessor :errors
238
+ attr_accessor :parent
248
239
 
249
240
  def initialize(object)
250
241
  @local_attributes = OrderedHash.new
@@ -253,14 +244,70 @@ module Doodle
253
244
  @local_conversions = {}
254
245
  @arg_order = []
255
246
  @errors = []
247
+ @parent = nil
248
+ end
249
+ real_inspect = Object.instance_method(:inspect)
250
+ define_method :real_inspect do
251
+ real_inspect.bind(self).call
252
+ end
253
+ def inspect
254
+ ''
256
255
  end
257
256
  end
258
257
 
258
+ # what it says on the tin :) various hacks to hide @__doodle__ variable
259
+ module SmokeAndMirrors
260
+ # redefine instance_variables to ignore our private @__doodle__ variable
261
+ # (hack to fool yaml and anything else that queries instance_variables)
262
+ meth = Object.instance_method(:instance_variables)
263
+ define_method :instance_variables do
264
+ meth.bind(self).call.reject{ |x| x =~ /@__doodle__/}
265
+ end
266
+
267
+ # hide @__doodle__ from inspect
268
+ # variants:
269
+ # #<Foo:0xb7de0064>
270
+ # #<Foo:0xb7c52d28 @name="Arthur Dent", @age=42>
271
+ # #<Class:#<Foo:0xb7de0064>>
272
+ # #<Class:Class>
273
+ # #<Class:#<Class:#<Foo:0xb7de0064>>>
274
+
275
+ # strip off all trailing > (count them = nesting)
276
+ # take leading chars up to first space (or EOS)
277
+ # rebuild rest
278
+
279
+ real_inspect = Object.instance_method(:inspect)
280
+ define_method :real_inspect do
281
+ real_inspect.bind(self).call
282
+ end
283
+ define_method :inspect do
284
+ # have to handle some built-ins as special case
285
+ built_in = Doodle::BuiltIns::BUILTINS.select{ |x| self.kind_of?(x) }.first
286
+ if built_in
287
+ built_in.instance_method(:inspect).bind(self).call
288
+ else
289
+ istr = real_inspect.bind(self).call
290
+ str = istr.gsub(/(>+$)/, '')
291
+ trailing = $1
292
+ klass = str.split(/\s/, 2).first
293
+ ivars = self.kind_of?(Module) ? [] : instance_variables
294
+ separator = ivars.size > 0 ? ' ' : ''
295
+
296
+ #pp [:istr, istr, :str, str, :trailing, trailing, :klass, klass]
297
+ # note to self: changing klass to <self.class will highlight cases that need the hack in parents
298
+ #%[<#{self.class}#{separator}#{instance_variables.map{|x| "#{x}=#{instance_variable_get(x).inspect}"}.join(' ')}#{trailing}]
299
+ %[#{klass}#{separator}#{ivars.map{|x| "#{x}=#{instance_variable_get(x).inspect}"}.join(' ')}#{trailing}]
300
+ end
301
+ end
302
+
303
+ end
304
+
259
305
  # the core module of Doodle - to get most facilities provided by Doodle
260
- # without inheriting from Doodle::Base, include Doodle::Helper, not this module
306
+ # without inheriting from Doodle, include Doodle::Core, not this module
261
307
  module BaseMethods
262
308
  include SelfClass
263
309
  include Inherited
310
+ include SmokeAndMirrors
264
311
 
265
312
  # this is the only way to get at internal values. Note: this is
266
313
  # initialized on the fly rather than in #initialize because
@@ -270,17 +317,12 @@ module Doodle
270
317
  end
271
318
  private :__doodle__
272
319
 
273
- # hack to fool yaml (and anything else that queries instance_variables)
274
- # pick a name that no-one else is likely to use
275
- alias :seoh_doodle_instance_variables_8b016735_bd60_44d9_bda5_5f9d0aade5a6 :instance_variables
276
- # redefine instance_variables to ignore our private @__doodle__ variable
277
- def instance_variables
278
- seoh_doodle_instance_variables_8b016735_bd60_44d9_bda5_5f9d0aade5a6.reject{ |x| x == '@__doodle__'}
279
- end
280
-
320
+ # helper for Marshal.dump
281
321
  def marshal_dump
322
+ # note: perhaps should also dump singleton attribute definitions?
282
323
  instance_variables.map{|x| [x, instance_variable_get(x)] }
283
324
  end
325
+ # helper for Marshal.load
284
326
  def marshal_load(data)
285
327
  data.each do |name, value|
286
328
  instance_variable_set(name, value)
@@ -291,7 +333,8 @@ module Doodle
291
333
  def errors
292
334
  __doodle__.errors
293
335
  end
294
-
336
+
337
+ # clear out the errors collection
295
338
  def clear_errors
296
339
  #pp [:clear_errors, self, caller]
297
340
  __doodle__.errors.clear
@@ -299,8 +342,6 @@ module Doodle
299
342
 
300
343
  # handle errors either by collecting in :errors or raising an exception
301
344
  def handle_error(name, *args)
302
- #pp [:caller, self, caller]
303
- #pp [:handle_error, name, args]
304
345
  # don't include duplicates (FIXME: hacky - shouldn't have duplicates in the first place)
305
346
  if !self.errors.include?([name, *args])
306
347
  self.errors << [name, *args]
@@ -310,6 +351,17 @@ module Doodle
310
351
  end
311
352
  end
312
353
 
354
+ def _handle_inherited_hash(tf, method)
355
+ if tf
356
+ collect_inherited(method).inject(OrderedHash.new){ |hash, item|
357
+ hash.merge(OrderedHash[*item])
358
+ }.merge(__send__(method))
359
+ else
360
+ __send__(method)
361
+ end
362
+ end
363
+ private :_handle_inherited_hash
364
+
313
365
  # return attributes defined in instance
314
366
  def local_attributes
315
367
  __doodle__.local_attributes
@@ -320,18 +372,39 @@ module Doodle
320
372
  # - if tf == true, returns all inherited attributes
321
373
  # - if tf == false, returns only those attributes defined in the current object/class
322
374
  def attributes(tf = true)
323
- if tf
324
- a = collect_inherited(:local_attributes).inject(OrderedHash.new){ |hash, item|
325
- #p [:hash, hash, :item, item]
375
+ results = _handle_inherited_hash(tf, :local_attributes)
376
+ if !kind_of?(Class) && singleton_class.respond_to?(:attributes)
377
+ results = results.merge(singleton_class.attributes)
378
+ end
379
+ results
380
+ end
381
+
382
+ # return attributes for class
383
+ def class_attributes(tf = true)
384
+ attrs = OrderedHash.new
385
+ if self.kind_of?(Class)
386
+ attrs = collect_inherited(:class_attributes).inject(OrderedHash.new){ |hash, item|
326
387
  hash.merge(OrderedHash[*item])
327
- }.merge(local_attributes)
328
- # d { [:attributes, self.to_s, a] }
329
- a
388
+ }.merge(singleton_class.respond_to?(:attributes) ? singleton_class.attributes : { })
389
+ attrs
330
390
  else
331
- local_attributes
391
+ self.class.class_attributes
332
392
  end
333
393
  end
334
394
 
395
+ # the set of conversions defined in the current class (i.e. without inheritance)
396
+ def local_conversions
397
+ __doodle__.local_conversions
398
+ end
399
+ protected :local_conversions
400
+
401
+ # returns hash of conversions
402
+ # - if tf == true, returns all inherited conversions
403
+ # - if tf == false, returns only those conversions defined in the current object/class
404
+ def conversions(tf = true)
405
+ _handle_inherited_hash(tf, :local_conversions)
406
+ end
407
+
335
408
  # the set of validations defined in the current class (i.e. without inheritance)
336
409
  def local_validations
337
410
  __doodle__.local_validations
@@ -343,51 +416,35 @@ module Doodle
343
416
  # - if tf == false, returns only those validations defined in the current object/class
344
417
  def validations(tf = true)
345
418
  if tf
346
- #p [:inherited_validations, collect_inherited(:local_validations)]
347
- #p [:local_validations, local_validations]
419
+ # note: validations are handled differently to attributes and
420
+ # conversions because ~all~ validations apply (so are stored
421
+ # as an array), whereas attributes and conversions are keyed
422
+ # by name and kind respectively, so only the most recent
423
+ # applies
424
+
348
425
  local_validations + collect_inherited(:local_validations)
349
426
  else
350
427
  local_validations
351
428
  end
352
429
  end
353
430
 
354
- # the set of conversions defined in the current class (i.e. without inheritance)
355
- def local_conversions
356
- __doodle__.local_conversions
357
- end
358
- protected :local_conversions
359
-
360
- # returns array of conversions
361
- # - if tf == true, returns all inherited conversions
362
- # - if tf == false, returns only those conversions defined in the current object/class
363
- def conversions(tf = true)
364
- if tf
365
- a = collect_inherited(:local_conversions).inject(OrderedHash.new){ |hash, item|
366
- #p [:hash, hash, :item, item]
367
- hash.merge(OrderedHash[*item])
368
- }.merge(self.local_conversions)
369
- # d { [:conversions, self.to_s, a] }
370
- a
371
- else
372
- local_conversions
373
- end
374
- end
375
-
376
431
  # lookup a single attribute by name, searching the singleton class first
377
432
  def lookup_attribute(name)
378
433
  # (look at singleton attributes first)
379
- # fixme[this smells like a hack to me - why not handled in attributes?]
380
- singleton_class.attributes[name] || attributes[name]
434
+ # fixme[this smells like a hack to me]
435
+ if self.class == Class
436
+ class_attributes[name]
437
+ else
438
+ attributes[name]
439
+ end
381
440
  end
382
441
  private :lookup_attribute
383
442
 
384
443
  # either get an attribute value (if no args given) or set it
385
444
  # (using args and/or block)
386
445
  def getter_setter(name, *args, &block)
387
- # d { [:getter_setter, name, args, block] }
388
446
  name = name.to_sym
389
447
  if block_given? || args.size > 0
390
- # setter
391
448
  _setter(name, *args, &block)
392
449
  else
393
450
  _getter(name)
@@ -397,21 +454,20 @@ module Doodle
397
454
 
398
455
  # get an attribute by name - return default if not otherwise defined
399
456
  def _getter(name, &block)
400
- ## d { [:_getter, 1, self.to_s, name, block, instance_variables] }
401
- # getter
402
457
  ivar = "@#{name}"
403
458
  if instance_variable_defined?(ivar)
404
- ## d { [:_getter, 2, name, block] }
405
459
  instance_variable_get(ivar)
406
460
  else
407
461
  # handle default
408
462
  # Note: use :init => value to cover cases where defaults don't work
409
463
  # (e.g. arrays that disappear when you go out of scope)
410
464
  att = lookup_attribute(name)
411
- #d { [:getter, name, att, block] }
412
- if att.default_defined?
465
+ # special case for class/singleton :init
466
+ if att.init_defined?
467
+ _setter(name, att.init)
468
+ elsif att.default_defined?
413
469
  case att.default
414
- when SaveBlock
470
+ when DeferredBlock
415
471
  instance_eval(&att.default.block)
416
472
  when Proc
417
473
  instance_eval(&att.default)
@@ -428,20 +484,16 @@ module Doodle
428
484
 
429
485
  # set an attribute by name - apply validation if defined
430
486
  def _setter(name, *args, &block)
431
- #pp [:_setter, self, self.class, name, args, block, caller]
487
+ Doodle::Debug.d { [:_setter, name, args] }
432
488
  ivar = "@#{name}"
433
489
  if block_given?
434
- args.unshift(SaveBlock.new(block))
435
- #p [:instance_eval_block, self, block]
436
- end
437
- # d { [:_setter, 3, :setting, name, ivar, args] }
438
- att = lookup_attribute(name)
439
- # d { [:_setter, 4, :setting, name, att] }
440
- if att
441
- #d { [:_setter, :instance_variable_set, :ivar, ivar, :args, args, :att_validate, att.validate(*args) ] }
490
+ args.unshift(DeferredBlock.new(block))
491
+ end
492
+ if att = lookup_attribute(name)
493
+ Doodle::Debug.d { [:_setter, name, args] }
442
494
  v = instance_variable_set(ivar, att.validate(self, *args))
443
495
  else
444
- #d { [:_setter, :instance_variable_set, ivar, args ] }
496
+ Doodle::Debug.d { [:_setter, "no attribute"] }
445
497
  v = instance_variable_set(ivar, *args)
446
498
  end
447
499
  validate!(false)
@@ -452,13 +504,11 @@ module Doodle
452
504
  # if block passed, define a conversion from class
453
505
  # if no args, apply conversion to arguments
454
506
  def from(*args, &block)
455
- # d { [:from, self, self.class, self.name, args, block] }
456
507
  if block_given?
457
508
  # set the rule for each arg given
458
509
  args.each do |arg|
459
510
  local_conversions[arg] = block
460
511
  end
461
- # d { [:from, conversions] }
462
512
  else
463
513
  convert(self, *args)
464
514
  end
@@ -471,7 +521,6 @@ module Doodle
471
521
 
472
522
  # add a validation that attribute must be of class <= kind
473
523
  def kind(*args, &block)
474
- # d { [:kind, args, block] }
475
524
  if args.size > 0
476
525
  # todo[figure out how to handle kind being specified twice?]
477
526
  @kind = args.first
@@ -492,16 +541,14 @@ module Doodle
492
541
  ancestors = value.class.ancestors
493
542
  matches = ancestors & conversions.keys
494
543
  indexed_matches = matches.map{ |x| ancestors.index(x)}
495
- #p [matches, indexed_matches, indexed_matches.min]
496
544
  if indexed_matches.size > 0
497
545
  converter_class = ancestors[indexed_matches.min]
498
- #p [:converter, converter_class]
499
546
  if converter = conversions[converter_class]
500
547
  value = converter[*args]
501
548
  end
502
549
  end
503
550
  end
504
- rescue => e
551
+ rescue Exception => e
505
552
  owner.handle_error name, ConversionError, e.to_s, [caller[-1]]
506
553
  end
507
554
  value
@@ -511,34 +558,30 @@ module Doodle
511
558
  def validate(owner, *args)
512
559
  Doodle::Debug.d { [:validate, self, :owner, owner, :args, args ] }
513
560
  value = convert(owner, *args)
514
- #d { [:validate, self, :args, args, :value, value ] }
515
561
  validations.each do |v|
516
562
  Doodle::Debug.d { [:validate, self, v, args, value] }
517
563
  if !v.block[value]
518
564
  owner.handle_error name, ValidationError, "#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })", [caller[-1]]
519
565
  end
520
566
  end
521
- #d { [:validate, :value, value ] }
522
567
  value
523
568
  end
524
-
569
+
525
570
  # define a getter_setter
526
571
  def define_getter_setter(name, *args, &block)
527
- # d { [:define_getter_setter, [self, self.class], name, args, block] }
528
-
529
572
  # need to use string eval because passing block
530
- module_eval "def #{name}(*args, &block); getter_setter(:#{name}, *args, &block); end", __FILE__, __LINE__
531
- module_eval "def #{name}=(*args, &block); _setter(:#{name}, *args); end", __FILE__, __LINE__
532
-
533
- # this is how it should be done (in 1.9)
534
- # module_eval {
535
- # define_method name do |*args, &block|
536
- # getter_setter(name.to_sym, *args, &block)
537
- # end
538
- # define_method "#{name}=" do |*args, &block|
539
- # _setter(name.to_sym, *args, &block)
540
- # end
541
- # }
573
+ sc_eval "def #{name}(*args, &block); getter_setter(:#{name}, *args, &block); end", __FILE__, __LINE__
574
+ sc_eval "def #{name}=(*args, &block); _setter(:#{name}, *args); end", __FILE__, __LINE__
575
+
576
+ # this is how it should be done (in 1.9)
577
+ # module_eval {
578
+ # define_method name do |*args, &block|
579
+ # getter_setter(name.to_sym, *args, &block)
580
+ # end
581
+ # define_method "#{name}=" do |*args, &block|
582
+ # _setter(name.to_sym, *args, &block)
583
+ # end
584
+ # }
542
585
  end
543
586
  private :define_getter_setter
544
587
 
@@ -547,9 +590,15 @@ module Doodle
547
590
  def define_collector(collection, name, klass = nil, &block)
548
591
  # need to use string eval because passing block
549
592
  if klass.nil?
550
- module_eval "def #{name}(*args, &block); args.unshift(block) if block_given?; #{collection}.<<(*args); end", __FILE__, __LINE__
593
+ sc_eval("def #{name}(*args, &block); args.unshift(block) if block_given?; #{collection}.<<(*args); end", __FILE__, __LINE__)
551
594
  else
552
- module_eval "def #{name}(*args, &block); #{collection} << #{klass}.new(*args, &block); end", __FILE__, __LINE__
595
+ sc_eval("def #{name}(*args, &block);
596
+ if args.all?{|x| x.kind_of?(#{klass})}
597
+ #{collection}.<<(*args)
598
+ else
599
+ #{collection} << #{klass}.new(*args, &block);
600
+ end
601
+ end", __FILE__, __LINE__)
553
602
  end
554
603
  end
555
604
  private :define_collector
@@ -577,13 +626,11 @@ module Doodle
577
626
  # end
578
627
  #
579
628
  def has(*args, &block)
580
- #what_am_i?([:has, args])
581
629
  Doodle::Debug.d { [:has, self, self.class, args] }
582
630
  name = args.shift.to_sym
583
631
  # d { [:has2, name, args] }
584
632
  key_values, positional_args = args.partition{ |x| x.kind_of?(Hash)}
585
633
  handle_error name, ArgumentError, "Too many arguments" if positional_args.size > 0
586
- # d { [:has_args, self, key_values, positional_args, args] }
587
634
  params = { :name => name }
588
635
  params = key_values.inject(params){ |acc, item| acc.merge(item)}
589
636
 
@@ -607,7 +654,6 @@ module Doodle
607
654
  define_collector name, collector_name, collector_klass
608
655
  end
609
656
 
610
- # d { [:has_args, :params, params] }
611
657
  # define getter setter before setting up attribute
612
658
  define_getter_setter name, *args, &block
613
659
  local_attributes[name] = attribute = Attribute.new(params, &block)
@@ -620,23 +666,18 @@ module Doodle
620
666
  else
621
667
  tmp_klass = collector_klass
622
668
  end
623
- enum.map{|x| tmp_klass.new(x)}
669
+ enum.map{|x|
670
+ if x.kind_of?(tmp_klass)
671
+ x
672
+ elsif tmp_klass.conversions.key?(x.class)
673
+ tmp_klass.from(x)
674
+ else
675
+ tmp_klass.new(x)
676
+ end
677
+ }
624
678
  end
625
679
  }
626
680
  end
627
- if is_class_self_defn? or is_singleton_defn?
628
- #pp [:args, args]
629
- init_values = get_init_values(false)
630
- # if init_values.size > 0
631
- #p [:init_values, name, self, is_class_self_defn? ? :CLASS : is_singleton_defn? ? :SINGLETON : '?', init_values, methods(false)]
632
- # #define_getter_setter(name)
633
- # #instance_variable_set("@#{name}", init_values[name])
634
- # end
635
- # # singleton_class do
636
- # #_setter(name, init_values[name])
637
- # # end
638
- end
639
-
640
681
  attribute
641
682
  end
642
683
 
@@ -644,7 +685,6 @@ module Doodle
644
685
  def arg_order(*args)
645
686
  if args.size > 0
646
687
  begin
647
- #p [:arg_order, 1, self, self.class, args]
648
688
  args.uniq!
649
689
  args.each do |x|
650
690
  handle_error :arg_order, ArgumentError, "#{x} not a Symbol" if !(x.class <= Symbol)
@@ -652,11 +692,9 @@ module Doodle
652
692
  end
653
693
  __doodle__.arg_order = args
654
694
  rescue Exception => e
655
- #p [InvalidOrderError, e.to_s]
656
695
  handle_error :arg_order, InvalidOrderError, e.to_s, [caller[-1]]
657
696
  end
658
697
  else
659
- #p [:arg_order, 3, self, self.class, :default]
660
698
  __doodle__.arg_order + (attributes.keys - __doodle__.arg_order)
661
699
  end
662
700
  end
@@ -666,63 +704,18 @@ module Doodle
666
704
  hash[n] = begin
667
705
  case a.init
668
706
  when NilClass, TrueClass, FalseClass, Fixnum
669
- #p [:init, :no_clone]
670
707
  a.init
671
- when SaveBlock
672
- #p [:init, :save_block]
708
+ when DeferredBlock
673
709
  instance_eval(&a.init.block)
674
710
  else
675
- #p [:init, :clone]
676
711
  a.init.clone
677
712
  end
678
713
  rescue Exception => e
679
- #p [:init, :rescue, e]
680
714
  a.init
681
715
  end
682
716
  ; hash }
683
717
  end
684
718
  private :get_init_values
685
-
686
- # helper function to initialize from hash - this is safe to use
687
- # after initialization (validate! is called if this method is
688
- # called after initialization)
689
- def initialize_from_hash(*args)
690
- defer_validation do
691
- # hash initializer
692
- # separate into array of hashes of form [{:k1 => v1}, {:k2 => v2}] and positional args
693
- key_values, args = args.partition{ |x| x.kind_of?(Hash)}
694
- Doodle::Debug.d { [:initialize_from_hash, :key_values, key_values, :args, args] }
695
-
696
- # match up positional args with attribute names (from arg_order) using idiom to create hash from array of assocs
697
- arg_keywords = Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))]
698
- # d { [:initialize, :arg_keywords, arg_keywords] }
699
-
700
- # set up initial values with ~clones~ of specified values (so not shared between instances)
701
- init_values = get_init_values
702
-
703
- # add to start of key_values array (so can be overridden by params)
704
- key_values.unshift(init_values)
705
-
706
- # merge all hash args into one
707
- key_values = key_values.inject(arg_keywords) { |hash, item| hash.merge(item)}
708
-
709
- # convert key names to symbols
710
- key_values = key_values.inject({}) {|h, (k, v)| h[k.to_sym] = v; h}
711
- Doodle::Debug.d { [:initialize_from_hash, :key_values2, key_values, :args2, args] }
712
-
713
- # create attributes
714
- key_values.keys.each do |key|
715
- Doodle::Debug.d { [:initialize_from_hash, :setting, key, key_values[key]] }
716
- if respond_to?(key)
717
- send(key, key_values[key])
718
- else
719
- # raise error if not defined
720
- handle_error key, Doodle::UnknownAttributeError, "Unknown attribute '#{key}' #{key_values[key].inspect}"
721
- end
722
- end
723
- end
724
- end
725
- #private :initialize_from_hash
726
719
 
727
720
  # return true if instance variable +name+ defined
728
721
  def ivar_defined?(name)
@@ -733,38 +726,48 @@ module Doodle
733
726
  # validate this object by applying all validations in sequence
734
727
  # - if all == true, validate all attributes, e.g. when loaded from YAML, else validate at object level only
735
728
  def validate!(all = true)
736
- #pp [:validate!, all, caller]
729
+ Doodle::Debug.d { [:validate!, all, caller] }
737
730
  if all
738
731
  clear_errors
739
732
  end
740
- #Doodle::Debug.d { [:validate!, self] }
741
- #Doodle::Debug.d { [:validate!, self, __doodle__.validation_on] }
742
733
  if __doodle__.validation_on
743
734
  if self.class == Class
744
- attribs = singleton_class.attributes
735
+ attribs = class_attributes
736
+ Doodle::Debug.d { [:validate!, "using class_attributes", class_attributes] }
745
737
  else
746
738
  attribs = attributes
739
+ Doodle::Debug.d { [:validate!, "using instance_attributes", attributes] }
747
740
  end
748
- #pp [:validate!, self, self.class, attributes]
749
741
  attribs.each do |name, att|
750
742
  # treat default as special case
751
- if [:default, :init].include?(att.name) || att.default_defined? || is_class_self_defn? || is_singleton_defn?
752
- # nop
753
- elsif !ivar_defined?(att.name) && self.class != Class
754
- handle_error name, Doodle::ValidationError, "#{self} missing required attribute '#{name}'", [caller[-1]]
743
+ if att.default_defined?
744
+ Doodle::Debug.d { [:validate!, "default_defined - breaking" ]}
745
+ break
755
746
  end
756
- # if all == true, reset values so conversions and validations are applied to raw instance variables
757
- # e.g. when loaded from YAML
758
- att_name = "@#{att.name}"
759
- if all && instance_variable_defined?(att_name)
760
- send(att.name, instance_variable_get(att_name))
747
+ ivar_name = "@#{att.name}"
748
+ if instance_variable_defined?(ivar_name)
749
+ # if all == true, reset values so conversions and
750
+ # validations are applied to raw instance variables
751
+ # e.g. when loaded from YAML
752
+ if all
753
+ Doodle::Debug.d { [:validate!, :sending, att.name, instance_variable_get(ivar_name) ] }
754
+ __send__(att.name, instance_variable_get(ivar_name))
755
+ end
756
+ elsif self.class != Class
757
+ handle_error name, Doodle::ValidationError, "#{self} missing required attribute '#{name}'", [caller[-1]]
761
758
  end
762
759
  end
763
760
  # now apply instance level validations
761
+
762
+ Doodle::Debug.d { [:validate!, "validations", validations ]}
764
763
  validations.each do |v|
765
764
  Doodle::Debug.d { [:validate!, self, v ] }
766
- if !instance_eval(&v.block)
767
- handle_error :validate!, ValidationError, "#{ self.inspect } must #{ v.message }", [caller[-1]]
765
+ begin
766
+ if !instance_eval(&v.block)
767
+ handle_error self, ValidationError, "#{ self.class } must #{ v.message }", [caller[-1]]
768
+ end
769
+ rescue Exception => e
770
+ handle_error self, ValidationError, e.to_s, [caller[-1]]
768
771
  end
769
772
  end
770
773
  end
@@ -787,20 +790,66 @@ module Doodle
787
790
  v
788
791
  end
789
792
 
793
+ # helper function to initialize from hash - this is safe to use
794
+ # after initialization (validate! is called if this method is
795
+ # called after initialization)
796
+ def initialize_from_hash(*args)
797
+ defer_validation do
798
+ # hash initializer
799
+ # separate into array of hashes of form [{:k1 => v1}, {:k2 => v2}] and positional args
800
+ key_values, args = args.partition{ |x| x.kind_of?(Hash)}
801
+ Doodle::Debug.d { [self.class, :initialize_from_hash, :key_values, key_values, :args, args] }
802
+
803
+ # match up positional args with attribute names (from arg_order) using idiom to create hash from array of assocs
804
+ arg_keywords = Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))]
805
+
806
+ # set up initial values with ~clones~ of specified values (so not shared between instances)
807
+ init_values = get_init_values
808
+
809
+ # add to start of key_values array (so can be overridden by params)
810
+ key_values.unshift(init_values)
811
+
812
+ # merge all hash args into one
813
+ key_values = key_values.inject(arg_keywords) { |hash, item| hash.merge(item)}
814
+
815
+ # convert key names to symbols
816
+ key_values = key_values.inject({}) {|h, (k, v)| h[k.to_sym] = v; h}
817
+ Doodle::Debug.d { [self.class, :initialize_from_hash, :key_values2, key_values, :args2, args] }
818
+
819
+ # create attributes
820
+ key_values.keys.each do |key|
821
+ Doodle::Debug.d { [self.class, :initialize_from_hash, :setting, key, key_values[key]] }
822
+ if respond_to?(key)
823
+ __send__(key, key_values[key])
824
+ else
825
+ # raise error if not defined
826
+ handle_error key, Doodle::UnknownAttributeError, "Unknown attribute '#{key}' #{key_values[key].inspect}"
827
+ end
828
+ end
829
+ end
830
+ end
831
+ #private :initialize_from_hash
832
+
833
+ # return containing object (set during initialization)
834
+ def parent
835
+ __doodle__.parent
836
+ end
837
+
790
838
  # object can be initialized from a mixture of positional arguments,
791
839
  # hash of keyword value pairs and a block which is instance_eval'd
792
840
  def initialize(*args, &block)
841
+ built_in = Doodle::BuiltIns::BUILTINS.select{ |x| self.kind_of?(x) }.first
842
+ if built_in
843
+ super
844
+ end
793
845
  __doodle__.validation_on = true
794
- #p [:Doodle_context_push, self]
846
+ __doodle__.parent = Doodle.context[-1]
795
847
  Doodle.context.push(self)
796
848
  defer_validation do
797
- # d { [:initialize, self.to_s, args, block] }
798
849
  initialize_from_hash(*args)
799
- # d { [:initialize, self.to_s, args, block, :calling_block] }
800
850
  instance_eval(&block) if block_given?
801
851
  end
802
852
  Doodle.context.pop
803
- #p [:Doodle_context_pop, ]
804
853
  end
805
854
 
806
855
  end
@@ -822,35 +871,36 @@ module Doodle
822
871
  # etc.
823
872
  module Factory
824
873
  RX_IDENTIFIER = /^[A-Za-z_][A-Za-z_0-9]+\??$/
825
- class << self
874
+ class << self
826
875
  # create a factory function in appropriate module for the specified class
827
876
  def factory(konst)
828
- #p [:factory, name]
829
877
  name = konst.to_s
830
878
  names = name.split(/::/)
831
879
  name = names.pop
832
880
  if names.empty?
833
881
  # top level class - should be available to all
834
882
  klass = Object
835
- #p [:names_empty, klass, mklass]
836
- if !klass.respond_to?(name) && name =~ Factory::RX_IDENTIFIER
883
+ method_defined = begin
884
+ method(name)
885
+ true
886
+ rescue
887
+ false
888
+ end
889
+
890
+ if !method_defined && !klass.respond_to?(name) && !eval("respond_to?(:#{name})", TOPLEVEL_BINDING) && name =~ Factory::RX_IDENTIFIER
837
891
  eval("def #{ name }(*args, &block); ::#{name}.new(*args, &block); end", ::TOPLEVEL_BINDING, __FILE__, __LINE__)
838
892
  end
839
893
  else
840
894
  klass = names.inject(self) {|c, n| c.const_get(n)}
841
- mklass = class << klass; self; end
842
- #p [:names, klass, mklass]
843
- # TODO: check how many times this is being called
895
+ # todo[check how many times this is being called]
844
896
  if !klass.respond_to?(name) && name =~ Factory::RX_IDENTIFIER
845
- klass.class_eval("def self.#{name}(*args, &block); #{name}.new(*args, &block); end", __FILE__, __LINE__)
897
+ klass.module_eval("def self.#{name}(*args, &block); #{name}.new(*args, &block); end", __FILE__, __LINE__)
846
898
  end
847
899
  end
848
- #p [:factory, mklass, klass, src]
849
900
  end
850
901
 
851
902
  # inherit the factory function capability
852
903
  def included(other)
853
- #p [:factory, :included, self, other ]
854
904
  super
855
905
  # make +factory+ method available
856
906
  factory other
@@ -858,12 +908,11 @@ module Doodle
858
908
  end
859
909
  end
860
910
 
861
- # Include Doodle::Helper if you want to derive from another class
911
+ # Include Doodle::Core if you want to derive from another class
862
912
  # but still get Doodle goodness in your class (including Factory
863
913
  # methods).
864
- module Helper
914
+ module Core
865
915
  def self.included(other)
866
- #p [:Helper, :included, self, other ]
867
916
  super
868
917
  other.module_eval {
869
918
  extend Embrace
@@ -872,17 +921,27 @@ module Doodle
872
921
  }
873
922
  end
874
923
  end
924
+ # deprecated
925
+ Helper = Core
875
926
 
876
- # derive from Base if you want all the Doodle goodness
927
+ # deprecated
877
928
  class Base
878
- include Helper
929
+ include Core
879
930
  end
931
+ include Core
932
+ end
880
933
 
881
- # todo[need to extend this]
882
- class Attribute < Doodle::Base
934
+ class Doodle
935
+ # Attribute is itself a Doodle object that is created by #has and
936
+ # added to the #attributes collection in an object's DoodleInfo
937
+ #
938
+ # It is used to provide a context for defining #must and #from rules
939
+ #
940
+ class Attribute < Doodle
941
+ # todo[want to design Attribute so it's extensible, e.g. to specific datatypes & built-in validations]
883
942
  # must define these methods before using them in #has below
884
943
 
885
- # bump off +validate!+ for Attributes - maybe better way of doing
944
+ # hack: bump off +validate!+ for Attributes - maybe better way of doing
886
945
  # this however, without this, tries to validate Attribute to :kind
887
946
  # specified, e.g. if you have
888
947
  #
@@ -891,6 +950,8 @@ module Doodle
891
950
  # it will fail because Attribute is not a kind of Date -
892
951
  # obviously, I have to think about this some more :S
893
952
  #
953
+ # at least, I could hand roll a custom validate! method for Attribute
954
+ #
894
955
  def validate!(all = true)
895
956
  end
896
957
 
@@ -920,9 +981,11 @@ module Doodle
920
981
  s.to_sym
921
982
  end
922
983
  end
984
+
923
985
  # default value (can be a block)
924
986
  has :default, :default => nil
925
- # initial value
987
+
988
+ # initial value
926
989
  has :init, :default => nil
927
990
 
928
991
  end