hanswurst 0.5.4 → 0.5.5

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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.4
1
+ 0.5.5
data/lib/hanswurst.rb CHANGED
@@ -1,390 +1,23 @@
1
1
  require 'couch_potato'
2
2
 
3
- class HanswurstValidator < ActiveModel::EachValidator
4
- def doc_is_valid_role?(doc, roles)
5
- !(roles & doc.hanswurst_roles.keys).empty?
6
- end
7
-
8
- def is_valid_klass?(klasses, value)
9
- valid = false
10
- klasses.each do |klass|
11
- valid = true if value.is_a? klass
12
- end
13
- valid
14
- end
15
-
16
- def doc_is_valid_klass?(doc, klasses)
17
- !(klasses & doc.hanswurst_roles.values).empty?
18
- end
19
-
20
-
21
- def validate_each(record, attribute, values)
22
- #p options
23
- #p record
24
- return if values.nil?
25
- values = values.values if values.class == Hash
26
- values = [values] unless values.class == Array
27
- valid = true
28
- max = options[:max]
29
- if max && values.size > max
30
- record.errors.add attribute, "must not have more than #{max} entries"
31
- return
32
- end
33
- fkey = options[:fkey] || false
34
- if fkey
35
- values = values.collect do |id|
36
- CouchPotato.database.load_document id
37
- end
38
- end
39
-
40
- if klass = options[:class]
41
- klasses = [klass].flatten
42
- if fkey
43
- klasses = klasses.collect{ |k| k.to_s }
44
- values.each do |doc|
45
- valid = false unless doc && doc.hanswurst_roles && doc_is_valid_klass?(doc, klasses)
46
- end
47
- record.errors.add attribute, "must be a doc id of a class #{klasses.join(' or ')}" unless valid
48
- else
49
- values.each do |thing|
50
- valid = false unless is_valid_klass?(klasses, thing)
51
- end
52
- record.errors.add attribute, "must be an instance of #{klasses.join(' or ')}" unless valid
53
- end
54
- elsif role = options[:role]
55
- roles = [role].flatten.collect{ |r| r.to_s }
56
- if fkey
57
- values.each do |doc|
58
- valid = false unless doc && doc.hanswurst_roles && doc_is_valid_role?(doc, roles)
59
- end
60
- record.errors.add attribute, "must be a doc id of a role #{roles.join(' or ')}" unless valid
61
- else
62
- klasses = roles.collect do |role_alias|
63
- klass = Hanswurst.role_class(role_alias.to_sym)
64
- record.errors.add(attribute, "could not find role #{role_alias} with Hanswurst.role_class. Did you register it with Hanswurst.register_role() ?") if klass.nil?
65
- klass
66
- end
67
- values.each do |thing|
68
- valid = false unless is_valid_klass?(klasses, thing)
69
- end
70
- record.errors.add attribute, "must be an instance of #{klasses.join(' or ')}" unless valid
71
- end
72
-
73
- end
74
- #roles = options[:in] || [options[:with]]
75
- end
76
- end
77
-
3
+ require_relative File.join('hanswurst', 'validator')
4
+ require_relative File.join('hanswurst', 'class_methods')
5
+ require_relative File.join('hanswurst', 'method_missing')
6
+ require_relative File.join('hanswurst', 'instance_methods')
7
+ require_relative File.join('hanswurst', 'delegates')
8
+ require_relative File.join('hanswurst', 'shares')
9
+ require_relative File.join('hanswurst', 'as')
10
+ require_relative File.join('hanswurst', 'callbacks')
78
11
 
79
12
  # Each Hanswurst may have a different combination of roles. It's a very flexible way to be a couch potato.
80
13
  class Hanswurst
