dao 4.2.1 → 4.4.2

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 (78) hide show
  1. data/README +103 -63
  2. data/Rakefile +3 -3
  3. data/dao.gemspec +27 -16
  4. data/lib/dao.rb +17 -17
  5. data/lib/dao/active_record.rb +1 -0
  6. data/lib/dao/api.rb +2 -1
  7. data/lib/dao/api/{endpoints.rb → call.rb} +1 -0
  8. data/lib/dao/api/context.rb +2 -0
  9. data/lib/dao/api/dsl.rb +1 -0
  10. data/lib/dao/api/initializers.rb +1 -0
  11. data/lib/dao/api/modes.rb +1 -0
  12. data/lib/dao/api/routes.rb +1 -0
  13. data/lib/dao/blankslate.rb +1 -0
  14. data/lib/dao/conducer.rb +315 -274
  15. data/lib/dao/conducer/active_model.rb +98 -0
  16. data/lib/dao/conducer/attributes.rb +1 -0
  17. data/lib/dao/conducer/autocrud.rb +58 -0
  18. data/lib/dao/conducer/callback_support.rb +20 -0
  19. data/lib/dao/conducer/collection.rb +45 -0
  20. data/lib/dao/conducer/controller_support.rb +104 -0
  21. data/lib/dao/conducer/nav_support.rb +9 -0
  22. data/lib/dao/conducer/view_support.rb +16 -0
  23. data/lib/dao/data.rb +2 -1
  24. data/lib/dao/db.rb +2 -0
  25. data/lib/dao/endpoint.rb +1 -0
  26. data/lib/dao/engine.rb +1 -0
  27. data/lib/dao/errors.rb +109 -99
  28. data/lib/dao/exceptions.rb +1 -0
  29. data/lib/dao/extractor.rb +1 -0
  30. data/lib/dao/form.rb +175 -20
  31. data/lib/dao/instance_exec.rb +1 -0
  32. data/lib/dao/mode.rb +1 -0
  33. data/lib/dao/mongo_mapper.rb +1 -0
  34. data/lib/dao/name.rb +1 -0
  35. data/lib/dao/params.rb +2 -1
  36. data/lib/dao/path.rb +1 -0
  37. data/lib/dao/path_map.rb +24 -0
  38. data/lib/dao/rack.rb +1 -0
  39. data/lib/dao/rack/middleware.rb +1 -0
  40. data/lib/dao/rack/middleware/params_parser.rb +1 -0
  41. data/lib/dao/rails.rb +12 -32
  42. data/lib/dao/rails/lib/generators/dao/USAGE +2 -2
  43. data/lib/dao/rails/lib/generators/dao/dao_generator.rb +8 -27
  44. data/lib/dao/rails/lib/generators/dao/templates/api.rb +2 -1
  45. data/lib/dao/rails/lib/generators/dao/templates/api_controller.rb +22 -20
  46. data/lib/dao/rails/lib/generators/dao/templates/conducer.rb +49 -43
  47. data/lib/dao/rails/lib/generators/dao/templates/dao.css +26 -25
  48. data/lib/dao/rails/lib/generators/dao/templates/dao.js +3 -0
  49. data/lib/dao/rails/lib/generators/dao/templates/dao_helper.rb +58 -45
  50. data/lib/dao/result.rb +50 -1
  51. data/lib/dao/route.rb +1 -0
  52. data/lib/dao/slug.rb +12 -36
  53. data/lib/dao/status.rb +91 -7
  54. data/lib/dao/stdext.rb +1 -0
  55. data/lib/dao/support.rb +90 -80
  56. data/lib/dao/upload.rb +396 -0
  57. data/lib/dao/validations.rb +23 -5
  58. data/lib/dao/validations/callback.rb +5 -0
  59. data/lib/dao/validations/common.rb +100 -3
  60. data/lib/dao/validations/instance.rb +17 -0
  61. data/lib/dao/validations/validator.rb +192 -91
  62. data/test/active_model_conducer_lint_test.rb +1 -0
  63. data/test/api_test.rb +15 -0
  64. data/test/conducer_test.rb +608 -90
  65. data/test/data/han-solo.jpg +0 -0
  66. data/test/form_test.rb +1 -0
  67. data/test/helper.rb +1 -0
  68. data/test/leak.rb +1 -0
  69. data/test/support_test.rb +4 -1
  70. data/test/testing.rb +1 -0
  71. data/test/validations_test.rb +176 -30
  72. metadata +120 -131
  73. data/b.rb +0 -38
  74. data/lib/dao/conducer/crud.rb +0 -70
  75. data/lib/dao/current.rb +0 -66
  76. data/lib/dao/image_cache.rb +0 -193
  77. data/lib/dao/rails/lib/generators/dao/templates/conducer_controller.rb +0 -79
  78. data/test/db.yml +0 -9
