hanswurst 0.5.5 → 0.5.6
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/README.md +187 -86
- data/VERSION +1 -1
- data/examples/bear_julius.rb +45 -0
- data/examples/first_marriage.rb +64 -0
- data/examples/immortal_julius.rb +25 -0
- data/examples/marriage_improvement.rb +50 -0
- data/examples/role.rb +24 -0
- data/examples/second_marriage.rb +69 -0
- data/lib/hanswurst.rb +15 -0
- data/lib/hanswurst/as.rb +35 -7
- data/lib/hanswurst/doc.rb +15 -0
- data/lib/hanswurst/instance_methods.rb +4 -1
- data/lib/hanswurst/method_missing.rb +5 -0
- data/test/examples/test_bear_julius.rb +11 -0
- data/test/examples/test_first_marriage.rb +11 -0
- data/test/examples/test_immortal_julius.rb +11 -0
- data/test/examples/test_marriage_improvement.rb +15 -0
- data/test/examples/test_second_marriage.rb +15 -0
- data/test/helper.rb +13 -0
- data/test/test_hanswurst.rb +19 -3
- metadata +47 -24
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
hanswurst: Be a couch potato and play different roles
|
2
2
|
=====================
|
3
3
|
|
4
|
+
<strong>hanswurst is discontinued in favour of [https://github.com/metakeule/thingtank](https://github.com/metakeule/thingtank) using CouchRest Model and a slightly different concept</strong>
|
5
|
+
|
4
6
|
Hanswurst is a library that uses couch potato to create arbitrary
|
5
7
|
objects that may have different roles. The roles determine the
|
6
8
|
properties and they can be mixed and matched at will.
|
@@ -19,132 +21,231 @@ or in rvm:
|
|
19
21
|
gem install hanswurst
|
20
22
|
|
21
23
|
|
22
|
-
|
24
|
+
Examples:
|
23
25
|
--------
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
class Person
|
28
|
-
include CouchPotato::Persistence
|
27
|
+
To simplify the class building we let them all inherit from a role class
|
29
28
|
|
30
|
-
|
31
|
-
|
29
|
+
class Role
|
30
|
+
include CouchPotato::Persistence # add the couch potato features
|
31
|
+
include Hanswurst::Shares # allows to share roles
|
32
|
+
include Hanswurst::Delegates # delegate methods to methods of a property that is a role
|
33
|
+
include Hanswurst::Doc # access the doc from inside the role
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
+
# simplify saving
|
36
|
+
def save
|
37
|
+
CouchPotato.database.save_document _doc
|
35
38
|
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
Imagine a Caesar is born
|
43
|
+
|
44
|
+
class Birth < Role
|
45
|
+
property :date
|
46
|
+
property :place
|
36
47
|
|
37
|
-
validates_presence_of :
|
48
|
+
validates_presence_of :date # make sure a date is given
|
38
49
|
end
|
39
50
|
|
40
|
-
|
41
|
-
|
51
|
+
# just in case he might die....we might want to have a date and maybe even a place
|
52
|
+
class Dead < Birth
|
53
|
+
end
|
42
54
|
|
43
|
-
|
44
|
-
property :
|
55
|
+
class Person < Role
|
56
|
+
property :name
|
57
|
+
property :gender
|
58
|
+
shares :birth => Birth # requires the shared property :birth which is a Birth
|
59
|
+
shares :dead => Dead # requires the shared property :dead which is a Dead
|
45
60
|
end
|
46
61
|
|
47
|
-
|
62
|
+
julius = Hanswurst.new
|
63
|
+
julius.person = Person.new # the shared property :birth is propagated
|
64
|
+
julius.person.gender = "m"
|
65
|
+
julius.person.name = 'Gaius Iulius Caesar'
|
48
66
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
hw.person.last_name = 'Hessling' # an RoleNotValid error is raised if hw.person.last_name is missing
|
67
|
+
# we could directly setup the properties and don't have to do 'julius.birth = Birth.new' first
|
68
|
+
julius.birth.date = "100 BC"
|
69
|
+
julius.birth.place = "Rome"
|
53
70
|
|
54
|
-
#
|
55
|
-
hw = Hanswurst.new :person => Person.new(:first_name => 'Diederich', :last_name => 'Hessling')
|
56
|
-
|
57
|
-
# this is also possible
|
58
|
-
hw.product = Product.new :price => 20, :in_stock => 1
|
71
|
+
julius.person.save # doen't matter on with role you call save, see Role#save above
|
59
72
|
|
60
|
-
|
73
|
+
julius_id = julius._id
|
61
74
|
|
62
75
|
later...
|
63
76
|
|
64
|
-
|
77
|
+
julius = CouchPotato.database.load_document julius_id
|
78
|
+
julius.person.name # => 'Gaius Iulius Caesar'
|
79
|
+
julius.birth.place # => "Rome"
|
65
80
|
|
66
|
-
hw.person.first_name # => 'Diederich'
|
67
|
-
hw.person.name # => 'Diederich Hessling'
|
68
|
-
hw.product.price # => 20
|
69
81
|
|
70
|
-
|
82
|
+
when he is adult, he wants to marry. now things are getting a bit more complicated:
|
71
83
|
|
72
|
-
|
73
|
-
|
84
|
+
# he needs a marriage and a women
|
85
|
+
class Marriage < Role
|
86
|
+
property :date # the date of the marriage
|
87
|
+
property :end # when the marriage ended
|
88
|
+
property :spouse # doc_id of the spouse
|
89
|
+
property :state # current state of the marriage, e.g. 'married', 'widowed', 'divorced'
|
74
90
|
|
75
|
-
|
76
|
-
|
77
|
-
property :city
|
91
|
+
# ensure that 'spouse' is a fkey (doc_id) of a Person and that we have only one of them
|
92
|
+
validates :spouse, :hanswurst => {:class => Person, :fkey => true, :max => 1}
|
78
93
|
end
|
79
94
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
95
|
+
# and we don't want to load the spouses document manually just to get her name. so here is a role that does it for us
|
96
|
+
class Spouse < Role
|
97
|
+
shares :marriage => Marriage # depends on the shared marriage role
|
98
|
+
|
99
|
+
# convenience method to get name of the current spouse by doc.spouse.name
|
100
|
+
def name
|
101
|
+
CouchPotato.database.load_document(id).person.name if (id=marriage_spouse)
|
102
|
+
end
|
103
|
+
|
104
|
+
def marriage_spouse
|
105
|
+
m = marriage()
|
106
|
+
return m.spouse if m and m.status == 'married'
|
107
|
+
return nil
|
108
|
+
end
|
109
|
+
|
110
|
+
def marriage()
|
111
|
+
_doc.marriage # here we access the shared marriage role via the _doc (Hanswurst document)
|
112
|
+
end
|
92
113
|
end
|
93
114
|
|
94
|
-
hw = Hanswurst.new :employee => Person.new(:last_name => 'Hessling')
|
95
|
-
emp = hw.employee
|
96
|
-
emp.address_privat = Address.new :city => 'Berlin'
|
97
|
-
emp.address_work = Address.new :city => 'Potsdam'
|
98
115
|
|
99
|
-
|
116
|
+
now we could easily get julius married
|
117
|
+
|
118
|
+
conny = Hanswurst.new
|
119
|
+
conny.person = Person.new :gender => "f", :name => 'Cornelia'
|
120
|
+
conny.save
|
121
|
+
|
122
|
+
julius.marriage = Marriage.new
|
123
|
+
julius.marriage.date = "84 BC"
|
124
|
+
julius.marriage.spouse = conny._id
|
125
|
+
julius.marriage.state = 'married'
|
126
|
+
|
127
|
+
julius.spouse.name # => 'Cornelia'
|
128
|
+
|
129
|
+
while that is nice, let see if we could make it more comfortable:
|
130
|
+
|
131
|
+
class Marriage
|
132
|
+
shares :spouse => Spouse # we need a spouse as role of the doc
|
100
133
|
|
101
|
-
|
102
|
-
|
134
|
+
# marry a person that is or is not already saved or part of a Hanswurst
|
135
|
+
def marry(person)
|
103
136
|
|
104
|
-
|
105
|
-
|
106
|
-
|
137
|
+
# attach it to a hanswurst, if it is not already
|
138
|
+
person = Hanswurst.new(:person => person).person unless person.respond_to? :_doc # if it responds to :_doc it already has a hanswurst doc attached to it
|
139
|
+
|
140
|
+
# save the person if it is not already
|
141
|
+
person.save unless person._doc._id
|
142
|
+
|
143
|
+
# assign the doc_id to spouse
|
144
|
+
self.spouse = person._doc._id
|
145
|
+
self.state = 'married'
|
146
|
+
|
147
|
+
# now look if she is already married to him and make her marry him
|
148
|
+
unless self._doc && self._doc._id && person._doc.marriage_spouse == self._doc._id
|
149
|
+
person._doc.marriage = Marriage.new :date => self.date
|
150
|
+
person._doc.marriage.marry self
|
151
|
+
person.save
|
152
|
+
end
|
153
|
+
end
|
107
154
|
end
|
108
155
|
|
109
|
-
|
110
|
-
|
111
|
-
|
156
|
+
it now becomes much less work and Cornelia also knows that she is married to Julius
|
157
|
+
|
158
|
+
conny = Person.new(:gender => "f", :name => 'Cornelia')
|
159
|
+
julius.marriage = Marriage.new :date => "84 BC"
|
160
|
+
julius.marriage.marry conny
|
112
161
|
|
113
|
-
|
162
|
+
julius.spouse.name # => 'Cornelia'
|
163
|
+
conny.spouse.name # => 'Gaius Iulius Caesar'
|
114
164
|
|
115
|
-
|
165
|
+
julius could even marry a second time, i.e. marriage becomes an Array of Marriage objects
|
166
|
+
|
167
|
+
marriage = Marriage.new :date => "68-65 BC"
|
168
|
+
marriage.marry Person.new(:gender => "f", :name => 'Pompeia')
|
169
|
+
julius.marriage << marriage
|
170
|
+
|
171
|
+
# ouch!
|
172
|
+
julius.spouse.name # => Error
|
173
|
+
|
174
|
+
we should let Spouse know that Marriage might be an array, so simply overwrite marriage with
|
175
|
+
|
176
|
+
class Spouse
|
177
|
+
def marriage()
|
178
|
+
[_doc.marriage].flatten.last
|
179
|
+
end
|
116
180
|
end
|
117
181
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
182
|
+
now it works:
|
183
|
+
|
184
|
+
julius.spouse.name # => 'Pompeia'
|
185
|
+
|
186
|
+
julius.marriage.size # => 2
|
187
|
+
julius.marriage.first.spouse.name # => 'Cornelia'
|
122
188
|
|
123
|
-
|
189
|
+
# oops!
|
190
|
+
julius.marriage.first.status # => 'married'
|
124
191
|
|
125
|
-
|
126
|
-
include CouchPotato::Persistence
|
192
|
+
julius is still married with Cornelia but he should not
|
127
193
|
|
128
|
-
|
129
|
-
|
130
|
-
|
194
|
+
if Cornelia died before his second marriage, it should'nt be a problem:
|
195
|
+
|
196
|
+
class Dead
|
197
|
+
# all callbacks of roles are called and defined like corresponding callbacks of the doc
|
198
|
+
before_save do
|
199
|
+
if _doc.spouse && marriage=_doc.spouse.marriage
|
200
|
+
marriage.end = self.date # marriage ends with date of dead
|
201
|
+
marriage.state = 'widowed'
|
202
|
+
end
|
203
|
+
end
|
131
204
|
end
|
132
205
|
|
133
206
|
class Person
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
shares :address_privat => Address
|
138
|
-
shares :address_work => Address
|
139
|
-
|
140
|
-
property :last_name
|
141
|
-
property :first_name
|
207
|
+
def die(date)
|
208
|
+
_doc.dead = Dead.new :date => date
|
209
|
+
end
|
142
210
|
end
|
143
211
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
212
|
+
# ok first replay the marriage of conny and julius to prevent errors
|
213
|
+
conny.marriage = nil
|
214
|
+
conny.save
|
215
|
+
|
216
|
+
julius.marriage = Marriage.new :date => "84 BC"
|
217
|
+
julius.marriage.marry conny
|
218
|
+
|
219
|
+
# now conny dies
|
220
|
+
conny.die "68-65 BC"
|
221
|
+
|
222
|
+
# and julius may marry Pompeia
|
223
|
+
marriage = Marriage.new :date => "68-65 BC"
|
224
|
+
marriage.marry Person.new(:gender => "f", :name => 'Pompeia')
|
225
|
+
julius.marriage << marriage
|
226
|
+
|
227
|
+
julius.marriage.first.status # => 'widowed'
|
228
|
+
julius.marriage.first.spouse.name # => 'Cornelia'
|
229
|
+
|
230
|
+
julius.marriage.last.status # => 'married'
|
231
|
+
julius.marriage.last.spouse.name # => 'Pompeia'
|
232
|
+
|
233
|
+
julius.spouse.name # => 'Pompeia'
|
234
|
+
|
235
|
+
since julius is immortal, no one should be able to destroy him:
|
236
|
+
|
237
|
+
class Undestroyable < Role
|
238
|
+
before_save do
|
239
|
+
false # never allow to destroy
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
julius.immortal = Undestroyable.new
|
244
|
+
|
245
|
+
CouchPotato.database.destroy_document julius
|
246
|
+
|
247
|
+
CouchPotato.database.load_document julius._id # => julius is still there
|
248
|
+
|
148
249
|
All views are attached to the hanswurst design document
|
149
250
|
You may create general views:
|
150
251
|
|
@@ -152,10 +253,10 @@ You may create general views:
|
|
152
253
|
|
153
254
|
or views specific for a role
|
154
255
|
|
155
|
-
Hanswurst.view_for :
|
256
|
+
Hanswurst.view_for :immortal, :all, :key => :created_at
|
156
257
|
|
157
258
|
# execute them this way
|
158
|
-
CouchPotato.database.view Hanswurst.
|
259
|
+
CouchPotato.database.view Hanswurst.immortal_all
|
159
260
|
|
160
261
|
the same works with lists.
|
161
262
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.6
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative 'role.rb'
|
2
|
+
|
3
|
+
class Birth < Role
|
4
|
+
property :date
|
5
|
+
property :place
|
6
|
+
|
7
|
+
validates_presence_of :date # make sure a date is given
|
8
|
+
end
|
9
|
+
|
10
|
+
# just in case he might die....we might want to have a date and maybe even a place
|
11
|
+
class Dead < Birth
|
12
|
+
end
|
13
|
+
|
14
|
+
class Person < Role
|
15
|
+
property :name
|
16
|
+
property :gender
|
17
|
+
shares :birth => Birth # requires the shared property :birth which is a Birth
|
18
|
+
shares :dead => Dead # requires the shared property :dead which is a Dead
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
# include this in the tests
|
23
|
+
module HanswurstExampleTests
|
24
|
+
|
25
|
+
def bear_julius
|
26
|
+
julius = Hanswurst.new
|
27
|
+
julius.person = Person.new # the shared property :birth is propagated
|
28
|
+
julius.person.gender = "m"
|
29
|
+
julius.person.name = 'Gaius Iulius Caesar'
|
30
|
+
|
31
|
+
# we could directly setup the properties and don't have to do 'julius.birth = Birth.new' first
|
32
|
+
julius.birth.date = "100 BC"
|
33
|
+
julius.birth.place = "Rome"
|
34
|
+
|
35
|
+
julius.person.save # doen't matter on with role you call save, see Role#save above
|
36
|
+
|
37
|
+
julius = julius.person.load
|
38
|
+
|
39
|
+
assert_equal 'Gaius Iulius Caesar', julius.person.name
|
40
|
+
assert_equal 'Rome', julius.birth.place
|
41
|
+
|
42
|
+
return julius
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative 'bear_julius.rb'
|
2
|
+
|
3
|
+
# he needs a marriage and a women
|
4
|
+
class Marriage < Role
|
5
|
+
#shares :person => Person # we need to be a person in order to marry
|
6
|
+
|
7
|
+
property :date # the date of the marriage
|
8
|
+
property :end # when the marriage ended
|
9
|
+
property :spouse # doc_id of the spouse
|
10
|
+
property :state # current state of the marriage, e.g. 'married', 'widowed', 'divorced'
|
11
|
+
|
12
|
+
# ensure that 'spouse' is a fkey (doc_id) of a Person and that we have only one of them
|
13
|
+
validates :spouse, :hanswurst => {:class => Person, :fkey => true, :max => 1}
|
14
|
+
end
|
15
|
+
|
16
|
+
# and we don't want to load the spouses document manually just to get her name. so here is a role that does it for us
|
17
|
+
class Spouse < Role
|
18
|
+
# shares :marriage => Marriage # depends on the shared marriage role
|
19
|
+
|
20
|
+
# convenience method to get name of the current spouse by doc.spouse.name
|
21
|
+
def name
|
22
|
+
if (id=marriage_spouse)
|
23
|
+
return CouchPotato.database.load_document(id).person.name
|
24
|
+
end
|
25
|
+
return nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def marriage_spouse
|
29
|
+
m = marriage()
|
30
|
+
return m.spouse if m and m.state == 'married'
|
31
|
+
return nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def marriage()
|
35
|
+
_doc.marriage # here we access the shared marriage role via the _doc (Hanswurst document)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# include this in the tests
|
40
|
+
module HanswurstExampleTests
|
41
|
+
|
42
|
+
def first_marriage()
|
43
|
+
julius = bear_julius
|
44
|
+
|
45
|
+
conny = Hanswurst.new :person => Person.new(:gender => "f", :name => 'Cornelia')
|
46
|
+
conny.person.save
|
47
|
+
|
48
|
+
julius.marriage = Marriage.new
|
49
|
+
julius.marriage.date = "84 BC"
|
50
|
+
julius.marriage.spouse = conny._id
|
51
|
+
julius.marriage.state = 'married'
|
52
|
+
|
53
|
+
julius.spouse = Spouse.new
|
54
|
+
|
55
|
+
assert_equal 'Cornelia', julius.spouse.name
|
56
|
+
|
57
|
+
julius.person.save
|
58
|
+
julius = julius.person.load
|
59
|
+
|
60
|
+
assert_equal 'Cornelia', julius.spouse.name
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative 'bear_julius.rb'
|
2
|
+
|
3
|
+
class Undestroyable < Role
|
4
|
+
before_destroy do
|
5
|
+
false # never allow to destroy
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
module HanswurstExampleTests
|
12
|
+
def julius_immortal
|
13
|
+
julius = bear_julius()
|
14
|
+
julius.immortal = Undestroyable.new
|
15
|
+
julius.immortal.save
|
16
|
+
|
17
|
+
id = julius._id
|
18
|
+
julius = load id
|
19
|
+
|
20
|
+
CouchPotato.database.destroy_document julius
|
21
|
+
|
22
|
+
assert !load(id).nil? # => julius is still there
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative 'first_marriage.rb'
|
2
|
+
|
3
|
+
class Marriage
|
4
|
+
shares :spouse => Spouse # we need a spouse as role of the doc
|
5
|
+
|
6
|
+
# marry a person that is or is not already saved or part of a Hanswurst
|
7
|
+
def marry(person)
|
8
|
+
# attach it to a hanswurst, if it has not one yet
|
9
|
+
person = Hanswurst.new(:person => person).person unless person.respond_to?(:_doc) && person._doc
|
10
|
+
|
11
|
+
# save the person if it is not already
|
12
|
+
person.save unless person._doc._id
|
13
|
+
|
14
|
+
# assign the doc_id to spouse
|
15
|
+
self.spouse = person._doc._id
|
16
|
+
self.state = 'married'
|
17
|
+
|
18
|
+
unless person._doc.marriage
|
19
|
+
person._doc.marriage = Marriage.new :date => self.date
|
20
|
+
end
|
21
|
+
|
22
|
+
# now look if she is already married to him and make her marry him
|
23
|
+
unless self._doc && self._doc._id && person._doc.spouse.marriage_spouse == self._doc._id
|
24
|
+
person._doc.marriage.marry self._doc.person
|
25
|
+
person.save
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Spouse
|
31
|
+
def marriage()
|
32
|
+
[_doc.marriage].flatten.last
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# include this in the tests
|
37
|
+
module HanswurstExampleTests
|
38
|
+
def improved_marriage()
|
39
|
+
julius = bear_julius()
|
40
|
+
julius.marriage = Marriage.new :date => "84 BC"
|
41
|
+
julius.marriage.marry Person.new(:gender => "f", :name => 'Cornelia')
|
42
|
+
conny = load julius.spouse.marriage_spouse
|
43
|
+
assert_equal 'married', julius.marriage.state
|
44
|
+
assert_equal 'Cornelia', julius.spouse.name
|
45
|
+
assert_equal 'Gaius Iulius Caesar', conny.spouse.name
|
46
|
+
return julius
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
data/examples/role.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
# class used by the other examples
|
3
|
+
class Role
|
4
|
+
include CouchPotato::Persistence # add the couch potato features
|
5
|
+
include Hanswurst::Shares # allows to share roles
|
6
|
+
include Hanswurst::Delegates # delegate methods to methods of a property that is a role
|
7
|
+
include Hanswurst::Doc # access the doc from inside the role
|
8
|
+
|
9
|
+
# simplify saving
|
10
|
+
def save
|
11
|
+
CouchPotato.database.save_document _doc
|
12
|
+
end
|
13
|
+
|
14
|
+
def load
|
15
|
+
CouchPotato.database.load_document _doc._id
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# include this in the tests
|
20
|
+
module HanswurstExampleTests
|
21
|
+
def load(doc_id)
|
22
|
+
CouchPotato.database.load_document doc_id
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require_relative 'marriage_improvement.rb'
|
2
|
+
|
3
|
+
class Dead
|
4
|
+
|
5
|
+
# all callbacks of roles are called and defined like corresponding callbacks of the doc
|
6
|
+
before_save do
|
7
|
+
if _doc.spouse && marriage=_doc.spouse.marriage
|
8
|
+
doc = CouchPotato.database.load_document(marriage.spouse)
|
9
|
+
m = doc.spouse.marriage
|
10
|
+
m.state = 'widowed'
|
11
|
+
m.end = self.date
|
12
|
+
CouchPotato.database.save_document doc
|
13
|
+
end
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Person
|
19
|
+
def die(date)
|
20
|
+
_doc.dead = Dead.new :date => date
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# include this in the tests
|
25
|
+
module HanswurstExampleTests
|
26
|
+
|
27
|
+
def second_marriage()
|
28
|
+
julius = improved_marriage()
|
29
|
+
julius.marriage << Marriage.new(:date => "68-65 BC")
|
30
|
+
julius.marriage.last.marry Person.new(:gender => "f", :name => 'Pompeia')
|
31
|
+
|
32
|
+
assert_equal 2, julius.marriage.size
|
33
|
+
assert_equal 'Cornelia', load(julius.marriage.first.spouse).person.name
|
34
|
+
assert_equal 'Pompeia', load(julius.marriage.last.spouse).person.name
|
35
|
+
assert_equal 'Pompeia', julius.spouse.name
|
36
|
+
assert_equal 'married', julius.marriage.first.state
|
37
|
+
assert_equal 'married', julius.marriage.last.state
|
38
|
+
|
39
|
+
return julius
|
40
|
+
end
|
41
|
+
|
42
|
+
def second_marriage_after_conny_died()
|
43
|
+
julius = bear_julius()
|
44
|
+
conny = Person.new(:gender => "f", :name => 'Cornelia')
|
45
|
+
julius.marriage = Marriage.new :date => "84 BC" # we need to attach marriage first (before calling marry), so that it has a _doc
|
46
|
+
julius.marriage.marry conny
|
47
|
+
julius.person.save
|
48
|
+
|
49
|
+
conny = load(julius.spouse.marriage_spouse)
|
50
|
+
conny.person.save
|
51
|
+
conny = conny.person.load
|
52
|
+
conny.person.die "68-65 BC"
|
53
|
+
conny.person.save # terminates the marriage
|
54
|
+
|
55
|
+
julius = julius.person.load
|
56
|
+
|
57
|
+
julius.marriage << Marriage.new(:date => "68-65 BC") # we need to attach marriage first (before calling marry), so that it has a _doc
|
58
|
+
julius.marriage.last.marry Person.new(:gender => "f", :name => 'Pompeia')
|
59
|
+
|
60
|
+
assert_equal 2, julius.marriage.size
|
61
|
+
assert_equal 'Cornelia', load(julius.marriage.first.spouse).person.name
|
62
|
+
assert_equal 'widowed', julius.marriage.first.state
|
63
|
+
assert_equal 'Pompeia', load(julius.marriage.last.spouse).person.name
|
64
|
+
assert_equal 'married', julius.marriage.last.state
|
65
|
+
assert_equal 'Pompeia', julius.spouse.name
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
data/lib/hanswurst.rb
CHANGED
@@ -5,10 +5,25 @@ require_relative File.join('hanswurst', 'class_methods')
|
|
5
5
|
require_relative File.join('hanswurst', 'method_missing')
|
6
6
|
require_relative File.join('hanswurst', 'instance_methods')
|
7
7
|
require_relative File.join('hanswurst', 'delegates')
|
8
|
+
require_relative File.join('hanswurst', 'doc')
|
8
9
|
require_relative File.join('hanswurst', 'shares')
|
9
10
|
require_relative File.join('hanswurst', 'as')
|
10
11
|
require_relative File.join('hanswurst', 'callbacks')
|
11
12
|
|
13
|
+
=begin
|
14
|
+
TODO
|
15
|
+
|
16
|
+
- throw errors where it makes sense
|
17
|
+
- make a module to allow the execution of callbacks of roles of roles
|
18
|
+
- improve the inheritence situation of Hanswurst
|
19
|
+
- meta module to collect metainformation about roles (by overwriting of methods of method_missing
|
20
|
+
- add possibility to dynamically create roles from a hash and to save this role-designs in the design document of hanswurst and recreate them wenn fetching data from database
|
21
|
+
- improve documentation
|
22
|
+
- performance tests
|
23
|
+
- examples for views
|
24
|
+
|
25
|
+
=end
|
26
|
+
|
12
27
|
# Each Hanswurst may have a different combination of roles. It's a very flexible way to be a couch potato.
|
13
28
|
class Hanswurst
|
14
29
|
include CouchPotato::Persistence
|
data/lib/hanswurst/as.rb
CHANGED
@@ -3,17 +3,29 @@ class Hanswurst
|
|
3
3
|
|
4
4
|
# helper for easy access to role attributes
|
5
5
|
class As
|
6
|
-
def initialize(doc, role)
|
6
|
+
def initialize(doc, role, rolename)
|
7
7
|
@doc = doc
|
8
8
|
@role = role
|
9
|
+
@rolename = rolename.to_sym
|
10
|
+
if @role.respond_to? :_doc=
|
11
|
+
@role._doc = @doc
|
12
|
+
end
|
9
13
|
end
|
10
14
|
|
11
15
|
def _role
|
12
16
|
@role
|
13
17
|
end
|
14
18
|
|
15
|
-
def
|
16
|
-
|
19
|
+
def << (new_role)
|
20
|
+
if new_role.class == Hanswurst::As
|
21
|
+
new_role = new_role._role
|
22
|
+
end
|
23
|
+
if @role.is_a? Array
|
24
|
+
@role << new_role
|
25
|
+
else
|
26
|
+
@role = [@role, new_role]
|
27
|
+
@doc.send :"#{@rolename}=", @role
|
28
|
+
end
|
17
29
|
end
|
18
30
|
|
19
31
|
def _doc
|
@@ -22,13 +34,29 @@ class Hanswurst
|
|
22
34
|
|
23
35
|
# we suppose, we have a subrole
|
24
36
|
def method_missing(meth, *args, &code)
|
25
|
-
|
26
|
-
|
27
|
-
|
37
|
+
rolename = meth
|
38
|
+
if meth.to_s =~ /^(.*)=$/
|
39
|
+
rolename = $1
|
40
|
+
case args.first
|
41
|
+
when Hanswurst::As
|
42
|
+
args = [args.first._role]
|
43
|
+
when Array
|
44
|
+
first = args.first.collect do |e|
|
45
|
+
e.class == Hanswurst::As ? e._role : e
|
46
|
+
end
|
47
|
+
args = [first]
|
48
|
+
end
|
49
|
+
end
|
28
50
|
|
29
51
|
obj = @role.send(meth, *args)
|
30
|
-
obj = self.class.new(@doc, @role.send(meth, *args)) if obj.is_a? CouchPotato::Persistence
|
52
|
+
obj = self.class.new(@doc, @role.send(meth, *args), rolename) if obj.is_a? CouchPotato::Persistence
|
31
53
|
obj.instance_eval(&code) if code
|
54
|
+
|
55
|
+
# a hack around http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
|
56
|
+
# since ActiveModel::Dirty does not recognize when property is a hash and hashentries do change
|
57
|
+
if meth.to_s =~ /^(.*)=$/
|
58
|
+
@doc.update_roles!
|
59
|
+
end
|
32
60
|
obj
|
33
61
|
end
|
34
62
|
end
|
@@ -89,6 +89,9 @@ class Hanswurst
|
|
89
89
|
|
90
90
|
# set instance for a role
|
91
91
|
def set(role, val)
|
92
|
+
if val.respond_to? :_doc=
|
93
|
+
val._doc = self
|
94
|
+
end
|
92
95
|
self.hanswurst_data ||= {}
|
93
96
|
self.hanswurst_data.update role.to_s => val
|
94
97
|
update_roles!
|
@@ -121,7 +124,7 @@ class Hanswurst
|
|
121
124
|
create(role.to_s)
|
122
125
|
end
|
123
126
|
if role_obj = hanswurst_data[role.to_s]
|
124
|
-
obj = As.new(self, role_obj)
|
127
|
+
obj = As.new(self, role_obj, role)
|
125
128
|
obj.instance_eval(&code) if code
|
126
129
|
return obj
|
127
130
|
end
|
@@ -6,11 +6,16 @@ class Hanswurst
|
|
6
6
|
case meth
|
7
7
|
when /^([^=]+)\=$/ # obj.role = ... # => we set a role
|
8
8
|
role=$1
|
9
|
+
if value.class == Hanswurst::As
|
10
|
+
return self.send meth, value._role
|
11
|
+
end
|
9
12
|
add_role(role, value.class) unless role_exists?(role)
|
10
13
|
set_role(role, value) if role_exists?(role)
|
11
14
|
return as(meth)
|
12
15
|
else
|
13
16
|
return as(meth) if role_exists?(meth.to_s)
|
17
|
+
return super if meth == :hanswurst_data_will_change!
|
18
|
+
return nil
|
14
19
|
end
|
15
20
|
super
|
16
21
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'marriage_improvement'
|
3
|
+
|
4
|
+
class TestImprovedMarriage < Test::Unit::TestCase
|
5
|
+
|
6
|
+
should "make the improved marriage" do
|
7
|
+
recreate_db()
|
8
|
+
improved_marriage()
|
9
|
+
end
|
10
|
+
|
11
|
+
should "make a second marriage" do
|
12
|
+
second_marriage()
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'second_marriage'
|
3
|
+
|
4
|
+
class TestSecondMarriage < Test::Unit::TestCase
|
5
|
+
|
6
|
+
should "make a second marriage" do
|
7
|
+
recreate_db()
|
8
|
+
second_marriage()
|
9
|
+
end
|
10
|
+
|
11
|
+
should "make a second marriage after conny died" do
|
12
|
+
recreate_db()
|
13
|
+
second_marriage_after_conny_died()
|
14
|
+
end
|
15
|
+
end
|
data/test/helper.rb
CHANGED
@@ -15,5 +15,18 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
15
15
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
16
16
|
require 'hanswurst'
|
17
17
|
|
18
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'examples'))
|
18
19
|
|
19
20
|
|
21
|
+
CouchPotato::Config.database_name = ENV['COUCH_POTATO_TEST_DB']
|
22
|
+
|
23
|
+
module HanswurstExampleTests
|
24
|
+
def recreate_db
|
25
|
+
CouchPotato.couchrest_database.recreate!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Test::Unit::TestCase
|
30
|
+
include HanswurstExampleTests
|
31
|
+
|
32
|
+
end
|
data/test/test_hanswurst.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
require 'helper'
|
2
|
-
require 'hanswurst'
|
3
|
-
|
4
|
-
CouchPotato::Config.database_name = ENV['COUCH_POTATO_TEST_DB']
|
5
2
|
|
6
3
|
|
7
4
|
class Pers
|
8
5
|
|
9
6
|
include CouchPotato::Persistence
|
7
|
+
include Hanswurst::Doc
|
10
8
|
property :firstname
|
11
9
|
property :lastname
|
12
10
|
|
@@ -17,6 +15,7 @@ class Pers
|
|
17
15
|
attr_accessor :callbacks
|
18
16
|
|
19
17
|
before_save do
|
18
|
+
p [:before_save, _doc._id] if _doc
|
20
19
|
@callbacks ||= []
|
21
20
|
@callbacks << :before_save
|
22
21
|
end
|
@@ -261,6 +260,23 @@ class TestCouch < Test::Unit::TestCase
|
|
261
260
|
assert_equal 'Donald', o.person.firstname
|
262
261
|
end
|
263
262
|
|
263
|
+
should "add roles" do
|
264
|
+
o = Hanswurst.new
|
265
|
+
o.product = Prod.new( :article_number => 'abc', :name => 'prod1' )
|
266
|
+
save o
|
267
|
+
o = load(o._id)
|
268
|
+
|
269
|
+
o.product << Prod.new( :article_number => 'def', :name => 'prod2' )
|
270
|
+
|
271
|
+
save o
|
272
|
+
#p [o.product]
|
273
|
+
o = load(o._id)
|
274
|
+
|
275
|
+
assert_equal 'prod1', o.product.first.name
|
276
|
+
assert_equal 'prod2', o.product.last.name
|
277
|
+
assert_equal 2, o.product.size
|
278
|
+
end
|
279
|
+
|
264
280
|
should "update role properties" do
|
265
281
|
o = Hanswurst.new
|
266
282
|
o.product = Prod.new( :article_number => 'xyz', :name => 'prod1' )
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hanswurst
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-02-05 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: couch_potato
|
16
|
-
requirement: &
|
16
|
+
requirement: &16666360 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *16666360
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: yard
|
27
|
-
requirement: &
|
27
|
+
requirement: &16665560 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 0.6.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *16665560
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: bundler
|
38
|
-
requirement: &
|
38
|
+
requirement: &16664780 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 1.0.0
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *16664780
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: jeweler
|
49
|
-
requirement: &
|
49
|
+
requirement: &16664060 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 1.5.2
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *16664060
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rcov
|
60
|
-
requirement: &
|
60
|
+
requirement: &16663240 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *16663240
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: mocha
|
71
|
-
requirement: &
|
71
|
+
requirement: &16662440 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *16662440
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: shoulda
|
82
|
-
requirement: &
|
82
|
+
requirement: &16659800 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,10 +87,10 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *16659800
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
92
|
name: linecache19
|
93
|
-
requirement: &
|
93
|
+
requirement: &16658920 !ruby/object:Gem::Requirement
|
94
94
|
none: false
|
95
95
|
requirements:
|
96
96
|
- - ! '>='
|
@@ -98,10 +98,10 @@ dependencies:
|
|
98
98
|
version: '0'
|
99
99
|
type: :development
|
100
100
|
prerelease: false
|
101
|
-
version_requirements: *
|
101
|
+
version_requirements: *16658920
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
103
|
name: ruby-debug19
|
104
|
-
requirement: &
|
104
|
+
requirement: &16658180 !ruby/object:Gem::Requirement
|
105
105
|
none: false
|
106
106
|
requirements:
|
107
107
|
- - ! '>='
|
@@ -109,10 +109,10 @@ dependencies:
|
|
109
109
|
version: '0'
|
110
110
|
type: :development
|
111
111
|
prerelease: false
|
112
|
-
version_requirements: *
|
112
|
+
version_requirements: *16658180
|
113
113
|
- !ruby/object:Gem::Dependency
|
114
114
|
name: couch_potato
|
115
|
-
requirement: &
|
115
|
+
requirement: &16657360 !ruby/object:Gem::Requirement
|
116
116
|
none: false
|
117
117
|
requirements:
|
118
118
|
- - ! '>='
|
@@ -120,7 +120,7 @@ dependencies:
|
|
120
120
|
version: '0'
|
121
121
|
type: :runtime
|
122
122
|
prerelease: false
|
123
|
-
version_requirements: *
|
123
|
+
version_requirements: *16657360
|
124
124
|
description: flexible enhancement of couch potato
|
125
125
|
email: ! 'Base64.decode64(''bGludXhAbWFyY3JlbmVhcm5zLmRl
|
126
126
|
|
@@ -137,15 +137,27 @@ files:
|
|
137
137
|
- README.md
|
138
138
|
- Rakefile
|
139
139
|
- VERSION
|
140
|
+
- examples/bear_julius.rb
|
141
|
+
- examples/first_marriage.rb
|
142
|
+
- examples/immortal_julius.rb
|
143
|
+
- examples/marriage_improvement.rb
|
144
|
+
- examples/role.rb
|
145
|
+
- examples/second_marriage.rb
|
140
146
|
- lib/hanswurst.rb
|
141
147
|
- lib/hanswurst/as.rb
|
142
148
|
- lib/hanswurst/callbacks.rb
|
143
149
|
- lib/hanswurst/class_methods.rb
|
144
150
|
- lib/hanswurst/delegates.rb
|
151
|
+
- lib/hanswurst/doc.rb
|
145
152
|
- lib/hanswurst/instance_methods.rb
|
146
153
|
- lib/hanswurst/method_missing.rb
|
147
154
|
- lib/hanswurst/shares.rb
|
148
155
|
- lib/hanswurst/validator.rb
|
156
|
+
- test/examples/test_bear_julius.rb
|
157
|
+
- test/examples/test_first_marriage.rb
|
158
|
+
- test/examples/test_immortal_julius.rb
|
159
|
+
- test/examples/test_marriage_improvement.rb
|
160
|
+
- test/examples/test_second_marriage.rb
|
149
161
|
- test/helper.rb
|
150
162
|
- test/test_hanswurst.rb
|
151
163
|
homepage: http://github.com/metakeule/hanswurst
|
@@ -163,7 +175,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
163
175
|
version: '0'
|
164
176
|
segments:
|
165
177
|
- 0
|
166
|
-
hash:
|
178
|
+
hash: 1041771107013312987
|
167
179
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
180
|
none: false
|
169
181
|
requirements:
|
@@ -172,10 +184,21 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
172
184
|
version: '0'
|
173
185
|
requirements: []
|
174
186
|
rubyforge_project:
|
175
|
-
rubygems_version: 1.8.
|
187
|
+
rubygems_version: 1.8.15
|
176
188
|
signing_key:
|
177
189
|
specification_version: 3
|
178
190
|
summary: Be a couch potato and play different roles
|
179
191
|
test_files:
|
192
|
+
- examples/bear_julius.rb
|
193
|
+
- examples/first_marriage.rb
|
194
|
+
- examples/immortal_julius.rb
|
195
|
+
- examples/marriage_improvement.rb
|
196
|
+
- examples/role.rb
|
197
|
+
- examples/second_marriage.rb
|
198
|
+
- test/examples/test_bear_julius.rb
|
199
|
+
- test/examples/test_first_marriage.rb
|
200
|
+
- test/examples/test_immortal_julius.rb
|
201
|
+
- test/examples/test_marriage_improvement.rb
|
202
|
+
- test/examples/test_second_marriage.rb
|
180
203
|
- test/helper.rb
|
181
204
|
- test/test_hanswurst.rb
|