81
- module Delegates
82
- def self.included(mod)
83
- mod.instance_eval do
84
- def delegates(hsh)
85
- @delegations ||= {}
86
- hsh.each do |prop,role|
87
- if prop.is_a? Array
88
- prop.each do |pr|
89
- @delegations.update pr => role
90
- end
91
- else
92
- @delegations.update prop => role
93
- end
94
- end
95
- end
96
-
97
- def delegations
98
- @delegations || {}
99
- end
100
- end
101
- end
102
-
103
- # shortcuts view_for_[role], list_for_[role]
104
- def method_missing(meth, *args, &code)
105
- meth.to_s =~ /^([^=]+)=?$/
106
- unified_meth = $1
107
- if role = self.class.delegations[unified_meth.to_sym]
108
- self.send(role).send(meth, *args, &code)
109
- else
110
- super
111
- end
112
- end
113
- end
114
-
115
- module Shares
116
- def self.included(mod)
117
- mod.instance_eval do
118
- def shares(hsh)
119
- hsh = {hsh => hsh} if hsh.is_a? Symbol
120
- @shared_dependancies ||= {}
121
- @shared_dependancies.update hsh
122
- end
123
-
124
- def shared_dependancies
125
- @shared_dependancies || {}
126
- end
127
- end
128
- end
129
- end
130
-
131
- # error raised when a role is not valid
132
- class RoleNotValid < Exception
133
- def initialize(role, value)
134
- @role = role
135
- @value = value
136
- end
137
-
138
- def message()
139
- errors = @value.errors.messages.collect do |field, err|
140
- "#{field} #{err.join('/')}"
141
- end.join(" # ")
142
- "#{@role} not valid: #{errors}"
143
- end
144
- end
145
-
146
- # helper for easy access to role attributes
147
- class As
148
- def initialize(doc, role)
149
- @doc = doc
150
- @role = role
151
- end
152
-
153
- def _role
154
- @role
155
- end
156
-
157
- def _id
158
- @doc._id
159
- end
160
-
161
- def _doc
162
- @doc
163
- end
164
-
165
- # we suppose, we have a subrole
166
- def method_missing(meth, *args, &code)
167
- # a hack around http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
168
- # since ActiveModel::Dirty does not recognize when property is a hash and hashentries do change
169
- @doc.update_roles! if meth =~ /=$/
170
-
171
- obj = @role.send(meth, *args)
172
- if obj.is_a? CouchPotato::Persistence
173
- obj = self.class.new(@doc, @role.send(meth, *args))
174
- end
175
- obj.instance_eval(&code) if code
176
- obj
177
- end
178
- end
179
-
180
- # class methods for Hanswurst
181
- module ClassMethods
182
-
183
- # get class obj from a classname
184
- def getClass(classname)
185
- classname.split('::').inject Kernel do |c,name| c = c.const_get name; end
186
- end
187
-
188
- # hsh is role_alias => klass where role_alias should be a symbol
189
- def register_role(hsh)
190
- @roles ||= {}
191
- @roles.update hsh
192
- end
193
-
194
- # returns the role klass for the role_alias (role_alias should be a symbol)
195
- def role_class(role_alias)
196
- @roles ||= {}
197
- @roles[role_alias]
198
- end
199
-
200
- # creates a view to show only documents of the role +role_alias+
201
- def role_view(role_alias, viewname, options)
202
- # I keep this commented out to inform the reader that we don't want this in order to easily reuse general lists
203
- #options[:list] &&= :"#{role_alias}_#{options[:list]}"
204
- options[:conditions] &&= " && ( #{options[:conditions]} )"
205
- options[:conditions] = "(doc.hanswurst_roles.#{role_alias} !== undefined)#{options[:conditions]}"
206
- self.view :"#{role_alias}_#{viewname}", options
207
- end
208
-
209
- alias :view_for :role_view
210
-
211
- # creates a list for documents of the role +role_alias+
212
- def role_list(role_alias, listname, val)
213
- self.list :"#{role_alias}_#{listname}", val.to_s
214
- end
215
-
216
- alias :list_for :role_list
217
-
218
- # shortcuts view_for_[role], list_for_[role]
219
- def method_missing(meth, *args, &code)
220
- case meth
221
- when /^view_for_(.+)$/
222
- send(:view_for, $1, *args)
223
- when /^list_for_(.+)$/
224
- send(:list_for, $1, *args)
225
- else
226
- super
227
- end
228
- end
229
- end
230
-
231
- extend ClassMethods
232
-
233
14
  include CouchPotato::Persistence
234
-
235
- module MethodMissing
236
- # dispatches self.role calls and self.role= setters
237
- def method_missing(meth, *args, &code)
238
- value = args.first
239
- case meth
240
- when /^([^=]+)\=$/ # obj.role = ... # => we set a role
241
- role=$1
242
- add_role(role, value.class) unless role_exists?(role)
243
- set_role(role, value) if role_exists?(role)
244
- return as(meth)
245
- else
246
- return as(meth) if role_exists?(meth.to_s)
247
- end
248
- super
249
- end
250
- end
15
+ include MethodMissing
16
+ include Callbacks
17
+ extend ClassMethods
251
18
 
252
19
  property :hanswurst_roles
253
20
  property :hanswurst_data
