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