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
@@ -0,0 +1,219 @@
1
+ module Dao
2
+ module Validations
3
+ class Validator
4
+ NotBlank = lambda{|value| !value.to_s.strip.empty?} unless defined?(NotBlank)
5
+ Cleared = 'Cleared'.freeze unless defined?(Cleared)
6
+
7
+ class << Validator
8
+ def for(*args, &block)
9
+ new(*args, &block)
10
+ end
11
+ end
12
+
13
+ attr_accessor :object
14
+ attr_accessor :validations
15
+ attr_accessor :errors
16
+ attr_accessor :status
17
+
18
+ def initialize(object)
19
+ @object = object
20
+ @validations = Map.new
21
+ @errors = Errors.new
22
+ @status = Status.new
23
+ end
24
+
25
+ fattr(:attributes) do
26
+ attributes =
27
+ catch(:attributes) do
28
+ if @object.respond_to?(:attributes)
29
+ throw :attributes, @object.attributes
30
+ end
31
+ if @object.instance_variable_defined?('@attributes')
32
+ throw :attributes, @object.instance_variable_get('@attributes')
33
+ end
34
+ if @object.is_a?(Map)
35
+ throw :attributes, @object
36
+ end
37
+ if @object.respond_to?(:to_map)
38
+ throw :attributes, Map.new(@object.to_map)
39
+ end
40
+ if @object.is_a?(Hash)
41
+ throw :attributes, Map.new(@object)
42
+ end
43
+ if @object.respond_to?(:to_hash)
44
+ throw :attributes, Map.new(@object.to_hash)
45
+ end
46
+ raise ArgumentError.new("found no attributes on #{ @object.inspect }(#{ @object.class.name })")
47
+ end
48
+
49
+ case attributes
50
+ when Map
51
+ attributes
52
+ when Hash
53
+ Map.new(attributes)
54
+ else
55
+ raise(ArgumentError.new("#{ attributes.inspect } (#{ attributes.class })"))
56
+ end
57
+ end
58
+
59
+ def add(*args, &block)
60
+ options = Map.options_for!(args)
61
+ block = args.pop if args.last.respond_to?(:call)
62
+ block ||= NotBlank
63
+ callback = Callback.new(options, &block)
64
+ validations.set(args => Callback::Chain.new) unless validations.has?(args)
65
+ validations.get(args).add(callback)
66
+ callback
67
+ end
68
+ alias_method('validates', 'add')
69
+
70
+ def run_validations!(*args)
71
+ run_validations(*args)
72
+ ensure
73
+ validated!
74
+ end
75
+
76
+ def validations_search_path
77
+ @validations_search_path ||= (
78
+ list = [
79
+ object,
80
+ object.class.ancestors.map{|ancestor| ancestor.respond_to?(:validator) ? ancestor : nil}
81
+ ]
82
+ list.flatten!
83
+ list.compact!
84
+ list.reverse!
85
+ list
86
+ )
87
+ end
88
+
89
+ def validations_list
90
+ validations_search_path.map{|object| object.validator.validations}
91
+ end
92
+
93
+ def run_validations(*args)
94
+ object = args.first || @object
95
+
96
+ attributes.extend(InstanceExec) unless attributes.respond_to?(:instance_exec)
97
+
98
+ previous_errors = []
99
+ new_errors = []
100
+
101
+ errors.each_message do |keys, message|
102
+ previous_errors.push([keys, message])
103
+ end
104
+ errors.clear!
105
+ status.ok!
106
+
107
+ list = validations_list
108
+
109
+ list.each do |validations|
110
+ validations.depth_first_each do |keys, chain|
111
+ chain.each do |callback|
112
+ next unless callback and callback.respond_to?(:to_proc)
113
+
114
+ number_of_errors = errors.size
115
+ value = attributes.get(keys)
116
+
117
+ returned =
118
+ catch(:validation) do
119
+ args = [value, attributes].slice(0, callback.arity)
120
+ attributes.instance_exec(*args, &callback)
121
+ end
122
+
123
+ case returned
124
+ when Hash
125
+ map = Map(returned)
126
+ valid = map[:valid]
127
+ message = map[:message]
128
+
129
+ when TrueClass, FalseClass
130
+ valid = returned
131
+ message = nil
132
+
133
+ else
134
+ any_errors_added = errors.size > number_of_errors
135
+ valid = !any_errors_added
136
+ message = nil
137
+ end
138
+
139
+ message ||= callback.options[:message]
140
+ message ||= (value.to_s.strip.empty? ? 'is blank' : 'is invalid')
141
+
142
+ unless valid
143
+ new_errors.push([keys, message])
144
+ else
145
+ new_errors.push([keys, Cleared])
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ previous_errors.each do |keys, message|
152
+ errors.add(keys, message) unless new_errors.assoc(keys)
153
+ end
154
+
155
+ new_errors.each do |keys, value|
156
+ next if value == Cleared
157
+ message = value
158
+ errors.add(keys, message)
159
+ end
160
+
161
+ if status.ok? and !errors.empty?
162
+ status.update(412)
163
+ end
164
+
165
+ errors
166
+ end
167
+
168
+ def validated?
169
+ @validated = false unless defined?(@validated)
170
+ @validated
171
+ end
172
+
173
+ def validated!(boolean = true)
174
+ @validated = !!boolean
175
+ end
176
+
177
+ def validate
178
+ run_validations
179
+ end
180
+
181
+ def validate!
182
+ raise Error.new("#{ object.class.name } is invalid!") unless valid?
183
+ object
184
+ end
185
+
186
+ def valid!
187
+ @forcing_validity = true
188
+ end
189
+
190
+ def forcing_validity?
191
+ defined?(@forcing_validity) and @forcing_validity
192
+ end
193
+
194
+ def forcing_validity!(boolean = true)
195
+ @forcing_validity = !!boolean
196
+ end
197
+
198
+ def valid?(*args)
199
+ if forcing_validity?
200
+ true
201
+ else
202
+ options = Map.options_for!(args)
203
+ validate #if(options[:validate] or !validated?)
204
+ errors.empty? and status.ok?
205
+ end
206
+ end
207
+
208
+ def reset
209
+ errors.clear!
210
+ status.update(:ok)
211
+ forcing_validity!(false)
212
+ validated!(false)
213
+ self
214
+ end
215
+ end
216
+ end
217
+
218
+ Validator = Validations::Validator
219
+ end
@@ -0,0 +1,19 @@
1
+ class LintTest < ActiveModel::TestCase
2
+ include ActiveModel::Lint::Tests
3
+
4
+ class LintConducer < Dao::Conducer; end
5
+
6
+ def setup
7
+ @model = LintConducer.new
8
+ end
9
+ end
10
+
11
+
12
+ BEGIN {
13
+ testdir = File.dirname(File.expand_path(__FILE__))
14
+ rootdir = File.dirname(testdir)
15
+ libdir = File.join(rootdir, 'lib')
16
+
17
+ require File.join(libdir, 'dao')
18
+ require File.join(testdir, 'testing')
19
+ }
data/test/api_test.rb ADDED
@@ -0,0 +1,261 @@
1
+
2
+
3
+ Testing Dao do
4
+ ## api
5
+ #
6
+ testing 'that an api class for your application can be built using a simple dsl' do
7
+ assert{
8
+ api_class =
9
+ Dao.api do
10
+ ### dsl
11
+ end
12
+ }
13
+ end
14
+
15
+ testing 'that apis can have callable endpoints added to them which accept params and return results' do
16
+ captured = []
17
+
18
+ api_class =
19
+ assert{
20
+ Dao.api do
21
+ endpoint(:foo) do |params, result|
22
+ captured.push(params, result)
23
+ end
24
+ end
25
+ }
26
+ api = assert{ api_class.new }
27
+ result = assert{ api.call(:foo, {}) }
28
+ assert{ result.is_a?(Hash) }
29
+ end
30
+
31
+ testing 'that endpoints are automatically called according to arity' do
32
+ api = assert{ Class.new(Dao.api) }
33
+ assert{ api.class_eval{ endpoint(:zero){|| result.update :args => [] } } }
34
+ assert{ api.class_eval{ endpoint(:one){|a| result.update :args => [a]} } }
35
+ assert{ api.class_eval{ endpoint(:two){|a,b| result.update :args => [a,b]} } }
36
+
37
+ assert{ api.new.call(:zero).args.size == 0 }
38
+ assert{ api.new.call(:one).args.size == 1 }
39
+ assert{ api.new.call(:two).args.size == 2 }
40
+ end
41
+
42
+ testing 'that endpoints have an auto-vivifying params/result' do
43
+ api = assert{ Class.new(Dao.api) }
44
+ assert{ api.class_eval{ endpoint(:foo){ params; result; } } }
45
+ result = assert{ api.new.call(:foo) }
46
+ assert{ result.path.to_s =~ /foo/ }
47
+ end
48
+
49
+ testing 'that an api can be called with different modes' do
50
+ api_class =
51
+ assert{
52
+ Dao.api do
53
+ call(:foo) do
54
+ data.modes = []
55
+
56
+ Dao::Mode.list.each do |mode|
57
+ send(mode){ data.modes.push(mode) }
58
+ end
59
+ end
60
+ end
61
+ }
62
+ api = assert{ api_class.new }
63
+
64
+ Dao::Mode.list.each do |mode|
65
+ result = api.mode(mode).call(:foo)
66
+ assert{ result.data.modes.include?(mode) }
67
+ end
68
+ end
69
+
70
+ testing 'that options/head/get are considered read modes' do
71
+ read_mode = assert{ Dao::Mode.read }
72
+
73
+ api_class =
74
+ assert{
75
+ Dao.api do
76
+ call(:foo) do
77
+ data.update :modes => []
78
+ read { data.modes.push(read_mode) }
79
+ end
80
+ end
81
+ }
82
+ api = assert{ api_class.new }
83
+
84
+ Dao::Mode::Read.each do |mode|
85
+ result = assert{ api.mode(mode).call(:foo) }
86
+ assert{ result.data.modes == [read_mode] }
87
+ end
88
+ end
89
+
90
+ testing 'that post/put/delete/trace/connect are considered write modes' do
91
+ write_mode = assert{ Dao::Mode.write }
92
+
93
+ api_class =
94
+ assert{
95
+ Dao.api do
96
+ call(:foo) do
97
+ data.update :modes => []
98
+ write { data.modes.push(write_mode) }
99
+ end
100
+ end
101
+ }
102
+ api = assert{ api_class.new }
103
+
104
+ Dao::Mode::Write.each do |mode|
105
+ result = assert{ api.mode(mode).call(:foo) }
106
+ assert{ result.data.modes == [write_mode] }
107
+ end
108
+ end
109
+
110
+ testing 'that the first, most specific, mode block encountered fires first' do
111
+ api_class =
112
+ assert{
113
+ Dao.api do
114
+ call(:foo) do
115
+ data.update :modes => []
116
+ Dao::Mode::Read.each do |mode|
117
+ send(mode){ data.modes.push(mode) }
118
+ end
119
+ read { data.modes.push(Dao::Mode.read) }
120
+ end
121
+ end
122
+ }
123
+ api = assert{ api_class.new }
124
+
125
+ read = Dao::Mode.read
126
+ result = assert{ api.mode(read).call(:foo) }
127
+ assert{ result.data.modes == [read] }
128
+
129
+ Dao::Mode::Read.each do |mode|
130
+ result = assert{ api.mode(mode).call(:foo) }
131
+ assert{ result.data.modes == [mode] }
132
+ end
133
+ end
134
+
135
+ ## results
136
+ #
137
+ testing 'that results can be created' do
138
+ result = assert{ Dao::Result.new }
139
+ assert{ result.path }
140
+ assert{ result.status }
141
+ assert{ result.errors }
142
+ assert{ result.params }
143
+ assert{ result.data }
144
+ end
145
+
146
+ testing 'that results can be created with a path' do
147
+ result = assert{ Dao::Result.new('/api/foo/bar') }
148
+ assert{ result.path == '/api/foo/bar' }
149
+ end
150
+
151
+ ## paths
152
+ #
153
+ testing 'that simple paths can be contstructed/compiled' do
154
+ path = assert{ Dao::Path.for('./api/../foo/bar') }
155
+ assert{ path =~ %r|^/| }
156
+ assert{ path !~ %r|[.]| }
157
+ assert{ path.params.is_a?(Hash) }
158
+ assert{ path.keys.is_a?(Array) }
159
+ assert{ path.pattern.is_a?(Regexp) }
160
+ end
161
+
162
+ ## routes
163
+ #
164
+ testing 'that an api has a list of routes' do
165
+ api_class =
166
+ assert{
167
+ Dao.api do
168
+ end
169
+ }
170
+ assert{ api_class.routes.is_a?(Array) }
171
+ end
172
+
173
+ testing 'that routed endpoints call be declared' do
174
+ api_class =
175
+ assert{
176
+ Dao.api do
177
+ call('/users/:user_id/comments/:comment_id') do
178
+ data.update(params)
179
+ end
180
+ end
181
+ }
182
+ api = api_class.new
183
+ end
184
+
185
+ testing 'that routed methods can be called with embedded params' do
186
+ api_class =
187
+ assert{
188
+ Dao.api do
189
+ call('/users/:user_id/comments/:comment_id') do
190
+ data.update(params)
191
+ end
192
+ end
193
+ }
194
+ api = api_class.new
195
+
196
+ {
197
+ '/users/4/comments/2' => {},
198
+ '/users/:user_id/comments/:comment_id' => {:user_id => 4, :comment_id => 2},
199
+ }.each do |path, params|
200
+ result = assert{ api.call(path, params) }
201
+ assert{ result.data.user_id.to_s =~ /4/ }
202
+ assert{ result.data.comment_id.to_s =~ /2/ }
203
+ assert{ result.path == '/users/4/comments/2' }
204
+ assert{ result.route == '/users/:user_id/comments/:comment_id' }
205
+ end
206
+ end
207
+
208
+ ## doc
209
+ #
210
+ testing 'that apis can be documented via the api' do
211
+ api_class =
212
+ assert {
213
+ Dao.api {
214
+ description 'foobar'
215
+ doc 'signature' => {'read' => '...', 'write' => '...'}
216
+ endpoint('/barfoo'){}
217
+ }
218
+ }
219
+ api_class_index = assert{ api_class.index.is_a?(Hash) }
220
+ api = assert{ api_class.new }
221
+ api_index = assert{ api.index.is_a?(Hash) }
222
+ assert{ api_class_index==api_index }
223
+ end
224
+
225
+ # aliases
226
+ #
227
+ testing 'that apis can alias methods' do
228
+ api_class =
229
+ assert {
230
+ Dao.api {
231
+ call('/barfoo'){ data.update(:k => :v) }
232
+ call('/foobar', :alias => '/barfoo')
233
+ }
234
+ }
235
+ api = assert{ api_class.new }
236
+ assert{ api.call('/barfoo').data.k == :v }
237
+ assert{ api.call('/foobar').data.k == :v }
238
+ end
239
+
240
+ protected
241
+ def hash_equal(a, b)
242
+ array = lambda{|h| h.to_a.map{|k,v| [k.to_s, v]}.sort}
243
+ array[a] == array[b]
244
+ end
245
+
246
+ def api(&block)
247
+ api_class = assert{ Dao.api(&block) }
248
+ api = assert{ api_class.new }
249
+ end
250
+ end
251
+
252
+
253
+ BEGIN {
254
+ testdir = File.dirname(File.expand_path(__FILE__))
255
+ rootdir = File.dirname(testdir)
256
+ libdir = File.join(rootdir, 'lib')
257
+
258
+ require File.join(libdir, 'dao')
259
+ require File.join(testdir, 'testing')
260
+ require File.join(testdir, 'helper')
261
+ }