254
-
255
- # check if every role is valid before saving
256
- before_save do
257
- self.hanswurst_data.each do |role,val|
258
- unless val.valid?
259
- raise RoleNotValid.new(role, val)
260
- end
261
- end
262
- end
263
-
264
- include MethodMissing
265
-
266
- def initialize(hsh={})
267
- unless hsh.empty?
268
- hsh.each do |role, obj|
269
- self.send(:"#{role}=", obj)
270
- end
271
- end
272
- end
273
-
274
- # copy the doc including the roles
275
- def copy()
276
- attributes = self.to_hash
277
- attributes.delete "ruby_class"
278
- attributes.delete "_id"
279
- attributes.delete "_rev"
280
- attributes.delete :created_at
281
- attributes.delete :updated_at
282
- hanswust_data = attributes.delete :hanswurst_data
283
- obj = self.class.new attributes
284
- hanswust_data.each do |role,val|
285
- obj.send(:"#{role}=", val.dup)
286
- end
287
- obj
288
- end
289
-
290
- def <<(hsh)
291
- add_roles(hsh)
292
- end
293
-
294
- # a hack around http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
295
- # since ActiveModel::Dirty does not recognize when property is a hash and hashentries do change
296
- def update_roles!
297
- self.hanswurst_data_will_change!
298
- end
299
-
300
- private
301
-
302
- # add a role
303
- def add_role(role, klass, &code)
304
- add_roles(role => klass)
305
- as(role, &code)
306
- end
307
-
308
- def handle_shared(role, klass)
309
- if klass.respond_to?(:shared_dependancies)
310
- shared = {}
311
- klass.shared_dependancies.each do |shared_role, shared_class|
312
- shared_class = Hanswurst.role_class(shared_class) if shared_class.is_a? Symbol
313
- shared[shared_role] = shared_class unless role_exists? shared_role
314
- end
315
- add_roles shared
316
- end
317
- end
318
-
319
- # add more hanswurst_roles at once
320
- def add_roles(hsh)
321
- self.hanswurst_data ||= {}
322
- self.hanswurst_roles ||= {}
323
- hsh.each do |role,klass|
324
- raise "class expected: #{klass.inspect}" unless klass.is_a? Class
325
- handle_shared(role, klass)
326
- self.hanswurst_roles.update role.to_s => klass.name
327
- end
328
- update_roles!
329
- self.hanswurst_roles
330
- end
331
-
332
- # does he have a role
333
- def role_exists?(role)
334
- self.hanswurst_roles and self.hanswurst_roles[role.to_s]
335
- end
336
-
337
- # does he have hanswurst_data for the role
338
- def hanswurst_data_exists?(role)
339
- self.hanswurst_data and self.hanswurst_data[role.to_s]
340
- end
341
-
342
- # set a role
343
- def set_role(role, value)
344
- value ? set(role, value) : delete_role(role)
345
- end
346
-
347
- # delete a role
348
- def delete_role(role)
349
- delete role # delete the hanswurst_data
350
- self.hanswurst_roles.delete role.to_s # and the role itself
351
- end
352
-
353
- # set instance for a role
354
- def set(role, val)
355
- self.hanswurst_data ||= {}
356
- self.hanswurst_data.update role.to_s => val
357
- update_roles!
358
- end
359
-
360
- # create a new instance of a role
361
- def create(role)
362
- raise "unknown role #{role.inspect}" unless role_exists? role.to_s
363
- self.hanswurst_data ||= {}
364
- self.hanswurst_data[role.to_s] = self.class.getClass(self.hanswurst_roles[role.to_s]).new
365
- update_roles!
366
- end
367
-
368
- # delete a role
369
- def delete(role)
370
- self.hanswurst_data ||= {}
371
- self.hanswurst_data.delete(role.to_s)
372
- update_roles!
373
- #self.hanswurst_data = {} # we have to do this, otherwise it won't save?!
374
- #self.hanswurst_data = hsh
375
- end
376
-
377
- def as(role, &code)
378
- return nil unless role_exists?(role)
379
- unless hanswurst_data[role.to_s]
380
- create(role.to_s)
381
- end
382
- if role_obj = hanswurst_data[role.to_s]
383
- obj = As.new(self, role_obj)
384
- obj.instance_eval(&code) if code
385
- return obj
386
- end
387
- nil
388
- end
389
21
  end
390
22
 