data/lib/dao/result.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Dao
2
3
  class Result < ::Map
3
4
  def initialize(*args, &block)
@@ -12,8 +13,56 @@ module Dao
12
13
  self.data = options[:data] || Data.new
13
14
  end
14
15
 
16
+ =begin
17
+ %w(
18
+ path
19
+ route
20
+ mode
21
+ status
22
+ params
23
+ errors
24
+ data
25
+ ).each do |attr|
26
+
27
+ module_eval <<-__, __FILE__, __LINE__
28
+ def #{ attr }(*value)
29
+ unless value.empty?
30
+ self["#{ attr }"] = value.first
31
+ end
32
+ self["#{ attr }"]
33
+ end
34
+
35
+ def #{ attr }=(value)
36
+ self["#{ attr }"] = value
37
+ end
38
+ __
39
+
40
+ end
41
+
42
+ def name
43
+ path
44
+ end
45
+
46
+ def attributes
47
+ params
48
+ end
49
+ =end
50
+
51
+
52
+ def form
53
+ @form ||= (
54
+ Form.new.tap do |f|
55
+ f.object = self
56
+ f.attributes = params
57
+ f.errors = errors
58
+ f.status = status
59
+ f.name = path
60
+ end
61
+ )
62
+ end
63
+
15
64
  def inspect
16
- ::JSON.pretty_generate(self, :max_nesting => 0)
65
+ Dao.json_for(self)
17
66
  end
18
67
  end
19
68
  end
data/lib/dao/route.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Dao
2
3
  class Route < ::String
3
4
  Default = '/index'.freeze
data/lib/dao/slug.rb CHANGED
@@ -1,40 +1,16 @@
1
- begin
2
- require 'rubygems'
3
- rescue LoadError
4
- end
5
-
6
- begin
7
- require 'unidecode'
8
- rescue LoadError
9
- end
10
-
11
- class Slug < ::String
12
- Slug::Version = '0.0.1'
13
-
14
- def Slug.version
15
- '0.0.1'
16
- end
17
-
18
- Join = '-'
1
+ # -*- encoding : utf-8 -*-
2
+ module Dao
3
+ class Slug < ::String
4
+ Join = '-'
19
5
 
20
- def Slug.for(*args)
21
- options = args.last.is_a?(Hash) ? args.pop : {}
22
- join = options[:join]||options['join']||Join
23
- string = args.flatten.compact.join(join)
24
- string = unidecode(string)
25
- words = string.to_s.scan(%r/\w+/)
26
- words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
27
- words.delete_if{|word| word.nil? or word.strip.empty?}
28
- new(words.join(join).downcase)
29
- end
30
-
31
- unless defined?(Unidecoder)
32
- def Slug.unidecode(string)
33
- string
34
- end
35
- else
36
- def Slug.unidecode(string)
37
- Unidecoder.decode(string)
6
+ def Slug.for(*args)
7
+ options = args.last.is_a?(Hash) ? args.pop : {}
8
+ join = options[:join]||options['join']||Join
9
+ string = args.flatten.compact.join(join)
10
+ words = string.to_s.scan(%r/\w+/)
11
+ words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
12
+ words.delete_if{|word| word.nil? or word.strip.empty?}
13
+ new(words.join(join).downcase)
38
14
  end
39
15
  end
40
16
  end
data/lib/dao/status.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Dao
2
3
  class Status < ::String
3
4
  ## http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
@@ -57,7 +58,84 @@ module Dao
57
58
  504 => "Gateway Timeout",
58
59
  505 => "HTTP Version Not Supported",
59
60
  507 => "Insufficient Storage",
