dao 0.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/README +35 -0
  2. data/Rakefile +6 -3
  3. data/TODO +37 -0
  4. data/dao.gemspec +30 -0
  5. data/db/dao.yml +8 -0
  6. data/lib/dao.rb +84 -5
  7. data/lib/dao/active_record.rb +76 -0
  8. data/lib/dao/api.rb +9 -0
  9. data/lib/dao/api/context.rb +38 -0
  10. data/lib/dao/api/dsl.rb +50 -0
  11. data/lib/dao/api/endpoints.rb +190 -0
  12. data/lib/dao/api/initializers.rb +71 -0
  13. data/lib/dao/api/modes.rb +85 -0
  14. data/lib/dao/blankslate.rb +5 -0
  15. data/lib/dao/data.rb +58 -0
  16. data/lib/dao/db.rb +183 -0
  17. data/lib/dao/endpoint.rb +16 -0
  18. data/lib/dao/engine.rb +7 -0
  19. data/lib/dao/errors.rb +238 -0
  20. data/lib/dao/exceptions.rb +2 -0
  21. data/lib/dao/form.rb +236 -0
  22. data/lib/dao/mode.rb +41 -0
  23. data/lib/dao/mongo_mapper.rb +70 -0
  24. data/lib/dao/params.rb +109 -0
  25. data/lib/dao/path.rb +149 -0
  26. data/lib/dao/rails.rb +15 -0
  27. data/lib/dao/rails/app/api.rb +55 -0
  28. data/lib/dao/rails/app/controllers/api_controller.rb +99 -0
  29. data/lib/dao/rails/lib/generators/dao/USAGE +9 -0
  30. data/lib/dao/rails/lib/generators/dao/api_generator.rb +3 -0
  31. data/lib/dao/rails/lib/generators/dao/dao_generator.rb +27 -0
  32. data/lib/dao/rails/lib/generators/dao/templates/api.rb +55 -0
  33. data/lib/dao/rails/lib/generators/dao/templates/api_controller.rb +99 -0
  34. data/lib/dao/result.rb +87 -0
  35. data/lib/dao/slug.rb +11 -0
  36. data/lib/dao/status.rb +223 -0
  37. data/lib/dao/stdext.rb +10 -0
  38. data/lib/dao/support.rb +62 -0
  39. data/lib/dao/validations.rb +115 -0
  40. data/sample/rails_app/Gemfile +33 -0
  41. data/sample/rails_app/Gemfile.lock +88 -0
  42. data/sample/rails_app/README +1 -0
  43. data/sample/rails_app/Rakefile +7 -0
  44. data/sample/rails_app/app/api.rb +55 -0
  45. data/sample/rails_app/app/controllers/api_controller.rb +99 -0
  46. data/sample/rails_app/app/controllers/application_controller.rb +3 -0
  47. data/sample/rails_app/app/helpers/application_helper.rb +2 -0
  48. data/sample/rails_app/app/views/layouts/application.html.erb +14 -0
  49. data/sample/rails_app/config.ru +4 -0
  50. data/sample/rails_app/config/application.rb +51 -0
  51. data/sample/rails_app/config/boot.rb +13 -0
  52. data/sample/rails_app/config/database.yml +22 -0
  53. data/sample/rails_app/config/environment.rb +5 -0
  54. data/sample/rails_app/config/environments/development.rb +26 -0
  55. data/sample/rails_app/config/environments/production.rb +49 -0
  56. data/sample/rails_app/config/environments/test.rb +35 -0
  57. data/sample/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  58. data/sample/rails_app/config/initializers/inflections.rb +10 -0
  59. data/sample/rails_app/config/initializers/mime_types.rb +5 -0
  60. data/sample/rails_app/config/initializers/secret_token.rb +7 -0
  61. data/sample/rails_app/config/initializers/session_store.rb +8 -0
  62. data/sample/rails_app/config/locales/en.yml +5 -0
  63. data/sample/rails_app/config/routes.rb +62 -0
  64. data/sample/rails_app/db/development.sqlite3 +0 -0
  65. data/sample/rails_app/db/seeds.rb +7 -0
  66. data/sample/rails_app/doc/README_FOR_APP +2 -0
  67. data/sample/rails_app/log/development.log +27 -0
  68. data/sample/rails_app/log/production.log +0 -0
  69. data/sample/rails_app/log/server.log +0 -0
  70. data/sample/rails_app/log/test.log +0 -0
  71. data/sample/rails_app/public/404.html +26 -0
  72. data/sample/rails_app/public/422.html +26 -0
  73. data/sample/rails_app/public/500.html +26 -0
  74. data/sample/rails_app/public/favicon.ico +0 -0
  75. data/sample/rails_app/public/images/rails.png +0 -0
  76. data/sample/rails_app/public/index.html +239 -0
  77. data/sample/rails_app/public/javascripts/application.js +2 -0
  78. data/sample/rails_app/public/javascripts/controls.js +965 -0
  79. data/sample/rails_app/public/javascripts/dragdrop.js +974 -0
  80. data/sample/rails_app/public/javascripts/effects.js +1123 -0
  81. data/sample/rails_app/public/javascripts/prototype.js +6001 -0
  82. data/sample/rails_app/public/javascripts/rails.js +175 -0
  83. data/sample/rails_app/public/robots.txt +5 -0
  84. data/sample/rails_app/script/rails +6 -0
  85. data/sample/rails_app/test/performance/browsing_test.rb +9 -0
  86. data/sample/rails_app/test/test_helper.rb +13 -0
  87. data/test/dao_test.rb +271 -0
  88. data/test/helper.rb +15 -0
  89. data/test/testing.rb +74 -0
  90. metadata +137 -9
@@ -0,0 +1,175 @@
1
+ (function() {
2
+ // Technique from Juriy Zaytsev
3
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
4
+ function isEventSupported(eventName) {
5
+ var el = document.createElement('div');
6
+ eventName = 'on' + eventName;
7
+ var isSupported = (eventName in el);
8
+ if (!isSupported) {
9
+ el.setAttribute(eventName, 'return;');
10
+ isSupported = typeof el[eventName] == 'function';
11
+ }
12
+ el = null;
13
+ return isSupported;
14
+ }
15
+
16
+ function isForm(element) {
17
+ return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
18
+ }
19
+
20
+ function isInput(element) {
21
+ if (Object.isElement(element)) {
22
+ var name = element.nodeName.toUpperCase()
23
+ return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
24
+ }
25
+ else return false
26
+ }
27
+
28
+ var submitBubbles = isEventSupported('submit'),
29
+ changeBubbles = isEventSupported('change')
30
+
31
+ if (!submitBubbles || !changeBubbles) {
32
+ // augment the Event.Handler class to observe custom events when needed
33
+ Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
34
+ function(init, element, eventName, selector, callback) {
35
+ init(element, eventName, selector, callback)
36
+ // is the handler being attached to an element that doesn't support this event?
37
+ if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
38
+ (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
39
+ // "submit" => "emulated:submit"
40
+ this.eventName = 'emulated:' + this.eventName
41
+ }
42
+ }
43
+ )
44
+ }
45
+
46
+ if (!submitBubbles) {
47
+ // discover forms on the page by observing focus events which always bubble
48
+ document.on('focusin', 'form', function(focusEvent, form) {
49
+ // special handler for the real "submit" event (one-time operation)
50
+ if (!form.retrieve('emulated:submit')) {
51
+ form.on('submit', function(submitEvent) {
52
+ var emulated = form.fire('emulated:submit', submitEvent, true)
53
+ // if custom event received preventDefault, cancel the real one too
54
+ if (emulated.returnValue === false) submitEvent.preventDefault()
55
+ })
56
+ form.store('emulated:submit', true)
57
+ }
58
+ })
59
+ }
60
+
61
+ if (!changeBubbles) {
62
+ // discover form inputs on the page
63
+ document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
64
+ // special handler for real "change" events
65
+ if (!input.retrieve('emulated:change')) {
66
+ input.on('change', function(changeEvent) {
67
+ input.fire('emulated:change', changeEvent, true)
68
+ })
69
+ input.store('emulated:change', true)
70
+ }
71
+ })
72
+ }
73
+
74
+ function handleRemote(element) {
75
+ var method, url, params;
76
+
77
+ var event = element.fire("ajax:before");
78
+ if (event.stopped) return false;
79
+
80
+ if (element.tagName.toLowerCase() === 'form') {
81
+ method = element.readAttribute('method') || 'post';
82
+ url = element.readAttribute('action');
83
+ params = element.serialize();
84
+ } else {
85
+ method = element.readAttribute('data-method') || 'get';
86
+ url = element.readAttribute('href');
87
+ params = {};
88
+ }
89
+
90
+ new Ajax.Request(url, {
91
+ method: method,
92
+ parameters: params,
93
+ evalScripts: true,
94
+
95
+ onComplete: function(request) { element.fire("ajax:complete", request); },
96
+ onSuccess: function(request) { element.fire("ajax:success", request); },
97
+ onFailure: function(request) { element.fire("ajax:failure", request); }
98
+ });
99
+
100
+ element.fire("ajax:after");
101
+ }
102
+
103
+ function handleMethod(element) {
104
+ var method = element.readAttribute('data-method'),
105
+ url = element.readAttribute('href'),
106
+ csrf_param = $$('meta[name=csrf-param]')[0],
107
+ csrf_token = $$('meta[name=csrf-token]')[0];
108
+
109
+ var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
110
+ element.parentNode.insert(form);
111
+
112
+ if (method !== 'post') {
113
+ var field = new Element('input', { type: 'hidden', name: '_method', value: method });
114
+ form.insert(field);
115
+ }
116
+
117
+ if (csrf_param) {
118
+ var param = csrf_param.readAttribute('content'),
119
+ token = csrf_token.readAttribute('content'),
120
+ field = new Element('input', { type: 'hidden', name: param, value: token });
121
+ form.insert(field);
122
+ }
123
+
124
+ form.submit();
125
+ }
126
+
127
+
128
+ document.on("click", "*[data-confirm]", function(event, element) {
129
+ var message = element.readAttribute('data-confirm');
130
+ if (!confirm(message)) event.stop();
131
+ });
132
+
133
+ document.on("click", "a[data-remote]", function(event, element) {
134
+ if (event.stopped) return;
135
+ handleRemote(element);
136
+ event.stop();
137
+ });
138
+
139
+ document.on("click", "a[data-method]", function(event, element) {
140
+ if (event.stopped) return;
141
+ handleMethod(element);
142
+ event.stop();
143
+ });
144
+
145
+ document.on("submit", function(event) {
146
+ var element = event.findElement(),
147
+ message = element.readAttribute('data-confirm');
148
+ if (message && !confirm(message)) {
149
+ event.stop();
150
+ return false;
151
+ }
152
+
153
+ var inputs = element.select("input[type=submit][data-disable-with]");
154
+ inputs.each(function(input) {
155
+ input.disabled = true;
156
+ input.writeAttribute('data-original-value', input.value);
157
+ input.value = input.readAttribute('data-disable-with');
158
+ });
159
+
160
+ var element = event.findElement("form[data-remote]");
161
+ if (element) {
162
+ handleRemote(element);
163
+ event.stop();
164
+ }
165
+ });
166
+
167
+ document.on("ajax:after", "form", function(event, element) {
168
+ var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
169
+ inputs.each(function(input) {
170
+ input.value = input.readAttribute('data-original-value');
171
+ input.removeAttribute('data-original-value');
172
+ input.disabled = false;
173
+ });
174
+ });
175
+ })();
@@ -0,0 +1,5 @@
1
+ # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2
+ #
3
+ # To ban all spiders from the entire site uncomment the next two lines:
4
+ # User-Agent: *
5
+ # Disallow: /
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+ require 'rails/performance_test_help'
3
+
4
+ # Profiling results for each test method are written to tmp/performance.
5
+ class BrowsingTest < ActionDispatch::PerformanceTest
6
+ def test_homepage
7
+ get '/'
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require File.expand_path('../../config/environment', __FILE__)
3
+ require 'rails/test_help'
4
+
5
+ class ActiveSupport::TestCase
6
+ # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
7
+ #
8
+ # Note: You'll currently still have to declare fixtures explicitly in integration tests
9
+ # -- they do not yet inherit this setting
10
+ fixtures :all
11
+
12
+ # Add more helper methods to be used by all tests here...
13
+ end
data/test/dao_test.rb ADDED
@@ -0,0 +1,271 @@
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 endpoints added to them which accept params and return results' do
23
+ captured = []
24
+
25
+ api_class =
26
+ assert{
27
+ Dao.api do
28
+ endpoint(: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 endpoints are automatically called according to arity' do
39
+ api = assert{ Class.new(Dao.api) }
40
+ assert{ api.class_eval{ endpoint(:zero){|| result.update :args => [] } } }
41
+ assert{ api.class_eval{ endpoint(:one){|a| result.update :args => [a]} } }
42
+ assert{ api.class_eval{ endpoint(: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 endpoints have an auto-vivifying params/result' do
50
+ api = assert{ Class.new(Dao.api) }
51
+ assert{ api.class_eval{ endpoint(:foo){ params; result; } } }
52
+ result = assert{ api.new.call(:foo) }
53
+ assert{ result.path.to_s =~ /foo/ }
54
+ end
55
+
56
+ # results
57
+ #
58
+ testing 'that results can be created' do
59
+ result = assert{ Dao::Result.new }
60
+ assert{ result.path }
61
+ assert{ result.status }
62
+ assert{ result.data }
63
+ assert{ result.errors }
64
+ assert{ result.validations }
65
+ end
66
+
67
+ testing 'that results can be created with a path' do
68
+ result = assert{ Dao::Result.new('/api/foo/bar') }
69
+ assert{ result.path == '/api/foo/bar' }
70
+ end
71
+
72
+ ## paths
73
+ #
74
+ testing 'that simple paths can be contstructed/compiled' do
75
+ path = assert{ Dao::Path.for('./api/../foo/bar') }
76
+ assert{ path =~ %r|^/| }
77
+ assert{ path !~ %r|[.]| }
78
+ assert{ path.params.is_a?(Hash) }
79
+ assert{ path.keys.is_a?(Array) }
80
+ assert{ path.pattern.is_a?(Regexp) }
81
+ end
82
+
83
+ # status
84
+ #
85
+ testing 'Status.for' do
86
+ assert{ Dao::Status.for(:unauthorized).code == 401 }
87
+ assert{ Dao::Status.for(:UNAUTHORIZED).code == 401 }
88
+ assert{ Dao::Status.for('unauthorized').code == 401 }
89
+ assert{ Dao::Status.for('UNAUTHORIZED').code == 401 }
90
+ assert{ Dao::Status.for('Unauthorized').code == 401 }
91
+ assert{ Dao::Status.for(:Unauthorized).code == 401 }
92
+ assert{ Dao::Status.for(:No_Content).code == 204 }
93
+ assert{ Dao::Status.for(:no_content).code == 204 }
94
+ end
95
+
96
+ testing 'status equality operator' do
97
+ s = Dao::Status.for(401)
98
+ assert{ s == :unauthorized }
99
+ assert{ s == 401 }
100
+ assert{ s != Array.new }
101
+ end
102
+
103
+ # parser
104
+ #
105
+ testing 'parsing a simple hash by key' do
106
+ params = {
107
+ 'key(a)' => 40,
108
+ 'key(b)' => 2
109
+ }
110
+ parsed = Dao.parse(:key, params)
111
+ expected = {'a' => 40, 'b' => 2}
112
+ assert{ parsed =~ expected }
113
+ end
114
+
115
+ testing 'parsing a nested hash by key' do
116
+ params = {
117
+ 'key(a,x)' => 40,
118
+ 'key(a,y)' => 2
119
+ }
120
+ parsed = Dao.parse(:key, params)
121
+ expected = {'a' => {'x' => 40, 'y' => 2}}
122
+ assert{ parsed =~ expected }
123
+ end
124
+
125
+ testing 'parsing a deeply nested hash by key' do
126
+ params = {
127
+ 'key(a,b,x)' => 40,
128
+ 'key(a,b,y)' => 2
129
+ }
130
+ parsed = Dao.parse(:key, params)
131
+ expected = {'a' => {'b' => {'x' => 40, 'y' => 2}}}
132
+ assert{ parsed =~ expected }
133
+ end
134
+
135
+ testing 'that params are auto-parsed if the api detects that they need to be ' do
136
+ assert{
137
+ api_class =
138
+ Dao.api do
139
+ endpoint('/foobar'){
140
+ data.update(params)
141
+ }
142
+ end
143
+ api = api_class.new
144
+
145
+ result = assert{ api.call('/foobar', 'key' => 'val') }
146
+ assert{ result.data =~ {'key' => 'val'} }
147
+
148
+ result = assert{ api.call('/foobar', '/foobar(key)' => 'val', '/foobar(a,0)' => 42, '/foobar(a,1)' => 42.0) }
149
+ assert{ result.data =~ {'key' => 'val', 'a' => [42, 42.0]} }
150
+ }
151
+ end
152
+
153
+ # errors.rb
154
+ #
155
+ testing 'that clear does not drop sticky errors' do
156
+ errors = Dao::Errors.new
157
+ errors.add! 'sticky', 'error'
158
+ errors.add 'not-sticky', 'error'
159
+ errors.clear
160
+ assert{ errors['sticky'].first == 'error' }
161
+ assert{ errors['not-sticky'].nil? }
162
+ end
163
+
164
+ testing 'that clear! ***does*** drop sticky errors' do
165
+ errors = Dao::Errors.new
166
+ errors.add! 'sticky', 'error'
167
+ errors.add 'not-sticky', 'error'
168
+ errors.clear!
169
+ assert{ errors['sticky'].nil? }
170
+ assert{ errors['not-sticky'].nil? }
171
+ end
172
+
173
+ testing 'that global errors are sticky' do
174
+ errors = Dao::Errors.new
175
+ global = Dao::Errors::Global
176
+ errors.add! 'global-error'
177
+ errors.clear
178
+ assert{ errors[global].first == 'global-error' }
179
+ errors.clear!
180
+ assert{ errors[global].nil? }
181
+ end
182
+
183
+ # validations
184
+ #
185
+ testing 'that simple validations work' do
186
+ result = Dao::Result.new
187
+ assert{ result.validates(:password){|password| password=='haxor'} }
188
+ result.params.set(:password, 'haxor')
189
+ assert{ result.valid? }
190
+ end
191
+
192
+ testing 'that validations have some syntax sugar' do
193
+ assert{
194
+ api_class =
195
+ Dao.api do
196
+ endpoint('/foobar'){
197
+ result.validates(:a)
198
+ params.validate(:b)
199
+ validates(:c)
200
+ }
201
+ end
202
+ api = api_class.new
203
+
204
+ result = assert{ api.call('/foobar', 'a' => true, 'b' => true, 'c' => true) }
205
+ }
206
+ end
207
+
208
+ # validating
209
+ #
210
+ testing 'that validations can be cleared and do not clobber manually added errors' do
211
+ result = Dao::Result.new
212
+ params = result.params
213
+ params = result.params
214
+ errors = result.errors
215
+
216
+ assert{ result.validates(:email){|email| email.to_s.split(/@/).size == 2} }
217
+ assert{ result.validates(:password){|password| password == 'haxor'} }
218
+
219
+ params.set(:email => 'ara@dojo4.com', :password => 'fubar')
220
+ assert{ not result.valid? }
221
+
222
+ params.set(:password => 'haxor')
223
+ assert{ result.valid? }
224
+
225
+ errors.add(:name, 'ara')
226
+ assert{ not result.valid? }
227
+ end
228
+
229
+ # doc
230
+ #
231
+ testing 'that apis can be documented via the api' do
232
+ api_class =
233
+ assert {
234
+ Dao.api {
235
+ description 'foobar'
236
+ doc 'signature' => {'read' => '...', 'write' => '...'}
237
+ endpoint('/barfoo'){}
238
+ }
239
+ }
240
+ api_class_index = assert{ api_class.index.is_a?(Hash) }
241
+ api = assert{ api_class.new }
242
+ api_index = assert{ api.index.is_a?(Hash) }
243
+ assert{ api_class_index==api_index }
244
+ end
245
+
246
+ =begin
247
+
248
+ # cloning
249
+ #
250
+ testing 'simple cloning' do
251
+ data = Dao.data(:foo)
252
+ clone = assert{ data.clone }
253
+ assert{ data.path == clone.path }
254
+ assert{ data.errors == clone.errors }
255
+ assert{ data.errors.object_id != clone.errors.object_id }
256
+ assert{ data.validations == clone.validations }
257
+ assert{ data.validations.object_id != clone.validations.object_id }
258
+ assert{ data.form != clone.form }
259
+ assert{ data.form.object_id != clone.form.object_id }
260
+ assert{ data.status == clone.status }
261
+ assert{ data.status.object_id != clone.status.object_id }
262
+ assert{ data == clone }
263
+ end
264
+
265
+ =end
266
+
267
+ def hash_equal(a, b)
268
+ array = lambda{|h| h.to_a.map{|k,v| [k.to_s, v]}.sort}
269
+ array[a] == array[b]
270
+ end
271
+ end