23
+
@@ -0,0 +1,35 @@
1
+ # Each Hanswurst may have a different combination of roles. It's a very flexible way to be a couch potato.
2
+ class Hanswurst
3
+
4
+ # helper for easy access to role attributes
5
+ class As
6
+ def initialize(doc, role)
7
+ @doc = doc
8
+ @role = role
9
+ end
10
+
11
+ def _role
12
+ @role
13
+ end
14
+
15
+ def _id
16
+ @doc._id
17
+ end
18
+
19
+ def _doc
20
+ @doc
21
+ end
22
+
23
+ # we suppose, we have a subrole
24
+ def method_missing(meth, *args, &code)
25
+ # a hack around http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
26
+ # since ActiveModel::Dirty does not recognize when property is a hash and hashentries do change
27
+ @doc.update_roles! if meth =~ /=$/
28
+
29
+ obj = @role.send(meth, *args)
30
+ obj = self.class.new(@doc, @role.send(meth, *args)) if obj.is_a? CouchPotato::Persistence
31
+ obj.instance_eval(&code) if code
32
+ obj
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,68 @@
1
+ class Hanswurst
2
+ module Callbacks
3
+ def self.included(base)
4
+ base.class_eval do
5
+ # mimic the create_document method from https://github.com/langalex/couch_potato/blob/master/lib/couch_potato/database.rb
6
+ before_create do
7
+ ok = true
8
+ self.hanswurst_data.each do |role,document|
9
+ if document.is_a?(CouchPotato::Persistence)
10
+ document.errors.clear
11
+ document.created_at ||= (Time.zone || Time).now
12
+ document.updated_at = (Time.zone || Time).now
13
+ (ok = false) if false == document.run_callbacks(:validation_on_save) do
14
+ (ok = false) if false == document.run_callbacks(:validation_on_create) do
15
+ raise(CouchPotato::Database::ValidationsFailedError.new(["role: " + role].concat(document.errors.full_messages))) unless database.send(:valid_document?, document)
16
+ end
17
+ end
18
+
19
+ (ok = false) if false == document.run_callbacks(:save) do
20
+ (ok = false) if false == document.run_callbacks(:create) do
21
+ true
22
+ end
23
+ end
24
+ end
25
+ end
26
+ ok
27
+ end
28
+
29
+ # mimic the update_document method from https://github.com/langalex/couch_potato/blob/master/lib/couch_potato/database.rb
30
+ before_update do
31
+ ok = true
32
+ self.hanswurst_data.each do |role,document|
33
+ if document.is_a?(CouchPotato::Persistence)
34
+ document.errors.clear
35
+ document.created_at ||= (Time.zone || Time).now
36
+ document.updated_at = (Time.zone || Time).now
37
+ (ok = false) if false == document.run_callbacks(:validation_on_save) do
38
+ (ok = false) if false == document.run_callbacks(:validation_on_update) do
39
+ raise(CouchPotato::Database::ValidationsFailedError.new(["role: " + role].concat(document.errors.full_messages))) unless database.send(:valid_document?, document)
40
+ end
41
+ end
42
+
43
+ (ok = false) if false == document.run_callbacks(:save) do
44
+ (ok = false) if false == document.run_callbacks(:update) do
45
+ true
46
+ end
47
+ end
48
+ end
49
+ end
50
+ ok
51
+ end
52
+
53
+ # mimic the destroy_document method from https://github.com/langalex/couch_potato/blob/master/lib/couch_potato/database.rb
54
+ before_destroy do
55
+ ok = true
56
+ self.hanswurst_data.each do |role,document|
57
+ if document.is_a?(CouchPotato::Persistence)
58
+ (ok = false) if false == document.run_callbacks(:destroy) do
59
+ true
60
+ end
61
+ end
62
+ end
63
+ ok
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,55 @@
1
+
2
+ # TODO allow subclasses of Hanswurst to have the role registry (or handle it via callback)
3
+
4
+ class Hanswurst
5
+ # class methods for Hanswurst
6
+ module ClassMethods
7
+
8
+ # get class obj from a classname
9
+ def getClass(classname)
10
+ classname.split('::').inject Kernel do |c,name| c = c.const_get name; end
11
+ end
12
+
13
+ # hsh is role_alias => klass where role_alias should be a symbol
14
+ def register_role(hsh)
15
+ @roles ||= {}
16
+ @roles.update hsh
17
+ end
18
+
19
+ # returns the role klass for the role_alias (role_alias should be a symbol)
20
+ def role_class(role_alias)
21
+ @roles ||= {}
22
+ @roles[role_alias]
23
+ end
24
+
25
+ # creates a view to show only documents of the role +role_alias+
26
+ def role_view(role_alias, viewname, options)
27
+ # I keep this commented out to inform the reader that we don't want this in order to easily reuse general lists
28
+ #options[:list] &&= :"#{role_alias}_#{options[:list]}"
29
+ options[:conditions] &&= " && ( #{options[:conditions]} )"
30
+ options[:conditions] = "(doc.hanswurst_roles.#{role_alias} !== undefined)#{options[:conditions]}"
31
+ self.view :"#{role_alias}_#{viewname}", options
32
+ end
33
+
34
+ alias :view_for :role_view
35
+
36
+ # creates a list for documents of the role +role_alias+
37
+ def role_list(role_alias, listname, val)
38
+ self.list :"#{role_alias}_#{listname}", val.to_s
39
+ end
40
+
41
+ alias :list_for :role_list
42
+
43
+ # shortcuts view_for_[role], list_for_[role]
44
+ def method_missing(meth, *args, &code)
45
+ case meth
46
+ when /^view_for_(.+)$/
47
+ send(:view_for, $1, *args)
48
+ when /^list_for_(.+)$/
49
+ send(:list_for, $1, *args)
50
+ else
51
+ super
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,35 @@
1
+ class Hanswurst
2
+ module Delegates
3
+ def self.included(mod)
4
+ mod.instance_eval do
5
+ def delegates(hsh)
6
+ @delegations ||= {}
7
+ hsh.each do |prop,role|
8
+ if prop.is_a? Array
9
+ prop.each do |pr|
10
+ @delegations.update pr => role
11
+ end
12
+ else
13
+ @delegations.update prop => role
14
+ end
15
+ end
16
+ end
17
+
18
+ def delegations
19
+ @delegations || {}
20
+ end
21
+ end
22
+ end
23
+
24
+ # shortcuts view_for_[role], list_for_[role]
25
+ def method_missing(meth, *args, &code)
26
+ meth.to_s =~ /^([^=]+)=?$/
27
+ unified_meth = $1
28
+ if role = self.class.delegations[unified_meth.to_sym]
29
+ self.send(role).send(meth, *args, &code)
30
+ else
31
+ super
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,130 @@
1
+ class Hanswurst
2
+ def initialize(hsh={})
3
+ unless hsh.empty?
4
+ hsh.each do |role, obj|
5
+ self.send(:"#{role}=", obj)
6
+ end
7
+ end
8
+ end
9
+
10
+ # copy the doc including the roles
11
+ def copy()
12
+ attributes = self.to_hash
13
+ attributes.delete "ruby_class"
14
+ attributes.delete "_id"
15
+ attributes.delete "_rev"
16
+ attributes.delete :created_at
17
+ attributes.delete :updated_at
18
+ hanswust_data = attributes.delete :hanswurst_data
19
+ obj = self.class.new attributes
20
+ hanswust_data.each do |role,val|
21
+ obj.send(:"#{role}=", val.dup)
22
+ end
23
+ obj
24
+ end
25
+
26
+ def <<(hsh)
27
+ add_roles(hsh)
28
+ end
29
+
30
+ # a hack around http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
31
+ # since ActiveModel::Dirty does not recognize when property is a hash and hashentries do change
32
+ def update_roles!
33
+ self.hanswurst_data_will_change!
34
+ true
35
+ end
36
+
37
+ private
38
+
39
+ # add a role
40
+ def add_role(role, klass, &code)
41
+ add_roles(role => klass)
42
+ as(role, &code)
43
+ end
44
+
45
+ def handle_shared(role, klass)
46
+ if klass.respond_to?(:shared_dependancies)
47
+ shared = {}
48
+ klass.shared_dependancies.each do |shared_role, shared_class|
49
+ shared_class = Hanswurst.role_class(shared_class) if shared_class.is_a? Symbol
50
+ shared[shared_role] = shared_class unless role_exists? shared_role
51
+ end
52
+ add_roles shared
53
+ end
54
+ end
55
+
56
+ # add more hanswurst_roles at once
57
+ def add_roles(hsh)
58
+ self.hanswurst_data ||= {}
59
+ self.hanswurst_roles ||= {}
60
+ hsh.each do |role,klass|
61
+ raise "class expected: #{klass.inspect}" unless klass.is_a? Class
62
+ handle_shared(role, klass)
63
+ self.hanswurst_roles.update role.to_s => klass.name
64
+ end
65
+ update_roles!
66
+ self.hanswurst_roles
67
+ end
68
+
69
+ # does he have a role
70
+ def role_exists?(role)
71
+ self.hanswurst_roles and self.hanswurst_roles[role.to_s]
72
+ end
73
+
74
+ # does he have hanswurst_data for the role
75
+ def hanswurst_data_exists?(role)
76
+ self.hanswurst_data and self.hanswurst_data[role.to_s]
77
+ end
78
+
79
+ # set a role
80
+ def set_role(role, value)
81
+ value ? set(role, value) : delete_role(role)
82
+ end
83
+
84
+ # delete a role
85
+ def delete_role(role)
86
+ return false unless delete(role) # delete the hanswurst_data
87
+ self.hanswurst_roles.delete role.to_s # and the role itself
88
+ end
89
+
90
+ # set instance for a role
91
+ def set(role, val)
92
+ self.hanswurst_data ||= {}
93
+ self.hanswurst_data.update role.to_s => val
94
+ update_roles!
95
+ end
96
+
97
+ # create a new instance of a role
98
+ def create(role)
99
+ raise "unknown role #{role.inspect}" unless role_exists? role.to_s
100
+ self.hanswurst_data ||= {}
101
+ self.hanswurst_data[role.to_s] = self.class.getClass(self.hanswurst_roles[role.to_s]).new
102
+ update_roles!
103
+ end
104
+
105
+ # delete a role
106
+ def delete(role)
107
+ self.hanswurst_data ||= {}
108
+ doc = self.hanswurst_data[role.to_s]
109
+ if doc && doc.is_a?(CouchPotato::Persistence)
110
+ return false if false == doc.run_callbacks(:destroy) do
111
+ self.hanswurst_data.delete(role.to_s)
112
+ update_roles!
113
+ end
114
+ end
115
+ true
116
+ end
117
+
118
+ def as(role, &code)
119
+ return nil unless role_exists?(role)
120
+ unless hanswurst_data[role.to_s]
121
+ create(role.to_s)
122
+ end
123
+ if role_obj = hanswurst_data[role.to_s]
124
+ obj = As.new(self, role_obj)
125
+ obj.instance_eval(&code) if code
126
+ return obj
127
+ end
128
+ nil
129
+ end
130
+ end
@@ -0,0 +1,18 @@
1
+ class Hanswurst
2
+ module MethodMissing
3
+ # dispatches self.role calls and self.role= setters
4
+ def method_missing(meth, *args, &code)
5
+ value = args.first
6
+ case meth
7
+ when /^([^=]+)\=$/ # obj.role = ... # => we set a role
8
+ role=$1
9
+ add_role(role, value.class) unless role_exists?(role)
10
+ set_role(role, value) if role_exists?(role)
11
+ return as(meth)
12
+ else
13
+ return as(meth) if role_exists?(meth.to_s)
14
+ end
15
+ super
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ class Hanswurst
2
+ module Shares
3
+ def self.included(mod)
4
+ mod.instance_eval do
5
+ def shares(hsh)
6
+ hsh = {hsh => hsh} if hsh.is_a? Symbol
7
+ @shared_dependancies ||= {}
8
+ @shared_dependancies.update hsh
9
+ end
10
+
11
+ def shared_dependancies
12
+ @shared_dependancies || {}
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,74 @@
1
+ class HanswurstValidator < ActiveModel::EachValidator
2
+ def doc_is_valid_role?(doc, roles)
3
+ !(roles & doc.hanswurst_roles.keys).empty?
4
+ end
5
+
6
+ def is_valid_klass?(klasses, value)
7
+ valid = false
8
+ klasses.each do |klass|
9
+ valid = true if value.is_a? klass
10
+ end
11
+ valid
12
+ end
13
+
14
+ def doc_is_valid_klass?(doc, klasses)
15
+ !(klasses & doc.hanswurst_roles.values).empty?
16
+ end
17
+
18
+
19
+ def validate_each(record, attribute, values)
20
+ #p options
21
+ #p record
22
+ return if values.nil?
23
+ values = values.values if values.class == Hash
24
+ values = [values] unless values.class == Array
25
+ valid = true
26
+ max = options[:max]
27
+ if max && values.size > max
28
+ record.errors.add attribute, "must not have more than #{max} entries"
29
+ return
30
+ end
31
+ fkey = options[:fkey] || false
32
+ if fkey
33
+ values = values.collect do |id|
34
+ CouchPotato.database.load_document id
35
+ end
36
+ end
37
+
38
+ if klass = options[:class]
39
+ klasses = [klass].flatten
40
+ if fkey
41
+ klasses = klasses.collect{ |k| k.to_s }
42
+ values.each do |doc|
43
+ valid = false unless doc && doc.hanswurst_roles && doc_is_valid_klass?(doc, klasses)
44
+ end
45
+ record.errors.add attribute, "must be a doc id of a class #{klasses.join(' or ')}" unless valid
46
+ else
47
+ values.each do |thing|
48
+ valid = false unless is_valid_klass?(klasses, thing)
49
+ end
50
+ record.errors.add attribute, "must be an instance of #{klasses.join(' or ')}" unless valid
51
+ end
52
+ elsif role = options[:role]
53
+ roles = [role].flatten.collect{ |r| r.to_s }
54
+ if fkey
55
+ values.each do |doc|
56
+ valid = false unless doc && doc.hanswurst_roles && doc_is_valid_role?(doc, roles)
57
+ end
58
+ record.errors.add attribute, "must be a doc id of a role #{roles.join(' or ')}" unless valid
59
+ else
60
+ klasses = roles.collect do |role_alias|
61
+ klass = Hanswurst.role_class(role_alias.to_sym)
62
+ record.errors.add(attribute, "could not find role #{role_alias} with Hanswurst.role_class. Did you register it with Hanswurst.register_role() ?") if klass.nil?
63
+ klass
64
+ end
65
+ values.each do |thing|
66
+ valid = false unless is_valid_klass?(klasses, thing)
67
+ end
68
+ record.errors.add attribute, "must be an instance of #{klasses.join(' or ')}" unless valid
69
+ end
70
+
71
+ end
72
+ #roles = options[:in] || [options[:with]]
73
+ end
74
+ end
@@ -5,6 +5,7 @@ CouchPotato::Config.database_name = ENV['COUCH_POTATO_TEST_DB']
5
5
 