60
- 510 => "Not Extended"
61
+ 510 => "Not Extended",
62
+
63
+
64
+ ### ref: https://github.com/joho/7XX-rfc
65
+
66
+ #70X => "Inexcusable",
67
+ 701 => "Meh",
68
+ 702 => "Emacs",
69
+
70
+ #71X => "Novelty Implementations",
71
+ 710 => "PHP",
72
+ 711 => "Convenience Store",
73
+ 719 => "I am not a teapot",
74
+
75
+ #72X => "Edge Cases",
76
+ 720 => "Unpossible",
77
+ 721 => "Known Unknowns",
78
+ 722 => "Unknown Unknowns",
79
+ 723 => "Tricky",
80
+ 724 => "This line should be unreachable",
81
+ 725 => "It works on my machine",
82
+ 726 => "It's a feature, not a bug",
83
+
84
+ #73X => "Fucking",
85
+ 731 => "Fucking Rubygems",
86
+ 732 => "Fucking Unicode",
87
+ 733 => "Fucking Deadlocks",
88
+ 734 => "Fucking Deferreds",
89
+ 735 => "Fucking IE",
90
+ 736 => "Fucking Race Conditions",
91
+ 737 => "FuckThreadsing",
92
+ 738 => "Fucking Bundler",
93
+ 739 => "Fucking Windows",
94
+
95
+ #74X => "Meme Driven",
96
+ 741 => "Compiling",
97
+ 742 => "A kitten dies",
98
+ 743 => "I thought I knew regular expressions",
99
+ 744 => "Y U NO write integration tests?",
100
+ 745 => "I don't always test my code, but when I do I do it in production",
101
+ 746 => "Missed Ballmer Peak",
102
+ 747 => "Motherfucking Snakes on the Motherfucking Plane",
103
+ 748 => "Confounded by Ponies",
104
+ 749 => "Reserved for Chuck Norris",
105
+
106
+ #75X => "Syntax Errors",
107
+ 750 => "Didn't bother to compile it",
108
+ 753 => "Syntax Error",
109
+
110
+ #76X => "Substance-Affected Developer",
111
+ 761 => "Hungover",
112
+ 762 => "Stoned",
113
+ 763 => "Under-Caffeinated",
114
+ 764 => "Over-Caffeinated",
115
+ 765 => "Railscamp",
116
+ 766 => "Sober",
117
+ 767 => "Drunk",
118
+
119
+ #77X => "Predictable Problems",
120
+ 771 => "Cached for too long",
121
+ 772 => "Not cached long enough",
122
+ 773 => "Not cached at all",
123
+ 774 => "Why was this cached?",
124
+ 776 => "Error on the Exception",
125
+ 777 => "Coincidence",
126
+ 778 => "Off By One Error",
127
+ 779 => "Off By Too Many To Count Error",
128
+
129
+ #78X => "Somebody Else's Problem",
130
+ 781 => "Operations",
131
+ 782 => "QA",
132
+ 783 => "It was a customer request, honestly",
133
+ 784 => "Management, obviously",
134
+ 785 => "TPS Cover Sheet not attached",
135
+
136
+ #79X => "Internet crashed",
137
+ 797 => "This is the last page of the Internet. Go back",
138
+ 799 => "End of the world"
61
139
  }
62
140
  ) unless defined?(Code2Message)
63
141
 
@@ -66,7 +144,8 @@ module Dao
66
144
  200 => 'success',
67
145
  300 => 'redirection',
68
146
  400 => 'client_error',
69
- 500 => 'server_error'
147
+ 500 => 'server_error',
148
+ 700 => 'developer_error'
70
149
  }) unless defined?(Groups)
71
150
 
72
151
  # class methods
@@ -78,10 +157,14 @@ module Dao
78
157
 
79
158
  def underscore(camel_cased_word)
80
159
  camel_cased_word.to_s.gsub(/::/, '/').
160
+ gsub(/\s+/, '_').
81
161
  gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
82
162
  gsub(/([a-z\d])([A-Z])/,'\1_\2').
83
- #gsub(/\s+/, '').
163
+ gsub(/[^A-Za-z0-9_]/, '_').
84
164
  tr("-", "_").
165
+ squeeze('_').
166
+ gsub(/^_+/, '').
167
+ gsub(/_+$/, '').
85
168
  downcase
86
169
  end
87
170
 
@@ -150,7 +233,7 @@ module Dao
150
233
 
151
234
  Symbol2Code = (
152
235
  Code2Message.inject(Hash.new) do |hash, (code, message)|
153
- sym = Status.underscore(message.gsub(/\s+/, "")).to_sym
236
+ sym = Status.underscore(message.gsub(/\s+/, "_")).to_sym
154
237
  hash.update(sym => code)
155
238
  end
156
239
  ) unless defined?(Symbol2Code)
