dao 3.3.0 → 4.2.1

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.
Files changed (60) hide show
  1. data/README +7 -0
  2. data/Rakefile +36 -17
  3. data/b.rb +38 -0
  4. data/dao.gemspec +41 -13
  5. data/lib/dao.rb +44 -13
  6. data/lib/dao/api.rb +1 -1
  7. data/lib/dao/api/context.rb +35 -45
  8. data/lib/dao/api/endpoints.rb +225 -91
  9. data/lib/dao/conducer.rb +437 -0
  10. data/lib/dao/conducer/attributes.rb +21 -0
  11. data/lib/dao/conducer/crud.rb +70 -0
  12. data/lib/dao/current.rb +66 -0
  13. data/lib/dao/db.rb +44 -5
  14. data/lib/dao/endpoint.rb +13 -1
  15. data/lib/dao/errors.rb +74 -59
  16. data/lib/dao/exceptions.rb +1 -2
  17. data/lib/dao/extractor.rb +68 -0
  18. data/lib/dao/form.rb +139 -46
  19. data/lib/dao/image_cache.rb +193 -0
  20. data/lib/dao/instance_exec.rb +1 -1
  21. data/lib/dao/name.rb +7 -0
  22. data/lib/dao/params.rb +16 -66
  23. data/lib/dao/rack.rb +3 -0
  24. data/lib/dao/rack/middleware.rb +5 -0
  25. data/lib/dao/rack/middleware/params_parser.rb +24 -0
  26. data/lib/dao/rails.rb +22 -5
  27. data/lib/dao/rails/lib/generators/dao/USAGE +2 -6
  28. data/lib/dao/rails/lib/generators/dao/dao_generator.rb +52 -7
  29. data/lib/dao/rails/lib/generators/dao/templates/api.rb +23 -7
  30. data/lib/dao/rails/lib/generators/dao/templates/api_controller.rb +24 -7
  31. data/lib/dao/rails/lib/generators/dao/templates/conducer.rb +64 -0
  32. data/lib/dao/rails/lib/generators/dao/templates/conducer_controller.rb +79 -0
  33. data/lib/dao/rails/lib/generators/dao/templates/dao.js +13 -6
  34. data/lib/dao/rails/lib/generators/dao/templates/dao_helper.rb +75 -11
  35. data/lib/dao/result.rb +1 -26
  36. data/lib/dao/slug.rb +37 -8
  37. data/lib/dao/status.rb +4 -0
  38. data/lib/dao/support.rb +155 -0
  39. data/lib/dao/validations.rb +48 -157
  40. data/lib/dao/validations/callback.rb +30 -0
  41. data/lib/dao/validations/common.rb +322 -320
  42. data/lib/dao/validations/validator.rb +219 -0
  43. data/test/active_model_conducer_lint_test.rb +19 -0
  44. data/test/api_test.rb +261 -0
  45. data/test/conducer_test.rb +205 -0
  46. data/test/db.yml +9 -0
  47. data/test/form_test.rb +42 -0
  48. data/test/support_test.rb +52 -0
  49. data/test/testing.rb +145 -24
  50. data/test/validations_test.rb +156 -0
  51. metadata +138 -21
  52. data/TODO +0 -33
  53. data/a.rb +0 -80
  54. data/db/dao.yml +0 -5
  55. data/lib/dao/api/interfaces.rb +0 -306
  56. data/lib/dao/interface.rb +0 -28
  57. data/lib/dao/presenter.rb +0 -129
  58. data/lib/dao/rails/lib/generators/dao/api_generator.rb +0 -3
  59. data/lib/dao/validations/base.rb +0 -68
  60. data/test/dao_test.rb +0 -506