6
6
 
7
7
  class Pers
8
+
8
9
  include CouchPotato::Persistence
9
10
  property :firstname
10
11
  property :lastname
@@ -12,6 +13,57 @@ class Pers
12
13
  def name(seperator)
13
14
  "#{lastname}#{seperator}#{firstname}"
14
15
  end
16
+
17
+ attr_accessor :callbacks
18
+
19
+ before_save do
20
+ @callbacks ||= []
21
+ @callbacks << :before_save
22
+ end
23
+
24
+ after_save do
25
+ @callbacks ||= []
26
+ @callbacks << :after_save
27
+ end
28
+
29
+ before_create do
30
+ @callbacks ||= []
31
+ @callbacks << :before_create
32
+ end
33
+
34
+ after_create do
35
+ @callbacks ||= []
36
+ @callbacks << :after_create
37
+ end
38
+
39
+ before_update do
40
+ @callbacks ||= []
41
+ @callbacks << :before_update
42
+ end
43
+
44
+ after_update do
45
+ @callbacks ||= []
46
+ @callbacks << :after_update
47
+ end
48
+
49
+ before_destroy do
50
+ @callbacks ||= []
51
+ @callbacks << :before_destroy
52
+ end
53
+
54
+ after_destroy do
55
+ @callbacks ||= []
56
+ @callbacks << :after_destroy
57
+ end
58
+ end
59
+
60
+ class PreventsDeletion
61
+ include CouchPotato::Persistence
62
+ property :data
63
+
64
+ before_destroy do
65
+ false
66
+ end
15
67
  end
