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
@@ -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
+ }