doodle 0.0.10 → 0.1.0

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