16
68
 
17
69
  class Prod
@@ -55,18 +107,11 @@ end
55
107
  class F
56
108
  include CouchPotato::Persistence
57
109
 
58
- #depends_on 'd' => D
59
-
60
110
  property :d
61
-
62
111
  property :f
63
-
64
112
  property :a # should be an instance of A
65
-
66
113
  property :perss # should be an Array of instances of Pers
67
-
68
- property :h # should be a Hash
69
-
114
+ property :h # should be a Hash
70
115
  end
71
116
 
72
117
  class G
@@ -142,6 +187,10 @@ class TestCouch < Test::Unit::TestCase
142
187
  CouchPotato.database.view v
143
188
  end
144
189
 
190
+ def destroy(doc)
191
+ CouchPotato.database.destroy_document doc
192
+ end
193
+
145
194
  def update(id)
146
195
  doc = load id
147
196
  yield doc
@@ -153,6 +202,52 @@ class TestCouch < Test::Unit::TestCase
153
202
  recreate_db()
154
203
  end
155
204
 
205
+ should "invoke callbacks" do
206
+ o = Hanswurst.new
207
+ o.pers = Pers.new( :lastname => 'Duck' )
208
+ save o
209
+ assert_equal [:before_save, :before_create, :after_create, :after_save], o.pers.callbacks
210
+
211
+ o = load o._id
212
+ o.pers.lastname = 'Doc'
213
+ save o
214
+ assert_equal [:before_save, :before_update, :after_update, :after_save], o.pers.callbacks
215
+
216
+ id = o._id
217
+ o = load o._id
218
+ pers = o.pers
219
+ destroy o
220
+ assert_equal [:before_destroy, :after_destroy], pers.callbacks
221
+
222
+ o = load id
223
+ assert_equal nil, o
224
+
225
+ o = Hanswurst.new
226
+ o.pers = Pers.new( :lastname => 'Duck' )
227
+ save o
228
+
229
+ o = load o._id
230
+ pers = o.pers
231
+ o.pers = nil
232
+ assert_equal [:before_destroy, :after_destroy], pers.callbacks
233
+
234
+ o = Hanswurst.new
235
+ o.prevents = PreventsDeletion.new :data => 'something'
236
+ save o
237
+ o = load o._id
238
+ o.prevents = nil
239
+ assert_equal 'something', o.prevents.data
240
+
241
+ o = Hanswurst.new
242
+ o.prevents = PreventsDeletion.new :data => 'something'
243
+ save o
244
+ id = o._id
245
+ o = load o._id
246
+ destroy o
247
+ o = load id
248
+ assert_equal 'something', o.prevents.data
249
+ end
250
+
156
251
  should "save stuff" do
