dao 3.3.0 → 4.2.1

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