@@ -165,9 +248,10 @@ module Dao
165
248
 
166
249
  # instance methods
167
250
  #
168
- attr :code
169
- attr :message
170
- attr :group
251
+ attr_accessor :code
252
+ attr_accessor :message
253
+ attr_accessor :group
254
+ attr_accessor :source
171
255
 
172
256
  def initialize(*args)
173
257
  update(*args)
data/lib/dao/stdext.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  class Array
2
3
  def to_dao(*args, &block)
3
4
  Dao.to_dao(self, *args, &block)
data/lib/dao/support.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Dao
2
3
  def map_for(*args, &block)
3
4
  Map.for(*args, &block)
@@ -66,42 +67,29 @@ module Dao
66
67
  Form.name_for(path, *keys)
67
68
  end
68
69
 
69
- def parse(*args, &block)
70
- Params.process(*args, &block)
71
- end
72
-
73
- def normalize_parameters(params)
74
- Params.normalize_parameters(params)
75
- end
76
-
77
70
  def current
78
- @current ||=
79
- Map.new(
80
- :controller => nil
81
- )
71
+ Current
82
72
  end
83
73
 
84
74
  def current_controller(*args)
85
- current.controller = args.first unless args.empty?
86
- current.controller || mock_controller
75
+ Current.controller = args.first unless args.empty?
76
+ Current.controller
87
77
  end
88
78
  alias_method('controller', 'current_controller')
89
79
 
90
80
  def current_controller=(controller)
91
- current.controller = controller
81
+ Current.controller = controller
92
82
  end
93
83
  alias_method('controller=', 'current_controller=')
94
84
 
85
+ def mock_controller
86
+ Current.mock_controller
87
+ end
88
+
95
89
  %w( request response session ).each do |attr|
96
90
  module_eval <<-__, __FILE__, __LINE__
97
91
  def current_#{ attr }