157
252
  o = Hanswurst.new
158
253
  o.product = Prod.new( :article_number => 'xyz', :name => 'prod1' )
@@ -241,11 +336,19 @@ class TestCouch < Test::Unit::TestCase
241
336
  assert_equal 'hiho', o.thing.a
242
337
  end
243
338
 
339
+ should "destroy a doc" do
340
+ o = Hanswurst.new
341
+ o.pers = Pers.new
342
+ o.pers.lastname = "hiho"
343
+ save o
344
+ o = load(o._id)
345
+ destroy o
346
+ end
244
347
 
245
348
  should "not save when validation fails" do
246
349
  o = Hanswurst.new
247
350
  o.product = Prod.new( :article_number => 'xyz')
248
- assert_raises Hanswurst::RoleNotValid do
351
+ assert_raises CouchPotato::Database::ValidationsFailedError do
249
352
  save o
250
353
  end
251
354
  end
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
4
+ version: 0.5.5
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-01-19 00:00:00.000000000Z
12
+ date: 2012-01-20 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: couch_potato
16
- requirement: &19722520 !ruby/object:Gem::Requirement
16
+ requirement: &15523120 !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: *19722520
24
+ version_requirements: *15523120
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: yard
27
- requirement: &19721800 !ruby/object:Gem::Requirement
27
+ requirement: &15522500 !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: *19721800
35
+ version_requirements: *15522500
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: bundler
38
- requirement: &19720980 !ruby/object:Gem::Requirement
38
+ requirement: &15521920 !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: *19720980
46
+ version_requirements: *15521920
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: jeweler
49
- requirement: &19720300 !ruby/object:Gem::Requirement
49
+ requirement: &15521320 !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: *19720300
57
+ version_requirements: *15521320
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rcov
60
- requirement: &19719700 !ruby/object:Gem::Requirement
60
+ requirement: &15520780 !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: *19719700
68
+ version_requirements: *15520780
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: mocha
71
- requirement: &19719020 !ruby/object:Gem::Requirement
71
+ requirement: &15520200 !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: *19719020
79
+ version_requirements: *15520200
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: shoulda
82
- requirement: &19686960 !ruby/object:Gem::Requirement
82
+ requirement: &15495700 !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: *19686960
90
+ version_requirements: *15495700
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: linecache19
93
- requirement: &19686300 !ruby/object:Gem::Requirement
93
+ requirement: &15495060 !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: *19686300
101
+ version_requirements: *15495060
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: ruby-debug19
104
- requirement: &19685720 !ruby/object:Gem::Requirement
104
+ requirement: &15494420 !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: *19685720
112
+ version_requirements: *15494420
113
113
  - !ruby/object:Gem::Dependency