@@ -1,3 +0,0 @@
1
- class ApiGenerator < Rails::Generators::NamedBase
2
- source_root File.expand_path('../templates', __FILE__)
3
- end
@@ -1,68 +0,0 @@
1
- module Dao
2
- # objects must have and errors object and a status object to use this mixin
3
- #
4
- module Validations::Base
5
- def validations
6
- @validations ||= Validations.for(self)
7
- end
8
-
9
- def is_valid=(boolean)
10
- @is_valid = !!boolean
11
- end
12
-
13
- def is_valid(*bool)
14
- @is_valid ||= nil
15
- @is_valid = !!bool.first unless bool.empty?
16
- @is_valid
17
- end
18
-
19
- def valid!
20
- @forcing_validity = true
21
- end
22
-
23
- def forcing_validity?
24
- defined?(@forcing_validity) and @forcing_validity
25
- end
26
-
27
- def valid?(*args)
28
- if forcing_validity?
29
- true
30
- else
31
- options = Dao.options_for!(args)
32
- validate unless validations.ran?
33
- validate if options[:validate]
34
- errors.empty? and status.ok?
35
- end
36
- end
37
-
38
- def validate(*args, &block)
39
- if !args.empty?
40
- validations.add(*args, &block)
41
- else
42
- validations.run
43
- status.update(420) if(status.ok? and !errors.empty?)
44
- errors.empty? and status.ok?
45
- end
46
- end
47
-
48
- # TODO - consider how to factor out this throw...
49
- #
50
- def validate!(*args, &block)
51
- if !args.empty?
52
- validations.add(*args, &block)
53
- end
54
- @forcing_validity = false
55
- validations.run!
56
- status.update(420) if(status.ok? and !errors.empty?)
57
- throw(:result, nil) unless(errors.empty? and status.ok?)
58
- end
59
-
60
- def validates(*args, &block)
61
- validations.add(*args, &block)
62
- end
63
-
64
- def validations
65
- @validations ||= Validations.for(self)
66
- end
67
- end
68
- end
data/test/dao_test.rb DELETED
@@ -1,506 +0,0 @@
1
- testdir = File.dirname(File.expand_path(__FILE__))
2
- rootdir = File.dirname(testdir)
3
- libdir = File.join(rootdir, 'lib')
4
-
5
- require File.join(libdir, 'dao')
6
- require File.join(testdir, 'testing')
7
- require File.join(testdir, 'helper')
8
-
9
-
10
- Testing Dao do
11
- ## api
12
- #
13
- testing 'that an api class for your application can be built using a simple dsl' do
14
- assert{
15
- api_class =
16
- Dao.api do
17
- ### dsl
18
- end
19
- }
20
- end
21
-
22
- testing 'that apis can have callable interfaces added to them which accept params and return results' do
23
- captured = []
24
-
25
- api_class =
26
- assert{
27
- Dao.api do
28
- interface(:foo) do |params, result|
29
- captured.push(params, result)
30
- end
31
- end
32
- }
33
- api = assert{ api_class.new }
34
- result = assert{ api.call(:foo, {}) }
35
- assert{ result.is_a?(Hash) }
36
- end
37
-
38
- testing 'that interfaces are automatically called according to arity' do
39
- api = assert{ Class.new(Dao.api) }
40
- assert{ api.class_eval{ interface(:zero){|| result.update :args => [] } } }
41
- assert{ api.class_eval{ interface(:one){|a| result.update :args => [a]} } }
42
- assert{ api.class_eval{ interface(:two){|a,b| result.update :args => [a,b]} } }
43
-
44
- assert{ api.new.call(:zero).args.size == 0 }
45
- assert{ api.new.call(:one).args.size == 1 }
46
- assert{ api.new.call(:two).args.size == 2 }
47
- end
48
-
49
- testing 'that interfaces have an auto-vivifying params/result' do
50
- api = assert{ Class.new(Dao.api) }
51
- assert{ api.class_eval{ interface(:foo){ params; result; } } }
52
- result = assert{ api.new.call(:foo) }
53
- assert{ result.path.to_s =~ /foo/ }
54
- end
55
-
56
- testing 'that an api can be called with different modes' do
57
- api_class =
58
- assert{
59
- Dao.api do
60
- call(:foo) do
61
- data.modes = []
62
-
63
- Dao::Mode.list.each do |mode|
64
- send(mode){ data.modes.push(mode) }
65
- end
66
- end
67
- end
68
- }
69
- api = assert{ api_class.new }
70
-
71
- Dao::Mode.list.each do |mode|
72
- result = api.mode(mode).call(:foo)
73
- assert{ result.data.modes.include?(mode) }
74
- end
75
- end
76
-
77
- testing 'that options/head/get are considered read modes' do
78
- read_mode = assert{ Dao::Mode.read }
79
-
80
- api_class =
81
- assert{
82
- Dao.api do
83
- call(:foo) do
84
- data.update :modes => []
85
- read { data.modes.push(read_mode) }
86
- end
87
- end
88
- }
89
- api = assert{ api_class.new }
90
-
91
- Dao::Mode::Read.each do |mode|
92
- result = assert{ api.mode(mode).call(:foo) }
93
- assert{ result.data.modes == [read_mode] }
94
- end
95
- end
96
-
97
- testing 'that post/put/delete/trace/connect are considered write modes' do
98
- write_mode = assert{ Dao::Mode.write }
99
-
100
- api_class =
101
- assert{
102
- Dao.api do
103
- call(:foo) do
104
- data.update :modes => []
105
- write { data.modes.push(write_mode) }
106
- end
107
- end
108
- }
109
- api = assert{ api_class.new }
110
-
111
- Dao::Mode::Write.each do |mode|
112
- result = assert{ api.mode(mode).call(:foo) }
113
- assert{ result.data.modes == [write_mode] }
114
- end
115
- end
116
-
117
- testing 'that the first, most specific, mode block encountered fires first' do
118
- api_class =
119
- assert{
120
- Dao.api do
121
- call(:foo) do
122
- data.update :modes => []
123
- Dao::Mode::Read.each do |mode|
124
- send(mode){ data.modes.push(mode) }
125
- end
126
- read { data.modes.push(Dao::Mode.read) }
127
- end
128
- end
129
- }
130
- api = assert{ api_class.new }
131
-
132
- read = Dao::Mode.read
133
- result = assert{ api.mode(read).call(:foo) }
134
- assert{ result.data.modes == [read] }
135
-
136
- Dao::Mode::Read.each do |mode|
137
- result = assert{ api.mode(mode).call(:foo) }
138
- assert{ result.data.modes == [mode] }
139
- end
140
- end
141
-
142
- ## results
143
- #
144
- testing 'that results can be created' do
145
- result = assert{ Dao::Result.new }
146
- assert{ result.path }
147
- assert{ result.status }
148
- assert{ result.errors }
149
- assert{ result.params }
150
- assert{ result.data }
151
- end
152
-
153
- testing 'that results can be created with a path' do
154
- result = assert{ Dao::Result.new('/api/foo/bar') }
155
- assert{ result.path == '/api/foo/bar' }
156
- end
157
-
158
- ## paths
159
- #
160
- testing 'that simple paths can be contstructed/compiled' do
161
- path = assert{ Dao::Path.for('./api/../foo/bar') }
162
- assert{ path =~ %r|^/| }
163
- assert{ path !~ %r|[.]| }
164
- assert{ path.params.is_a?(Hash) }
165
- assert{ path.keys.is_a?(Array) }
166
- assert{ path.pattern.is_a?(Regexp) }
167
- end
168
-
169
- ## routes
170
- #
171
- testing 'that an api has a list of routes' do
172
- api_class =
173
- assert{
174
- Dao.api do
175
- end
176
- }
177
- assert{ api_class.routes.is_a?(Array) }
178
- end
179
-
180
- testing 'that routed interfaces call be declared' do
181
- api_class =
182
- assert{
183
- Dao.api do
184
- call('/users/:user_id/comments/:comment_id') do
185
- data.update(params)
186
- end
187
- end
188
- }
189
- api = api_class.new
190
- end
191
-
192
- testing 'that routed methods can be called with embedded params' do
193
- api_class =
194
- assert{
195
- Dao.api do
196
- call('/users/:user_id/comments/:comment_id') do
197
- data.update(params)
198
- end
199
- end
200
- }
201
- api = api_class.new
202
-
203
- {
204
- '/users/4/comments/2' => {},
205
- #'/users/comments' => {:user_id => 4, :comment_id => 2},
206
- '/users/:user_id/comments/:comment_id' => {:user_id => 4, :comment_id => 2},
207
- }.each do |path, params|
208
- result = assert{ api.call(path, params) }
209
- assert{ result.data.user_id.to_s =~ /4/ }
210
- assert{ result.data.comment_id.to_s =~ /2/ }
211
- assert{ result.path == '/users/4/comments/2' }
212
- end
213
- end
214
-
215
-
216
- ## status
217
- #
218
- testing 'Status.for' do
219
- assert{ Dao::Status.for(:unauthorized).code == 401 }
220
- assert{ Dao::Status.for(:UNAUTHORIZED).code == 401 }
221
- assert{ Dao::Status.for('unauthorized').code == 401 }
222
- assert{ Dao::Status.for('UNAUTHORIZED').code == 401 }
223
- assert{ Dao::Status.for('Unauthorized').code == 401 }
224
- assert{ Dao::Status.for(:Unauthorized).code == 401 }
225
- assert{ Dao::Status.for(:No_Content).code == 204 }
226
- assert{ Dao::Status.for(:no_content).code == 204 }
227
- end
228
-
229
- testing 'status equality operator' do
230
- s = Dao::Status.for(401)
231
- assert{ s == :unauthorized }
232
- assert{ s == 401 }
233
- assert{ s != Array.new }
234
- end
235
-
236
- ## parser
237
- #
238
- testing 'parsing a simple hash by key' do
239
- params = {
240
- 'key(a)' => 40,
241
- 'key(b)' => 2
242
- }
243
- parsed = Dao.parse(:key, params)
244
- expected = {'a' => 40, 'b' => 2}
245
- assert{ parsed =~ expected }
246
- end
247
-
248
- testing 'parsing a nested hash by key' do
249
- params = {
250
- 'key(a,x)' => 40,
251
- 'key(a,y)' => 2
252
- }
253
- parsed = Dao.parse(:key, params)
254
- expected = {'a' => {'x' => 40, 'y' => 2}}
255
- assert{ parsed =~ expected }
256
- end
257
-
258
- testing 'parsing a deeply nested hash by key' do
259
- params = {
260
- 'key(a,b,x)' => 40,
261
- 'key(a,b,y)' => 2
262
- }
263
- parsed = Dao.parse(:key, params)
264
- expected = {'a' => {'b' => {'x' => 40, 'y' => 2}}}
265
- assert{ parsed =~ expected }
266
- end
267
-
268
- testing 'that params are auto-parsed if the api detects that they need to be ' do
269
- assert{
270
- api_class =
271
- Dao.api do
272
- call('/foobar'){
273
- data.update(params)
274
- }
275
- end
276
- api = api_class.new
277
-
278
- result = assert{ api.call('/foobar', 'key' => 'val') }
279
- assert{ result.data =~ {'key' => 'val'} }
280
-
281
- result = assert{ api.call('/foobar', '/foobar(key)' => 'val', '/foobar(a,0)' => 42, '/foobar(a,1)' => 42.0) }
282
- assert{ result.data =~ {'key' => 'val', 'a' => [42, 42.0]} }
283
- }
284
- end
285
-
286
- testing 'that parsing folds in top level keys by default' do
287
- params = {
288
- 'key(a)' => 40,
289
- 'key(b)' => 2,
290
- 'a' => 'clobbered',
291
- 'b' => 'clobbered',
292
- 'c' => 42
293
- }
294
- parsed = Dao.parse(:key, params)
295
- expected = {'a' => 40, 'b' => 2, 'c' => 42}
296
- assert{ parsed =~ expected }
297
- end
298
-
299
- testing 'that parsing can have folding turned off' do
300
- params = {
301
- 'key(a)' => 40,
302
- 'key(b)' => 2,
303
- 'a' => 'clobbered',
304
- 'b' => 'clobbered',
305
- 'c' => 42
306
- }
307
- parsed = Dao.parse(:key, params, :fold => false)
308
- expected = {'a' => 40, 'b' => 2}
309
- assert{ parsed =~ expected }
310
- end
311
-
312
- testing 'that parse folding can be white list-ly selective' do
313
- params = {
314
- 'key(a)' => 40,
315
- 'key(b)' => 2,
316
- 'a' => 'clobbered',
317
- 'b' => 'clobbered',
318
- 'c' => 42,
319
- 'd' => 'not included...'
320
- }
321
- parsed = Dao.parse(:key, params, :include => [:c])
322
- expected = {'a' => 40, 'b' => 2, 'c' => 42}
323
- assert{ parsed =~ expected }
324
- end
325
-
326
- testing 'that parse folding can be black list-ly selective' do
327
- params = {
328
- 'key(a)' => 40,
329
- 'key(b)' => 2,
330
- 'a' => 'clobbered',
331
- 'b' => 'clobbered',
332
- 'c' => 42,
333
- 'd' => 'rejected...',
334
- 'e' => 'rejected...'
335
- }
336
- parsed = Dao.parse(:key, params, :except => [:d, :e])
337
- expected = {'a' => 40, 'b' => 2, 'c' => 42}
338
- assert{ parsed =~ expected }
339
- end
340
-
341
- ## errors
342
- #
343
- testing 'that clear does not drop sticky errors' do
344
- errors = Dao::Errors.new
345
- errors.add! 'sticky', 'error'
346
- errors.add 'not-sticky', 'error'
347
- errors.clear
348
- assert{ errors['sticky'].first == 'error' }
349
- assert{ errors['not-sticky'].nil? }
350
- end
351
-
352
- testing 'that clear! ***does*** drop sticky errors' do
353
- errors = Dao::Errors.new
354
- errors.add! 'sticky', 'error'
355
- errors.add 'not-sticky', 'error'
356
- errors.clear!
357
- assert{ errors['sticky'].nil? }
358
- assert{ errors['not-sticky'].nil? }
359
- end
360
-
361
- testing 'that global errors are sticky' do
362
- errors = Dao::Errors.new
363
- global = Dao::Errors::Global
364
- errors.add! 'global-error'
365
- errors.clear
366
- assert{ errors[global].first == 'global-error' }
367
- errors.clear!
368
- assert{ errors[global].nil? }
369
- end
370
-
371
- ## validations
372
- #
373
- testing 'that simple validations work' do
374
- params = Dao::Params.new
375
- assert{ params.validates(:password){|password| password=='haxor'} }
376
- params.set(:password, 'haxor')
377
- assert{ params.valid? }
378
- end
379
-
380
- testing 'that validations have some syntax sugar' do
381
- assert{
382
- api_class =
383
- Dao.api do
384
- interface('/foobar'){
385
- params.validate(:a)
386
- validates(:b)
387
- validate!
388
- }
389
- end
390
- api = api_class.new
391
-
392
- result = assert{ api.call('/foobar', 'a' => true, 'b' => true) }
393
- assert{ result.status.ok? }
394
-
395
- result = assert{ api.call('/foobar') }
396
- assert{ !result.status.ok? }
397
- assert{ result.errors.size==2 }
398
- }
399
- end
400
-
401
- testing 'that validations use instance_exec' do
402
- a, b = nil
403
-
404
- api_class =
405
- Dao.api do
406
- interface('/foobar'){
407
- params.validate(:a){ b = get(:b) }
408
- params.validate(:b){ a = get(:a) }
409
- validate!
410
- }
411
- end
412
- api = api_class.new
413
-
414
- result = assert{ api.call('/foobar', 'a' => 40, 'b' => 2) }
415
- assert{ result.status.ok? }
416
- assert{ a == 40 }
417
- assert{ b == 2 }
418
- end
419
-
420
- testing 'simple validates_confirmation_of' do
421
- api_class =
422
- Dao.api do
423
- interface('/foobar'){
424
- params.validates_as_email(:email)
425
- params.validates_confirmation_of(:email)
426
- validate!
427
- }
428
- end
429
- api = api_class.new
430
-
431
- result = assert{ api.call('/foobar', 'email' => 'ara.t.howard@gmail.com', 'email_confirmation' => 'ara.t.howard@gmail.com') }
432
- assert{ result.status.ok? }
433
- assert{ result.errors.empty? }
434
-
435
- result = assert{ api.call('/foobar', 'email' => 'ara.t.howard@gmail.com', 'email_confirmation' => 'ara@dojo4.com') }
436
- assert{ !result.status.ok? }
437
- assert{ !result.errors.empty? }
438
- end
439
-
440
- ## validating
441
- #
442
- testing 'that validations can be cleared and do not clobber manually added errors' do
443
- params = Dao::Params.new
444
- errors = params.errors
445
-
446
- assert{ params.validates(:email){|email| email.to_s.split(/@/).size == 2} }
447
- assert{ params.validates(:password){|password| password == 'pa$$w0rd'} }
448
-
449
- params.set(:email => 'ara@dojo4.com', :password => 'pa$$w0rd')
450
- assert{ params.valid? }
451
-
452
- params.set(:password => 'haxor')
453
- assert{ !params.valid?(:validate => true) }
454
-
455
- errors.add(:name, 'ara')
456
- assert{ not params.valid? }
457
- end
458
-
459
- ## doc
460
- #
461
- testing 'that apis can be documented via the api' do
462
- api_class =
463
- assert {
464
- Dao.api {
465
- description 'foobar'
466
- doc 'signature' => {'read' => '...', 'write' => '...'}
467
- interface('/barfoo'){}
468
- }
469
- }
470
- api_class_index = assert{ api_class.index.is_a?(Hash) }
471
- api = assert{ api_class.new }
472
- api_index = assert{ api.index.is_a?(Hash) }
473
- assert{ api_class_index==api_index }
474
- end
475
-
476
- =begin
477
-
478
- # cloning
479
- #
480
- testing 'simple cloning' do
481
- data = Dao.data(:foo)
482
- clone = assert{ data.clone }
483
- assert{ data.path == clone.path }
484
- assert{ data.errors == clone.errors }
485
- assert{ data.errors.object_id != clone.errors.object_id }
486
- assert{ data.validations == clone.validations }
487
- assert{ data.validations.object_id != clone.validations.object_id }
488
- assert{ data.form != clone.form }
489
- assert{ data.form.object_id != clone.form.object_id }
490
- assert{ data.status == clone.status }
491
- assert{ data.status.object_id != clone.status.object_id }
492
- assert{ data == clone }
493
- end
494
-
495
- =end
496
-
497
- def hash_equal(a, b)
498
- array = lambda{|h| h.to_a.map{|k,v| [k.to_s, v]}.sort}
499
- array[a] == array[b]
500
- end
501
-
502
- def api(&block)
503
- api_class = assert{ Dao.api(&block) }
504
- api = assert{ api_class.new }
505
- end
506
- end