98
- @current_#{ attr } ||= current_controller.instance_eval{ #{ attr } }
99
- end
100
- def current_#{ attr }=(value)
101
- @current_#{ attr } = value
102
- end
103
- def #{ attr }
104
- current_#{ attr }
92
+ current_controller.instance_eval{ #{ attr } }
105
93
  end
106
94
  __
107
95
  end
@@ -109,7 +97,7 @@ module Dao
109
97
  %w( current_user effective_user real_user ).each do |attr|
110
98
  module_eval <<-__, __FILE__, __LINE__
111
99
  def #{ attr }
112
- @#{ attr } ||= current_controller.instance_eval{ #{ attr } }
100
+ current_controller.instance_eval{ #{ attr } }
113
101
  end
114
102
  __
115
103
  end
@@ -122,58 +110,13 @@ module Dao
122
110
  end
123
111
  end
124
112
 
125
- def key_for(*keys)
126
- key = keys.flatten.join('.').strip
127
- key.split(%r/\s*[,.:_-]\s*/).map{|key| key =~ %r/^\d+$/ ? Integer(key) : key}
128
- end
129
-
130
- def mock_controller
131
- ensure_rails_application do
132
- require 'action_dispatch/testing/test_request.rb'
133
- require 'action_dispatch/testing/test_response.rb'
134
- store = ActiveSupport::Cache::MemoryStore.new
135
- controller =
136
- begin
137
- ApplicationController.new
138
- rescue NameError
139
- ActionController::Base.new
140
- end
141
- controller.perform_caching = true
142
- controller.cache_store = store
143
- request = ActionDispatch::TestRequest.new
144
- response = ActionDispatch::TestResponse.new
145
- controller.request = request
146
- controller.response = response
147
- controller.send(:initialize_template_class, response)
148
- controller.send(:assign_shortcuts, request, response)
149
- controller.send(:default_url_options).merge!(DefaultUrlOptions) if defined?(DefaultUrlOptions)
150
- controller
151
- end
152
- end
153
-
154
- def ensure_rails_application(&block)
155
- if Rails.application.nil?
156
- mock = Class.new(Rails::Application)
157
- Rails.application = mock.instance
158
- begin
159
- block.call()
160
- ensure
161
- Rails.application = nil
162
- end
163
- else
164
- block.call()
165
- end
166
- end
167
-
168
113
  def normalize_parameters(params)
169
114
  dao = (params.delete('dao') || {}).merge(params.delete(:dao) || {})
170
115
 
171
- unless dao.blank?
116
+ unless dao.empty?
172
117
  dao.each do |key, paths_and_values|
173
- params[key] = nil
174
118
  next if paths_and_values.blank?
175
-
176
- map = Map.new
119
+ map = Map.for(params[key])
177
120
 
178
121
  paths_and_values.each do |path, value|
179
122
  keys = keys_for(path)
@@ -182,16 +125,25 @@ module Dao
182
125
 
183
126
  params[key] = map
184
127
  end
128
+
129
+ params['dao'] = dao
185
130
  end
186
131
 
187
- #params[:dao] = {:normalized => true}
188
132
  params
189
133
  end
190
134
 
191
- def keys_for(keys)
192
- keys.strip.split(%r/\s*[,._-]\s*/).map{|key| key =~ %r/^\d+$/ ? Integer(key) : key}
135
+ def keys_for(*keys)
136
+ keys = keys.join('.').scan(/[^\,\.\s]+/iomx)
137
+
138
+ keys.map do |key|
139
+ digity, stringy, digits = %r/^(~)?(\d+)$/iomx.match(key).to_a
140
+
141
+ digity ? stringy ? String(digits) : Integer(digits) : key
142
+ end
193
143
  end
194
144
 
145
+ alias_method(:key_for, :keys_for)
146
+
195
147
  def render_json(object, options = {})
196
148
  options = options.to_options!
197
149
  controller = options[:controller] || Dao.current_controller
@@ -210,18 +162,76 @@ module Dao
210
162
  end
211
163
  end
212
164
 
213
- def json_for(object)
165
+ def json_for(object, options = {})
214
166
  object = object.as_json if object.respond_to?(:as_json)
215
167
 
168
+ options = options.empty? ? Map.for(options) : options
169
+ options[:pretty] = json_pretty? unless options.has_key?(:pretty)
170
+
216
171
  begin
217
- if Rails.env.production?
218
- ::JSON.generate(object)
219
- else
220
- ::JSON.pretty_generate(object, :max_nesting => 0)
221
- end
172
+ MultiJson.dump(object, options)
222
173
  rescue Object => e
223
- Rails.logger.error(e)
224
174
  YAML.load( object.to_yaml ).to_json
225
175
  end
226
176
  end
177
+
178
+ def json_pretty?
179
+ @json_pretty ||= (defined?(Rails) ? !Rails.env.production? : true)
180
+ end
181
+
182
+ def call(object, method, *args, &block)
183
+ args = Dao.args_for_arity(args, object.method(method).arity)
184
+ object.send(method, *args, &block)
185
+ end
186
+
187
+ def args_for_arity(args, arity)
188
+ arity = Integer(arity.respond_to?(:arity) ? arity.arity : arity)
189
+ arity < 0 ? args.dup : args.slice(0, arity)
190
+ end
191
+
192
+ def tree_walk(node, *path, &block)
193
+ iterator = Array === node ? :each_with_index : :each
194
+
195
+ node.send(iterator) do |key, val|
196
+ key, val = val, key if Array === node
197
+ path.push(key)
198
+ begin
199
+ caught =
200
+ catch(:tree_walk) do
201
+ block.call(path, val)
202
+ nil
203
+ end
204
+ next if caught==:next_sibling
205
+
206
+ case val
207
+ when Hash, Array
208
+ tree_walk(val, *path, &block)
209
+ end
210
+ ensure
211
+ path.pop
212
+ end
213
+ end
214
+ end
215
+
216
+
217
+ {
218
+ 'ffi-uuid' => proc{|*args| FFI::UUID.generate_time.to_s},
219
+ 'uuidtools' => proc{|*args| UUIDTools::UUID.timestamp_create.to_s},
220
+ 'uuid' => proc{|*args| UUID.generate.to_s},
221
+ }.each do |lib, implementation|
222
+ begin
223
+ require(lib)
224
+ define_method(:uuid, &implementation)
225
+ break
226
+ rescue LoadError
227
+ nil
228
+ end
229
+ end
230
+ abort 'no suitable uuid generation library detected' unless method_defined?(:uuid)
231
+
232
+ def ensure_interface!(object, *interface)
233
+ interface.flatten.compact.each do |method|
234
+ raise(NotImplementedError, "#{ object.class.name }##{ method }") unless object.respond_to?(method)
235
+ end
236
+ end
227
237
  end