114
114
  name: couch_potato
115
- requirement: &19685160 !ruby/object:Gem::Requirement
115
+ requirement: &15493800 !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: *19685160
123
+ version_requirements: *15493800
124
124
  description: flexible enhancement of couch potato
125
125
  email: ! 'Base64.decode64(''bGludXhAbWFyY3JlbmVhcm5zLmRl
126
126
 
@@ -138,6 +138,14 @@ files:
138
138
  - Rakefile
139
139
  - VERSION
140
140
  - lib/hanswurst.rb
141
+ - lib/hanswurst/as.rb
142
+ - lib/hanswurst/callbacks.rb
143
+ - lib/hanswurst/class_methods.rb
144
+ - lib/hanswurst/delegates.rb
145
+ - lib/hanswurst/instance_methods.rb
146
+ - lib/hanswurst/method_missing.rb
147
+ - lib/hanswurst/shares.rb
148
+ - lib/hanswurst/validator.rb
141
149
  - test/helper.rb
142
150
  - test/test_hanswurst.rb
143
151
  homepage: http://github.com/metakeule/hanswurst
@@ -155,7 +163,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
155
163
  version: '0'
156
164
  segments:
157
165
  - 0
158
- hash: -3336574218003098224
166
+ hash: -1349200765658973839
159
167
  required_rubygems_version: !ruby/object:Gem::Requirement
160
168
  none: false
161
169
  requirements: