doodle 0.0.8 → 0.0.9
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.
- data/ChangeLog +36 -0
- data/examples/event-location.rb +47 -3
- data/examples/event.rb +30 -21
- data/examples/example-01.rb +1 -0
- data/examples/example-01.rdoc +3 -2
- data/examples/example-02.rb +1 -0
- data/examples/example-02.rdoc +2 -1
- data/examples/example-03.rb +45 -0
- data/examples/example-03.rdoc +55 -0
- data/lib/doodle.rb +220 -186
- data/lib/semantic.cache +8 -0
- data/spec/bugs_spec.rb +1 -1
- data/spec/class_spec.rb +2 -2
- data/spec/collector_spec.rb +49 -1
- data/spec/conversion_spec.rb +0 -8
- data/spec/doodle_context_spec.rb +46 -0
- data/spec/doodle_spec.rb +3 -3
- data/spec/extra_args_spec.rb +20 -0
- data/spec/factory_spec.rb +80 -0
- data/spec/init_spec.rb +60 -6
- data/spec/serialization_spec.rb +7 -2
- data/spec/singleton_spec.rb +63 -0
- metadata +22 -18
- data/examples/event1.rb +0 -33
- data/examples/event2.rb +0 -21
- data/examples/src/sequel_core-1.3/lib/sequel_core/exceptions.rb +0 -48
data/ChangeLog
CHANGED
@@ -1,5 +1,41 @@
|
|
1
1
|
= ChangeLog for doodle
|
2
2
|
|
3
|
+
== 0.0.9 / 2008-04-12
|
4
|
+
|
5
|
+
- new features:
|
6
|
+
- added Doodle.context and Doodle.parent (only valid during
|
7
|
+
initialization)
|
8
|
+
- use SaveBlock to distinguish between Proc and block args to :init
|
9
|
+
- :collect now creates array by default if no :init specified (thanks to
|
10
|
+
James Adam :)
|
11
|
+
- attributes defined with collect now have :init => [] if no :init
|
12
|
+
specified
|
13
|
+
- :collect can now initialize from Enumerables by default
|
14
|
+
- raise UnknownAttributeError if initialize called with unspecified attribute key
|
15
|
+
- new examples:
|
16
|
+
- mail example (with gmail smtp support)
|
17
|
+
- Doodle.parent
|
18
|
+
- datatypes
|
19
|
+
- new specs for singletons, Doodle.context and Doodle.parent, factory
|
20
|
+
- updated docs
|
21
|
+
- removed unused code and tidied up
|
22
|
+
- bug fixes:
|
23
|
+
- fixed memory leak and provided custom to_yaml and marshal_dump/load
|
24
|
+
- fixed regex for factory methods
|
25
|
+
- fixed bug where validate! was creating instance variables with defaults
|
26
|
+
- fixed errors collection
|
27
|
+
- fixed yaml test for JRuby
|
28
|
+
- don't define factory method if one by same name already defined
|
29
|
+
|
30
|
+
== 0.0.8 / 2008-03-25
|
31
|
+
|
32
|
+
- renamed rake task upload_gem to publish_gem
|
33
|
+
- bumped version and updated todo list
|
34
|
+
- don't define factory method if one by same name already defined
|
35
|
+
- apply validation! to raw instance variables - also, raise Doodle::ValidationError rather than ArgumentError if missing required values
|
36
|
+
- tweaked bugs_spec
|
37
|
+
- added spec to cover applying conversions to yaml loaded data
|
38
|
+
|
3
39
|
== 0.0.7 / 2008-03-22
|
4
40
|
|
5
41
|
- return self from validate! (so you can use idiom foo = YAML::load(yaml).validate!)
|
data/examples/event-location.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
1
2
|
require 'rubygems'
|
2
3
|
require 'date'
|
3
4
|
require 'doodle'
|
@@ -25,11 +26,15 @@ class Event
|
|
25
26
|
Date.parse(s)
|
26
27
|
end
|
27
28
|
end
|
28
|
-
has :locations, :init => [], :collect => {:place => "Location"}
|
29
|
+
has :locations, :init => [], :collect => {:place => "Location"} do
|
30
|
+
from Array do |array|
|
31
|
+
array.map{|x| Location(x)}
|
32
|
+
end
|
33
|
+
end
|
29
34
|
end
|
30
35
|
|
31
36
|
event = Event "Festival" do
|
32
|
-
date '
|
37
|
+
date '2018-04-01'
|
33
38
|
place "The muddy field"
|
34
39
|
place "Beer tent" do
|
35
40
|
event "Drinking"
|
@@ -46,7 +51,46 @@ str =<<EOS
|
|
46
51
|
name: Glastonbury
|
47
52
|
date: 2000-07-01
|
48
53
|
EOS
|
49
|
-
|
54
|
+
|
55
|
+
def capture(&block)
|
56
|
+
begin
|
57
|
+
block.call
|
58
|
+
rescue Exception => e
|
59
|
+
e
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
res = capture {
|
64
|
+
event = YAML::load(str).validate! # will raise Doodle::ValidationError
|
65
|
+
}
|
66
|
+
pp res
|
67
|
+
|
68
|
+
hash_data = {
|
69
|
+
:name => "Festival",
|
70
|
+
:date => '2010-04-01',
|
71
|
+
:locations =>
|
72
|
+
[
|
73
|
+
{
|
74
|
+
:events => [],
|
75
|
+
:name => "The muddy field",
|
76
|
+
},
|
77
|
+
{
|
78
|
+
:name => "Beer tent",
|
79
|
+
:events =>
|
80
|
+
[
|
81
|
+
{
|
82
|
+
:name => "Drinking",
|
83
|
+
:locations => [],
|
84
|
+
}
|
85
|
+
]
|
86
|
+
}
|
87
|
+
]
|
88
|
+
}
|
89
|
+
|
90
|
+
pp hash_data
|
91
|
+
|
92
|
+
e = Event(hash_data)
|
93
|
+
pp e
|
50
94
|
|
51
95
|
__END__
|
52
96
|
--- !ruby/object:Event
|
data/examples/event.rb
CHANGED
@@ -1,30 +1,39 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require 'rubygems'
|
1
3
|
require 'date'
|
4
|
+
require 'pp'
|
2
5
|
require 'doodle'
|
3
6
|
|
4
|
-
class
|
5
|
-
has :
|
6
|
-
|
7
|
-
|
7
|
+
class Location < Doodle::Base
|
8
|
+
has :name, :kind => String
|
9
|
+
has :events, :init => [], :collect => :Event
|
10
|
+
end
|
11
|
+
|
12
|
+
class Event
|
13
|
+
# or if you want to inherit from another class
|
14
|
+
include Doodle::Helper
|
15
|
+
include Doodle::Factory
|
16
|
+
|
17
|
+
has :name, :kind => String
|
18
|
+
has :date do
|
19
|
+
kind Date
|
20
|
+
default { Date.today }
|
21
|
+
must 'be >= today' do |value|
|
22
|
+
value >= Date.today
|
8
23
|
end
|
9
|
-
|
10
|
-
|
11
|
-
from String do |value|
|
12
|
-
Date.parse(value)
|
24
|
+
from String do |s|
|
25
|
+
Date.parse(s)
|
13
26
|
end
|
14
27
|
end
|
15
|
-
|
16
|
-
args = value.split(' to ')
|
17
|
-
new(*args)
|
18
|
-
end
|
28
|
+
has :locations, :init => [], :collect => {:place => "Location"}
|
19
29
|
end
|
20
|
-
event = Event.from '2008-03-05 to 2008-03-06'
|
21
|
-
event.start_date.to_s # => "2008-03-05"
|
22
|
-
event.end_date.to_s # => "2008-03-06"
|
23
|
-
event.start_date = '2001-01-01'
|
24
|
-
event.start_date # =>
|
25
|
-
event.start_date.to_s # =>
|
26
30
|
|
27
|
-
|
28
|
-
|
31
|
+
event = Event "Festival" do
|
32
|
+
date '2008-04-01'
|
33
|
+
place "The muddy field"
|
34
|
+
place "Beer tent" do
|
35
|
+
event "Drinking"
|
36
|
+
end
|
29
37
|
end
|
30
|
-
|
38
|
+
|
39
|
+
pp event
|
data/examples/example-01.rb
CHANGED
data/examples/example-01.rdoc
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
1
2
|
require 'date'
|
2
3
|
require 'doodle'
|
3
4
|
|
@@ -11,6 +12,6 @@ class DateRange < Doodle::Base
|
|
11
12
|
end
|
12
13
|
|
13
14
|
dr = DateRange.new
|
14
|
-
dr.start_date # => #<Date:
|
15
|
-
dr.end_date # => #<Date:
|
15
|
+
dr.start_date # => #<Date: 4909137/2,0,2299161>
|
16
|
+
dr.end_date # => #<Date: 4909137/2,0,2299161>
|
16
17
|
|
data/examples/example-02.rb
CHANGED
data/examples/example-02.rdoc
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
1
2
|
require 'date'
|
2
3
|
require 'doodle'
|
3
4
|
|
@@ -59,4 +60,4 @@ dr.end_date # => #<Date: 4908931/2,0,2299161>
|
|
59
60
|
dr = DateRange '2008-01-01', '2007-12-31'
|
60
61
|
dr.start_date # =>
|
61
62
|
dr.end_date # =>
|
62
|
-
# ~> -:
|
63
|
+
# ~> -:60: #<DateRange:0xb7a52640 @end_date=#<Date: 4908931/2,0,2299161>, @start_date=#<Date: 4908933/2,0,2299161>> must have end_date >= start_date (Doodle::ValidationError)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require 'doodle'
|
3
|
+
|
4
|
+
# collect works for anything that provides a :<< method
|
5
|
+
|
6
|
+
class Text < Doodle::Base
|
7
|
+
has :text, :init => "", :collect => :part
|
8
|
+
end
|
9
|
+
|
10
|
+
text = Text do
|
11
|
+
part "Hello"
|
12
|
+
part " "
|
13
|
+
part "World"
|
14
|
+
end
|
15
|
+
|
16
|
+
puts text.text
|
17
|
+
|
18
|
+
class Lines < Doodle::Base
|
19
|
+
has :lines, :init => [], :collect => :line
|
20
|
+
end
|
21
|
+
|
22
|
+
lines = Lines do
|
23
|
+
line "Hello"
|
24
|
+
line "World"
|
25
|
+
end
|
26
|
+
|
27
|
+
puts lines.lines.join("\n")
|
28
|
+
|
29
|
+
require 'set'
|
30
|
+
|
31
|
+
class Thing < Doodle::Base
|
32
|
+
has :properties, :init => Set.new, :collect => :prop
|
33
|
+
end
|
34
|
+
|
35
|
+
# this is a contrived example
|
36
|
+
thing = Thing do
|
37
|
+
prop 'name'
|
38
|
+
prop 'size'
|
39
|
+
prop 'size'
|
40
|
+
prop 'name'
|
41
|
+
end
|
42
|
+
|
43
|
+
thing.prop 'name'
|
44
|
+
p thing
|
45
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require 'doodle'
|
3
|
+
|
4
|
+
# collect works for anything that provides a :<< method
|
5
|
+
|
6
|
+
class Text < Doodle::Base
|
7
|
+
has :text, :init => "", :collect => :part
|
8
|
+
end
|
9
|
+
|
10
|
+
text = Text do
|
11
|
+
part "Hello"
|
12
|
+
part " "
|
13
|
+
part "World"
|
14
|
+
end
|
15
|
+
|
16
|
+
puts text.text
|
17
|
+
|
18
|
+
class Lines < Doodle::Base
|
19
|
+
has :lines, :init => [], :collect => :line
|
20
|
+
end
|
21
|
+
|
22
|
+
lines = Lines do
|
23
|
+
line "Hello"
|
24
|
+
line "World"
|
25
|
+
end
|
26
|
+
|
27
|
+
puts lines.lines.join("\n")
|
28
|
+
|
29
|
+
require 'set'
|
30
|
+
|
31
|
+
class Thing < Doodle::Base
|
32
|
+
has :properties, :init => Set.new, :collect => :prop
|
33
|
+
end
|
34
|
+
|
35
|
+
# this is a contrived example
|
36
|
+
thing = Thing do
|
37
|
+
prop 'name'
|
38
|
+
prop 'size'
|
39
|
+
prop 'size'
|
40
|
+
prop 'name'
|
41
|
+
end
|
42
|
+
|
43
|
+
thing.prop 'name'
|
44
|
+
p thing
|
45
|
+
|
46
|
+
# >> [:collector_klass, nil]
|
47
|
+
# >> [:collector_klass, nil]
|
48
|
+
# >> [:collector_klass, nil]
|
49
|
+
# >> [:collector_klass, nil]
|
50
|
+
# >> Hello World
|
51
|
+
# >> [:collector_klass, nil]
|
52
|
+
# >> Hello
|
53
|
+
# >> World
|
54
|
+
# >> [:collector_klass, nil]
|
55
|
+
# >> #<Thing:0xb7b6a6b8 @properties=#<Set: {"name", "size"}>>
|
data/lib/doodle.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
# Copyright (C) 2007 by Sean O'Halpin, 2007-11-24
|
3
3
|
|
4
4
|
require 'molic_orderedhash' # todo[replace this with own (required functions only) version]
|
5
|
+
require 'pp'
|
6
|
+
#require 'bleak_house' if ENV['BLEAK_HOUSE']
|
5
7
|
|
6
8
|
# *doodle* is my attempt at an eco-friendly metaprogramming framework that does not
|
7
9
|
# have pollute core Ruby objects such as Object, Class and Module.
|
@@ -12,7 +14,33 @@ require 'molic_orderedhash' # todo[replace this with own (required functions on
|
|
12
14
|
|
13
15
|
# Docs at http://doodle.rubyforge.org
|
14
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
|
+
|
15
32
|
module Doodle
|
33
|
+
VERSION = '0.0.9'
|
34
|
+
# where are we?
|
35
|
+
class << self
|
36
|
+
def context
|
37
|
+
Thread.current[:doodle_context] ||= []
|
38
|
+
end
|
39
|
+
def parent
|
40
|
+
context[-2]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
16
44
|
module Debug
|
17
45
|
class << self
|
18
46
|
# output result of block if ENV['DEBUG_DOODLE'] set
|
@@ -34,12 +62,24 @@ module Doodle
|
|
34
62
|
end
|
35
63
|
end
|
36
64
|
|
65
|
+
# error handling
|
66
|
+
@@raise_exception_on_error = true
|
67
|
+
def self.raise_exception_on_error
|
68
|
+
@@raise_exception_on_error
|
69
|
+
end
|
70
|
+
def self.raise_exception_on_error=(tf)
|
71
|
+
@@raise_exception_on_error = tf
|
72
|
+
end
|
73
|
+
|
37
74
|
# internal error raised when a default was expected but not found
|
38
75
|
class NoDefaultError < Exception
|
39
76
|
end
|
40
77
|
# raised when a validation rule returns false
|
41
78
|
class ValidationError < Exception
|
42
79
|
end
|
80
|
+
# raised when a validation rule returns false
|
81
|
+
class UnknownAttributeError < Exception
|
82
|
+
end
|
43
83
|
# raised when a conversion fails
|
44
84
|
class ConversionError < Exception
|
45
85
|
end
|
@@ -59,39 +99,12 @@ module Doodle
|
|
59
99
|
sc
|
60
100
|
end
|
61
101
|
|
62
|
-
# return self if a Module, else the singleton class
|
63
|
-
def self_class
|
64
|
-
self.kind_of?(Module) ? self : singleton_class
|
65
|
-
end
|
66
|
-
|
67
|
-
# frankly a hack to allow init options to work for singleton classes
|
68
|
-
def class_init(params = {}, &block)
|
69
|
-
sc = singleton_class(&block)
|
70
|
-
sc.attributes.select{|n, a| a.init_defined? }.each do |n, a|
|
71
|
-
send(n, a.init)
|
72
|
-
end
|
73
|
-
sc
|
74
|
-
end
|
75
102
|
end
|
76
103
|
|
77
104
|
# provide an alternative inheritance chain that works for singleton
|
78
105
|
# classes as well as modules, classes and instances
|
79
106
|
module Inherited
|
80
107
|
|
81
|
-
# def supers
|
82
|
-
# supers = []
|
83
|
-
# s = superclass rescue nil
|
84
|
-
# while !s.nil?
|
85
|
-
# supers << s
|
86
|
-
# last_s = s.superclass rescue nil
|
87
|
-
# if last_s == s
|
88
|
-
# last_s = nil
|
89
|
-
# end
|
90
|
-
# s = last_s
|
91
|
-
# end
|
92
|
-
# supers
|
93
|
-
# end
|
94
|
-
|
95
108
|
# parents returns the set of parent classes of an object.
|
96
109
|
# note[this is horribly complicated and kludgy - is there a better way?
|
97
110
|
# could do with refactoring]
|
@@ -102,13 +115,7 @@ module Doodle
|
|
102
115
|
klasses = []
|
103
116
|
if defined?(superclass)
|
104
117
|
klass = superclass
|
105
|
-
|
106
|
-
if self == superclass
|
107
|
-
# d { [:parents, 'self == superclass'] }
|
108
|
-
klass = nil
|
109
|
-
else
|
110
|
-
#p [:klass_singleton_class, klass]
|
111
|
-
#p [:parents, 'klass = superclass', self, klass, self.ancestors]
|
118
|
+
if self != superclass
|
112
119
|
#
|
113
120
|
# fixme[any other way to do this? seems really clunky to have to hack strings]
|
114
121
|
#
|
@@ -118,38 +125,21 @@ module Doodle
|
|
118
125
|
if cap = self.to_s.match(regex)
|
119
126
|
if cap.captures.size > 0
|
120
127
|
k = const_get(cap[1])
|
128
|
+
# push onto front of array
|
121
129
|
if k.respond_to?(:superclass) && k.superclass.respond_to?(:singleton_class)
|
122
130
|
klasses.unshift k.superclass.singleton_class
|
123
131
|
end
|
124
132
|
end
|
125
|
-
|
126
|
-
#p [:klasses, klasses]
|
127
|
-
loop do
|
128
|
-
if klass.nil?
|
129
|
-
break
|
130
|
-
end
|
133
|
+
until klass.nil?
|
131
134
|
klasses.unshift klass
|
132
|
-
#p [:loop_klasses, klasses]
|
133
135
|
if klass == klass.superclass
|
134
|
-
#p [:HERE_HERE_BEFORE, klasses]
|
135
|
-
#break
|
136
136
|
return klasses # oof
|
137
137
|
end
|
138
138
|
klass = klass.superclass
|
139
139
|
end
|
140
|
-
#p [:HERE_HERE, klasses]
|
141
140
|
else
|
142
|
-
|
143
|
-
#p [:klasses, klasses]
|
144
|
-
loop do
|
145
|
-
if klass.nil?
|
146
|
-
break
|
147
|
-
end
|
141
|
+
until klass.nil?
|
148
142
|
klasses << klass
|
149
|
-
#p [:loop_klasses, klasses]
|
150
|
-
if klass == klass.superclass
|
151
|
-
break
|
152
|
-
end
|
153
143
|
klass = klass.superclass
|
154
144
|
end
|
155
145
|
end
|
@@ -157,21 +147,11 @@ module Doodle
|
|
157
147
|
end
|
158
148
|
else
|
159
149
|
klass = self.class
|
160
|
-
|
161
|
-
#p [:klasses, klasses]
|
162
|
-
loop do
|
163
|
-
if klass.nil?
|
164
|
-
break
|
165
|
-
end
|
150
|
+
until klass.nil?
|
166
151
|
klasses << klass
|
167
|
-
#p [:loop_klasses, klasses]
|
168
|
-
if klass == klass.superclass
|
169
|
-
break
|
170
|
-
end
|
171
152
|
klass = klass.superclass
|
172
153
|
end
|
173
154
|
end
|
174
|
-
#p [:HERE_HERE_END, klasses]
|
175
155
|
klasses
|
176
156
|
end
|
177
157
|
|
@@ -181,7 +161,6 @@ module Doodle
|
|
181
161
|
klasses = parents
|
182
162
|
#p [:parents, parents]
|
183
163
|
# d { [:collect_inherited, :parents, message, klasses] }
|
184
|
-
#klasses = self_class.ancestors # this produces quite different behaviour
|
185
164
|
klasses.each do |klass|
|
186
165
|
#p [:testing, klass]
|
187
166
|
if klass.respond_to?(message)
|
@@ -202,7 +181,7 @@ module Doodle
|
|
202
181
|
#
|
203
182
|
# this works down to third level <tt>class << self</tt> - in practice, this is
|
204
183
|
# perfectly good - it would be great to have a completely general
|
205
|
-
# solution but I'm doubt whether the payoff is worth the
|
184
|
+
# solution but I'm doubt whether the payoff is worth the effort
|
206
185
|
|
207
186
|
module Embrace
|
208
187
|
# fake module inheritance chain
|
@@ -219,8 +198,8 @@ module Doodle
|
|
219
198
|
# ensure that subclasses are also embraced
|
220
199
|
define_method :inherited do |klass|
|
221
200
|
#p [:embrace, :inherited, klass]
|
222
|
-
klass.send(:embrace, other)
|
223
|
-
klass.send(:include, Factory)
|
201
|
+
klass.send(:embrace, other) # n.b. closure
|
202
|
+
klass.send(:include, Factory) # is there another way to do this? i.e. not in embrace
|
224
203
|
super(klass) if defined?(super)
|
225
204
|
end
|
226
205
|
}
|
@@ -228,14 +207,14 @@ module Doodle
|
|
228
207
|
end
|
229
208
|
end
|
230
209
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
@
|
210
|
+
class SaveBlock
|
211
|
+
attr_accessor :block
|
212
|
+
def initialize(arg_block = nil, &block)
|
213
|
+
arg_block = block if block_given?
|
214
|
+
@block = arg_block
|
236
215
|
end
|
237
216
|
end
|
238
|
-
|
217
|
+
|
239
218
|
# A Validation represents a validation rule applied to the instance
|
240
219
|
# after initialization. Generated using the Doodle::BaseMethods#must directive.
|
241
220
|
class Validation
|
@@ -251,22 +230,13 @@ module Doodle
|
|
251
230
|
end
|
252
231
|
|
253
232
|
class DoodleInfo
|
254
|
-
DOODLES = {}
|
255
|
-
@@raise_exception_on_error = true
|
256
233
|
attr_accessor :local_attributes
|
257
234
|
attr_accessor :local_validations
|
258
235
|
attr_accessor :local_conversions
|
259
236
|
attr_accessor :validation_on
|
260
237
|
attr_accessor :arg_order
|
261
238
|
attr_accessor :errors
|
262
|
-
|
263
|
-
def self.raise_exception_on_error
|
264
|
-
@@raise_exception_on_error
|
265
|
-
end
|
266
|
-
def self.raise_exception_on_error=(tf)
|
267
|
-
@@raise_exception_on_error = tf
|
268
|
-
end
|
269
|
-
|
239
|
+
|
270
240
|
def initialize(object)
|
271
241
|
@local_attributes = OrderedHash.new
|
272
242
|
@local_validations = []
|
@@ -274,13 +244,6 @@ module Doodle
|
|
274
244
|
@local_conversions = {}
|
275
245
|
@arg_order = []
|
276
246
|
@errors = []
|
277
|
-
|
278
|
-
oid = object.object_id
|
279
|
-
ObjectSpace.define_finalizer(object) do
|
280
|
-
# this seems to be called only on exit
|
281
|
-
Doodle::Debug.d { "finalizing #{oid}" }
|
282
|
-
DOODLES.delete(oid)
|
283
|
-
end
|
284
247
|
end
|
285
248
|
end
|
286
249
|
|
@@ -290,36 +253,53 @@ module Doodle
|
|
290
253
|
include SelfClass
|
291
254
|
include Inherited
|
292
255
|
|
293
|
-
# this is the only way to get at internal values
|
294
|
-
#
|
295
|
-
|
256
|
+
# this is the only way to get at internal values. Note: this is
|
257
|
+
# initialized on the fly rather than in #initialize because
|
258
|
+
# classes and singletons don't call #initialize
|
296
259
|
def __doodle__
|
297
|
-
|
260
|
+
@__doodle__ ||= DoodleInfo.new(self)
|
298
261
|
end
|
299
262
|
private :__doodle__
|
300
|
-
|
301
|
-
#
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
263
|
+
|
264
|
+
# hack to fool yaml (and anything else that queries instance_variables)
|
265
|
+
# pick a name that no-one else is likely to use
|
266
|
+
alias :seoh_doodle_instance_variables_8b016735_bd60_44d9_bda5_5f9d0aade5a6 :instance_variables
|
267
|
+
# redefine instance_variables to ignore our private @__doodle__ variable
|
268
|
+
def instance_variables
|
269
|
+
seoh_doodle_instance_variables_8b016735_bd60_44d9_bda5_5f9d0aade5a6.reject{ |x| x == '@__doodle__'}
|
270
|
+
end
|
271
|
+
|
272
|
+
def marshal_dump
|
273
|
+
instance_variables.map{|x| [x, instance_variable_get(x)] }
|
274
|
+
end
|
275
|
+
def marshal_load(data)
|
276
|
+
data.each do |name, value|
|
277
|
+
instance_variable_set(name, value)
|
306
278
|
end
|
307
279
|
end
|
308
280
|
|
309
|
-
#
|
281
|
+
# where should I put this?
|
310
282
|
def errors
|
311
|
-
|
312
|
-
e.push(*a.send(:__doodle__).errors)
|
313
|
-
e
|
314
|
-
end
|
315
|
-
errs.push(__doodle__.errors).reject{|x| x.size == 0}
|
283
|
+
__doodle__.errors
|
316
284
|
end
|
285
|
+
|
317
286
|
def clear_errors
|
318
|
-
|
319
|
-
a.send(:__doodle__).errors.clear
|
320
|
-
end
|
287
|
+
#pp [:clear_errors, self, caller]
|
321
288
|
__doodle__.errors.clear
|
322
289
|
end
|
290
|
+
|
291
|
+
# handle errors either by collecting in :errors or raising an exception
|
292
|
+
def handle_error(name, *args)
|
293
|
+
#pp [:caller, self, caller]
|
294
|
+
#pp [:handle_error, name, args]
|
295
|
+
# don't include duplicates (FIXME: hacky - shouldn't have duplicates in the first place)
|
296
|
+
if !self.errors.include?([name, *args])
|
297
|
+
self.errors << [name, *args]
|
298
|
+
end
|
299
|
+
if Doodle.raise_exception_on_error
|
300
|
+
raise(*args)
|
301
|
+
end
|
302
|
+
end
|
323
303
|
|
324
304
|
# return attributes defined in instance
|
325
305
|
def local_attributes
|
@@ -413,13 +393,7 @@ module Doodle
|
|
413
393
|
ivar = "@#{name}"
|
414
394
|
if instance_variable_defined?(ivar)
|
415
395
|
## d { [:_getter, 2, name, block] }
|
416
|
-
|
417
|
-
#d { [:_getter, :defined, name, v] }
|
418
|
-
# if v.kind_of?(Lazy)
|
419
|
-
# p [name, self, self.class, v]
|
420
|
-
# v = instance_eval &v.block
|
421
|
-
# end
|
422
|
-
v
|
396
|
+
instance_variable_get(ivar)
|
423
397
|
else
|
424
398
|
# handle default
|
425
399
|
# Note: use :init => value to cover cases where defaults don't work
|
@@ -427,14 +401,17 @@ module Doodle
|
|
427
401
|
att = lookup_attribute(name)
|
428
402
|
#d { [:getter, name, att, block] }
|
429
403
|
if att.default_defined?
|
430
|
-
|
404
|
+
case att.default
|
405
|
+
when SaveBlock
|
406
|
+
instance_eval(&att.default.block)
|
407
|
+
when Proc
|
431
408
|
instance_eval(&att.default)
|
432
409
|
else
|
433
410
|
att.default
|
434
411
|
end
|
435
412
|
else
|
436
413
|
# This is an internal error (i.e. shouldn't happen)
|
437
|
-
handle_error name, NoDefaultError, "
|
414
|
+
handle_error name, NoDefaultError, "Error - '#{name}' has no default defined", [caller[-1]]
|
438
415
|
end
|
439
416
|
end
|
440
417
|
end
|
@@ -442,15 +419,18 @@ module Doodle
|
|
442
419
|
|
443
420
|
# set an attribute by name - apply validation if defined
|
444
421
|
def _setter(name, *args, &block)
|
445
|
-
#
|
422
|
+
#pp [:_setter, self, self.class, name, args, block]
|
446
423
|
ivar = "@#{name}"
|
447
|
-
|
424
|
+
if block_given?
|
425
|
+
args.unshift(SaveBlock.new(block))
|
426
|
+
#p [:instance_eval_block, self, block]
|
427
|
+
end
|
448
428
|
# d { [:_setter, 3, :setting, name, ivar, args] }
|
449
429
|
att = lookup_attribute(name)
|
450
430
|
# d { [:_setter, 4, :setting, name, att] }
|
451
431
|
if att
|
452
432
|
#d { [:_setter, :instance_variable_set, :ivar, ivar, :args, args, :att_validate, att.validate(*args) ] }
|
453
|
-
v = instance_variable_set(ivar, att.validate(*args))
|
433
|
+
v = instance_variable_set(ivar, att.validate(self, *args))
|
454
434
|
else
|
455
435
|
#d { [:_setter, :instance_variable_set, ivar, args ] }
|
456
436
|
v = instance_variable_set(ivar, *args)
|
@@ -471,7 +451,7 @@ module Doodle
|
|
471
451
|
end
|
472
452
|
# d { [:from, conversions] }
|
473
453
|
else
|
474
|
-
convert(*args)
|
454
|
+
convert(self, *args)
|
475
455
|
end
|
476
456
|
end
|
477
457
|
|
@@ -493,10 +473,11 @@ module Doodle
|
|
493
473
|
end
|
494
474
|
|
495
475
|
# convert a value according to conversion rules
|
496
|
-
def convert(
|
476
|
+
def convert(owner, *args)
|
497
477
|
begin
|
478
|
+
value = args.first
|
498
479
|
if (converter = conversions[value.class])
|
499
|
-
value = converter[
|
480
|
+
value = converter[*args]
|
500
481
|
else
|
501
482
|
# try to find nearest ancestor
|
502
483
|
ancestors = value.class.ancestors
|
@@ -507,24 +488,25 @@ module Doodle
|
|
507
488
|
converter_class = ancestors[indexed_matches.min]
|
508
489
|
#p [:converter, converter_class]
|
509
490
|
if converter = conversions[converter_class]
|
510
|
-
value = converter[
|
491
|
+
value = converter[*args]
|
511
492
|
end
|
512
493
|
end
|
513
494
|
end
|
514
495
|
rescue => e
|
515
|
-
handle_error name, ConversionError, e.to_s, [caller[-1]]
|
496
|
+
owner.handle_error name, ConversionError, e.to_s, [caller[-1]]
|
516
497
|
end
|
517
498
|
value
|
518
499
|
end
|
519
500
|
|
520
501
|
# validate that args meet rules defined with +must+
|
521
|
-
def validate(*args)
|
522
|
-
|
502
|
+
def validate(owner, *args)
|
503
|
+
Doodle::Debug.d { [:validate, self, :owner, owner, :args, args ] }
|
504
|
+
value = convert(owner, *args)
|
523
505
|
#d { [:validate, self, :args, args, :value, value ] }
|
524
506
|
validations.each do |v|
|
525
|
-
Doodle::Debug.d { [:validate, self, v, args ] }
|
507
|
+
Doodle::Debug.d { [:validate, self, v, args, value] }
|
526
508
|
if !v.block[value]
|
527
|
-
handle_error name, ValidationError, "#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })", [caller[-1]]
|
509
|
+
owner.handle_error name, ValidationError, "#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })", [caller[-1]]
|
528
510
|
end
|
529
511
|
end
|
530
512
|
#d { [:validate, :value, value ] }
|
@@ -533,11 +515,21 @@ module Doodle
|
|
533
515
|
|
534
516
|
# define a getter_setter
|
535
517
|
def define_getter_setter(name, *args, &block)
|
536
|
-
# d { [:define_getter_setter, [self, self.class
|
518
|
+
# d { [:define_getter_setter, [self, self.class], name, args, block] }
|
537
519
|
|
538
520
|
# need to use string eval because passing block
|
539
|
-
module_eval "def #{name}(*args, &block); getter_setter(:#{name}, *args, &block); end"
|
540
|
-
module_eval "def #{name}=(*args, &block); _setter(:#{name}, *args); end"
|
521
|
+
module_eval "def #{name}(*args, &block); getter_setter(:#{name}, *args, &block); end", __FILE__, __LINE__
|
522
|
+
module_eval "def #{name}=(*args, &block); _setter(:#{name}, *args); end", __FILE__, __LINE__
|
523
|
+
|
524
|
+
# this is how it should be done (in 1.9)
|
525
|
+
# module_eval {
|
526
|
+
# define_method name do |*args, &block|
|
527
|
+
# getter_setter(name.to_sym, *args, &block)
|
528
|
+
# end
|
529
|
+
# define_method "#{name}=" do |*args, &block|
|
530
|
+
# _setter(name.to_sym, *args, &block)
|
531
|
+
# end
|
532
|
+
# }
|
541
533
|
end
|
542
534
|
private :define_getter_setter
|
543
535
|
|
@@ -546,9 +538,9 @@ module Doodle
|
|
546
538
|
def define_collector(collection, name, klass = nil, &block)
|
547
539
|
# need to use string eval because passing block
|
548
540
|
if klass.nil?
|
549
|
-
module_eval "def #{name}(*args, &block); args.unshift(block) if block_given?; #{collection}.<<(*args); end"
|
541
|
+
module_eval "def #{name}(*args, &block); args.unshift(block) if block_given?; #{collection}.<<(*args); end", __FILE__, __LINE__
|
550
542
|
else
|
551
|
-
module_eval "def #{name}(*args, &block); #{collection} << #{klass}.new(*args, &block); end"
|
543
|
+
module_eval "def #{name}(*args, &block); #{collection} << #{klass}.new(*args, &block); end", __FILE__, __LINE__
|
552
544
|
end
|
553
545
|
end
|
554
546
|
private :define_collector
|
@@ -576,7 +568,7 @@ module Doodle
|
|
576
568
|
# end
|
577
569
|
#
|
578
570
|
def has(*args, &block)
|
579
|
-
Doodle::Debug.d { [:has, self, self.class,
|
571
|
+
Doodle::Debug.d { [:has, self, self.class, args] }
|
580
572
|
name = args.shift.to_sym
|
581
573
|
# d { [:has2, name, args] }
|
582
574
|
key_values, positional_args = args.partition{ |x| x.kind_of?(Hash)}
|
@@ -586,24 +578,42 @@ module Doodle
|
|
586
578
|
params = key_values.inject(params){ |acc, item| acc.merge(item)}
|
587
579
|
|
588
580
|
# don't pass collector params through to Attribute
|
581
|
+
collector_klass = nil
|
589
582
|
if collector = params.delete(:collect)
|
583
|
+
if !params.key?(:init)
|
584
|
+
params[:init] = []
|
585
|
+
end
|
590
586
|
if collector.kind_of?(Hash)
|
591
|
-
collector_name,
|
587
|
+
collector_name, collector_klass = collector.to_a[0]
|
592
588
|
else
|
593
589
|
# if Capitalized word given, treat as classname
|
594
590
|
# and create collector for specific class
|
595
|
-
|
596
|
-
collector_name = Utils.snake_case(
|
597
|
-
if
|
598
|
-
|
591
|
+
collector_klass = collector.to_s
|
592
|
+
collector_name = Utils.snake_case(collector_klass)
|
593
|
+
if collector_klass !~ /^[A-Z]/
|
594
|
+
collector_klass = nil
|
599
595
|
end
|
600
596
|
end
|
601
|
-
define_collector name, collector_name,
|
597
|
+
define_collector name, collector_name, collector_klass
|
602
598
|
end
|
603
599
|
|
604
600
|
# d { [:has_args, :params, params] }
|
605
|
-
|
601
|
+
# define getter setter before setting up attribute
|
606
602
|
define_getter_setter name, *args, &block
|
603
|
+
local_attributes[name] = attribute = Attribute.new(params, &block)
|
604
|
+
# if a collector has been defined and has a specific class, then you can pass in an array of hashes
|
605
|
+
if collector_klass
|
606
|
+
attribute.instance_eval {
|
607
|
+
from Enumerable do |enum|
|
608
|
+
if !collector_klass.kind_of?(Class)
|
609
|
+
tmp_klass = self.class.const_get(collector_klass)
|
610
|
+
else
|
611
|
+
tmp_klass = collector_klass
|
612
|
+
end
|
613
|
+
enum.map{|x| tmp_klass.new(x)}
|
614
|
+
end
|
615
|
+
}
|
616
|
+
end
|
607
617
|
attribute
|
608
618
|
end
|
609
619
|
|
@@ -647,11 +657,17 @@ module Doodle
|
|
647
657
|
hash[n] = begin
|
648
658
|
case a.init
|
649
659
|
when NilClass, TrueClass, FalseClass, Fixnum
|
660
|
+
#p [:init, :no_clone]
|
650
661
|
a.init
|
662
|
+
when SaveBlock
|
663
|
+
#p [:init, :save_block]
|
664
|
+
instance_eval(&a.init.block)
|
651
665
|
else
|
666
|
+
#p [:init, :clone]
|
652
667
|
a.init.clone
|
653
668
|
end
|
654
|
-
rescue
|
669
|
+
rescue Exception => e
|
670
|
+
#p [:init, :rescue, e]
|
655
671
|
a.init
|
656
672
|
end
|
657
673
|
; hash }
|
@@ -672,7 +688,8 @@ module Doodle
|
|
672
688
|
if respond_to?(key)
|
673
689
|
send(key, key_values[key])
|
674
690
|
else
|
675
|
-
|
691
|
+
# raise error if not defined
|
692
|
+
handle_error key, Doodle::UnknownAttributeError, "Unknown attribute '#{key}' #{key_values[key].inspect}"
|
676
693
|
end
|
677
694
|
end
|
678
695
|
end
|
@@ -688,6 +705,10 @@ module Doodle
|
|
688
705
|
# validate this object by applying all validations in sequence
|
689
706
|
# - if all == true, validate all attributes, e.g. when loaded from YAML, else validate at object level only
|
690
707
|
def validate!(all = true)
|
708
|
+
#pp [:validate!, all, caller]
|
709
|
+
if all
|
710
|
+
clear_errors
|
711
|
+
end
|
691
712
|
#Doodle::Debug.d { [:validate!, self] }
|
692
713
|
#Doodle::Debug.d { [:validate!, self, __doodle__.validation_on] }
|
693
714
|
if __doodle__.validation_on
|
@@ -700,13 +721,14 @@ module Doodle
|
|
700
721
|
end
|
701
722
|
# if all == true, reset values so conversions and validations are applied to raw instance variables
|
702
723
|
# e.g. when loaded from YAML
|
703
|
-
|
704
|
-
|
724
|
+
att_name = "@#{att.name}"
|
725
|
+
if all && instance_variable_defined?(att_name)
|
726
|
+
send(att.name, instance_variable_get(att_name))
|
705
727
|
end
|
706
728
|
end
|
707
729
|
# now apply instance level validations
|
708
730
|
validations.each do |v|
|
709
|
-
|
731
|
+
Doodle::Debug.d { [:validate!, self, v ] }
|
710
732
|
if !instance_eval(&v.block)
|
711
733
|
handle_error :validate!, ValidationError, "#{ self.inspect } must #{ v.message }", [caller[-1]]
|
712
734
|
end
|
@@ -735,13 +757,16 @@ module Doodle
|
|
735
757
|
# hash of keyword value pairs and a block which is instance_eval'd
|
736
758
|
def initialize(*args, &block)
|
737
759
|
__doodle__.validation_on = true
|
738
|
-
|
760
|
+
#p [:Doodle_context_push, self]
|
761
|
+
Doodle.context.push(self)
|
739
762
|
defer_validation do
|
740
763
|
# d { [:initialize, self.to_s, args, block] }
|
741
764
|
initialize_from_hash(*args)
|
742
765
|
# d { [:initialize, self.to_s, args, block, :calling_block] }
|
743
766
|
instance_eval(&block) if block_given?
|
744
767
|
end
|
768
|
+
Doodle.context.pop
|
769
|
+
#p [:Doodle_context_pop, ]
|
745
770
|
end
|
746
771
|
|
747
772
|
end
|
@@ -762,38 +787,40 @@ module Doodle
|
|
762
787
|
# stimpy = Dog(:name => 'Stimpy')
|
763
788
|
# etc.
|
764
789
|
module Factory
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
790
|
+
RX_IDENTIFIER = /^[A-Za-z_][A-Za-z_0-9]+\??$/
|
791
|
+
class << self
|
792
|
+
# create a factory function in appropriate module for the specified class
|
793
|
+
def factory(konst)
|
794
|
+
#p [:factory, name]
|
795
|
+
name = konst.to_s
|
796
|
+
names = name.split(/::/)
|
797
|
+
name = names.pop
|
798
|
+
if names.empty?
|
799
|
+
# top level class - should be available to all
|
800
|
+
klass = Object
|
801
|
+
#p [:names_empty, klass, mklass]
|
802
|
+
if !klass.respond_to?(name) && name =~ Factory::RX_IDENTIFIER
|
803
|
+
eval("def #{ name }(*args, &block); ::#{name}.new(*args, &block); end", ::TOPLEVEL_BINDING, __FILE__, __LINE__)
|
804
|
+
end
|
805
|
+
else
|
806
|
+
klass = names.inject(self) {|c, n| c.const_get(n)}
|
807
|
+
mklass = class << klass; self; end
|
808
|
+
#p [:names, klass, mklass]
|
809
|
+
# TODO: check how many times this is being called
|
810
|
+
if !klass.respond_to?(name) && name =~ Factory::RX_IDENTIFIER
|
811
|
+
klass.class_eval("def self.#{name}(*args, &block); #{name}.new(*args, &block); end", __FILE__, __LINE__)
|
812
|
+
end
|
783
813
|
end
|
814
|
+
#p [:factory, mklass, klass, src]
|
815
|
+
end
|
816
|
+
|
817
|
+
# inherit the factory function capability
|
818
|
+
def included(other)
|
819
|
+
#p [:factory, :included, self, other ]
|
820
|
+
super
|
821
|
+
# make +factory+ method available
|
822
|
+
factory other
|
784
823
|
end
|
785
|
-
#p [:factory, mklass, klass, src]
|
786
|
-
end
|
787
|
-
# inherit the factory function capability
|
788
|
-
def self.included(other)
|
789
|
-
#p [:factory, :included, self, other ]
|
790
|
-
super
|
791
|
-
#raise Exception, "#{self} can only be included in a Class" if !other.kind_of? Class
|
792
|
-
# make +factory+ method available
|
793
|
-
other.extend self
|
794
|
-
other.module_eval {
|
795
|
-
factory
|
796
|
-
}
|
797
824
|
end
|
798
825
|
end
|
799
826
|
|
@@ -807,6 +834,7 @@ module Doodle
|
|
807
834
|
other.module_eval {
|
808
835
|
extend Embrace
|
809
836
|
embrace BaseMethods
|
837
|
+
include Factory
|
810
838
|
}
|
811
839
|
end
|
812
840
|
end
|
@@ -834,13 +862,13 @@ module Doodle
|
|
834
862
|
|
835
863
|
# is this attribute optional? true if it has a default defined for it
|
836
864
|
def optional?
|
837
|
-
|
865
|
+
default_defined? or init_defined?
|
838
866
|
end
|
839
867
|
|
840
868
|
# an attribute is required if it has no default or initial value defined for it
|
841
869
|
def required?
|
842
870
|
# d { [:default?, self.class, self.name, instance_variable_defined?("@default"), @default] }
|
843
|
-
!
|
871
|
+
!optional?
|
844
872
|
end
|
845
873
|
|
846
874
|
# has default been defined?
|
@@ -853,12 +881,18 @@ module Doodle
|
|
853
881
|
end
|
854
882
|
|
855
883
|
# name of attribute
|
856
|
-
has :name
|
884
|
+
has :name, :kind => Symbol do
|
885
|
+
from String do |s|
|
886
|
+
s.to_sym
|
887
|
+
end
|
888
|
+
end
|
857
889
|
# default value (can be a block)
|
858
|
-
has :default
|
890
|
+
has :default, :default => nil
|
859
891
|
# initial value
|
860
|
-
has :init
|
892
|
+
has :init, :default => nil
|
893
|
+
|
861
894
|
end
|
895
|
+
|
862
896
|
end
|
863
897
|
|
864
898
|
############################################################
|