wrest 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. data/README.rdoc +24 -17
  2. data/Rakefile +5 -4
  3. data/VERSION.yml +1 -1
  4. data/examples/delicious.rb +7 -3
  5. data/examples/redirection.rb +27 -0
  6. data/examples/twitter.rb +78 -0
  7. data/{spec/wrest/http/request_spec.rb → examples/twitter_public_timeline.rb} +15 -12
  8. data/examples/wow_realm_status.rb +7 -3
  9. data/lib/wrest/components/attributes_container/alias_accessors.rb +66 -0
  10. data/lib/wrest/components/attributes_container/typecaster.rb +44 -38
  11. data/lib/wrest/components/attributes_container.rb +23 -5
  12. data/lib/wrest/components/mutators/base.rb +1 -1
  13. data/lib/wrest/components/translators.rb +1 -1
  14. data/lib/wrest/core_ext/string/conversions.rb +1 -1
  15. data/lib/wrest/exceptions.rb +14 -2
  16. data/lib/wrest/http/get.rb +2 -0
  17. data/lib/wrest/http/redirection.rb +35 -0
  18. data/lib/wrest/http/request.rb +37 -13
  19. data/lib/wrest/http/response.rb +17 -0
  20. data/lib/wrest/http.rb +1 -0
  21. data/lib/wrest/resource/base.rb +1 -2
  22. data/lib/wrest/uri.rb +2 -0
  23. data/lib/wrest/version.rb +1 -1
  24. data/spec/functional/sample_rails_app/README +3 -0
  25. data/spec/functional/sample_rails_app/Rakefile +10 -0
  26. data/spec/functional/sample_rails_app/app/controllers/application_controller.rb +10 -0
  27. data/spec/functional/sample_rails_app/app/controllers/lead_bottles_controller.rb +7 -0
  28. data/spec/functional/sample_rails_app/app/helpers/application_helper.rb +3 -0
  29. data/spec/functional/sample_rails_app/app/models/bottle.rb +3 -0
  30. data/spec/functional/sample_rails_app/app/models/glass_bottle.rb +3 -0
  31. data/spec/functional/sample_rails_app/app/models/lead_bottle.rb +3 -0
  32. data/spec/functional/sample_rails_app/config/boot.rb +110 -0
  33. data/spec/functional/sample_rails_app/config/database.yml +16 -0
  34. data/spec/functional/sample_rails_app/config/environment.rb +42 -0
  35. data/spec/functional/sample_rails_app/config/environments/development.rb +17 -0
  36. data/spec/functional/sample_rails_app/config/environments/production.rb +28 -0
  37. data/spec/functional/sample_rails_app/config/environments/test.rb +28 -0
  38. data/spec/functional/sample_rails_app/config/initializers/backtrace_silencers.rb +7 -0
  39. data/spec/functional/sample_rails_app/config/initializers/inflections.rb +10 -0
  40. data/spec/functional/sample_rails_app/config/initializers/mime_types.rb +5 -0
  41. data/spec/functional/sample_rails_app/config/initializers/new_rails_defaults.rb +19 -0
  42. data/spec/functional/sample_rails_app/config/initializers/session_store.rb +15 -0
  43. data/spec/functional/sample_rails_app/config/locales/en.yml +5 -0
  44. data/spec/functional/sample_rails_app/config/routes.rb +3 -0
  45. data/spec/functional/sample_rails_app/db/development.sqlite3 +0 -0
  46. data/spec/functional/sample_rails_app/db/migrate/20090319115628_create_bottle.rb +13 -0
  47. data/spec/functional/sample_rails_app/db/schema.rb +20 -0
  48. data/spec/functional/sample_rails_app/db/test.sqlite3 +0 -0
  49. data/spec/functional/sample_rails_app/log/development.log +1 -0
  50. data/spec/functional/sample_rails_app/public/404.html +30 -0
  51. data/spec/functional/sample_rails_app/public/422.html +30 -0
  52. data/spec/functional/sample_rails_app/public/500.html +30 -0
  53. data/spec/functional/sample_rails_app/public/favicon.ico +0 -0
  54. data/spec/functional/sample_rails_app/public/images/rails.png +0 -0
  55. data/spec/functional/sample_rails_app/public/index.html +275 -0
  56. data/spec/functional/sample_rails_app/public/robots.txt +5 -0
  57. data/spec/functional/sample_rails_app/script/about +4 -0
  58. data/spec/functional/sample_rails_app/script/autospec +6 -0
  59. data/spec/functional/sample_rails_app/script/console +3 -0
  60. data/spec/functional/sample_rails_app/script/dbconsole +3 -0
  61. data/spec/functional/sample_rails_app/script/destroy +3 -0
  62. data/spec/functional/sample_rails_app/script/generate +3 -0
  63. data/spec/functional/sample_rails_app/script/performance/benchmarker +3 -0
  64. data/spec/functional/sample_rails_app/script/performance/profiler +3 -0
  65. data/spec/functional/sample_rails_app/script/plugin +3 -0
  66. data/spec/functional/sample_rails_app/script/runner +3 -0
  67. data/spec/functional/sample_rails_app/script/server +3 -0
  68. data/spec/functional/sample_rails_app/script/spec +10 -0
  69. data/spec/functional/sample_rails_app/script/spec_server +9 -0
  70. data/spec/functional/sample_rails_app/test/performance/browsing_test.rb +9 -0
  71. data/spec/functional/sample_rails_app/test/test_helper.rb +38 -0
  72. data/spec/functional/sample_rails_app/tmtags +2559 -0
  73. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/MIT-LICENSE +20 -0
  74. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/README.rdoc +100 -0
  75. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/Rakefile +18 -0
  76. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/init.rb +5 -0
  77. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/install.rb +1 -0
  78. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/base.rb +140 -0
  79. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/controllers/resources.rb +16 -0
  80. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/controllers/resources_controller.rb +26 -0
  81. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/controllers/routes_controller.rb +16 -0
  82. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/core_extensions/api.rb +26 -0
  83. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/core_extensions/exception.rb +23 -0
  84. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/core_extensions/from_json.rb +15 -0
  85. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/dispatch.rb +235 -0
  86. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/models/resourced_route.rb +84 -0
  87. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/query.rb +337 -0
  88. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/render/html.rb +50 -0
  89. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/render/json.rb +75 -0
  90. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/render/xml.rb +65 -0
  91. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/render.rb +63 -0
  92. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/retrieve.rb +74 -0
  93. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full/version.rb +9 -0
  94. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/lib/resource_full.rb +14 -0
  95. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/spec/resource_full/base_spec.rb +88 -0
  96. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/spec/resource_full/controllers/resources_spec.rb +29 -0
  97. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/spec/resource_full/dispatch_spec.rb +262 -0
  98. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/spec/resource_full/models/resourced_route_spec.rb +62 -0
  99. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/spec/resource_full/query/parameter_spec.rb +57 -0
  100. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/spec/resource_full/query_spec.rb +462 -0
  101. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/spec/resource_full/render/html_spec.rb +4 -0
  102. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/spec/resource_full/render/json_spec.rb +107 -0
  103. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/spec/resource_full/render/xml_spec.rb +98 -0
  104. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/spec/resource_full/render_spec.rb +5 -0
  105. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/spec/resource_full/retrieve_spec.rb +173 -0
  106. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/spec/spec_helper.rb +98 -0
  107. data/spec/functional/sample_rails_app/vendor/plugins/resource_full/uninstall.rb +1 -0
  108. data/spec/functional/spec_helper.rb +0 -0
  109. data/spec/{spec_helper.rb → unit/spec_helper.rb} +2 -2
  110. data/spec/unit/wrest/components/attributes_container/alias_accessors_spec.rb +49 -0
  111. data/spec/{wrest → unit/wrest}/components/attributes_container/typecaster_spec.rb +28 -8
  112. data/spec/{wrest → unit/wrest}/components/attributes_container_spec.rb +42 -15
  113. data/spec/{wrest → unit/wrest}/components/mutators/base_spec.rb +1 -1
  114. data/spec/{wrest → unit/wrest}/components/mutators/camel_to_snake_spec.rb +0 -0
  115. data/spec/{wrest → unit/wrest}/components/mutators/xml_mini_type_caster_spec.rb +0 -0
  116. data/spec/{wrest → unit/wrest}/components/mutators/xml_simple_type_caster_spec.rb +0 -0
  117. data/spec/{wrest → unit/wrest}/components/mutators_spec.rb +0 -0
  118. data/spec/{wrest → unit/wrest}/components/translators/xml_spec.rb +0 -0
  119. data/spec/{wrest → unit/wrest}/components/translators_spec.rb +1 -1
  120. data/spec/{wrest → unit/wrest}/core_ext/hash/conversions_spec.rb +0 -0
  121. data/spec/{wrest → unit/wrest}/core_ext/string/conversions_spec.rb +14 -0
  122. data/spec/unit/wrest/http/redirection_spec.rb +42 -0
  123. data/spec/unit/wrest/http/request_spec.rb +70 -0
  124. data/spec/unit/wrest/http/response_spec.rb +45 -0
  125. data/spec/{wrest → unit/wrest}/resource/base_spec.rb +5 -4
  126. data/spec/{wrest → unit/wrest}/uri_spec.rb +68 -67
  127. data/spec/{wrest → unit/wrest}/uri_template_spec.rb +0 -0
  128. metadata +187 -38
  129. data/lib/wrest/exceptions/method_not_overridden_exception.rb +0 -17
  130. data/lib/wrest/exceptions/unsupported_content_type_exception.rb +0 -17
  131. data/spec/wrest/http/response_spec.rb +0 -21
@@ -0,0 +1,84 @@
1
+ module ResourceFull
2
+ module Models
3
+ class RouteNotFound < Exception
4
+ end
5
+
6
+ class ResourcedRoute
7
+ attr_reader :verb, :name, :pattern, :action, :controller
8
+
9
+ class << self
10
+ def find(what, opts={})
11
+ case what
12
+ when :all
13
+ find_all_routes(opts)
14
+ else
15
+ find_named_route(what)
16
+ end
17
+ end
18
+
19
+ def find_named_route(name)
20
+ all_named_routes.find {|route| route.name == name} or raise ResourceFull::Models::RouteNotFound, "Could not find route #{name}"
21
+ end
22
+
23
+ def find_all_routes(opts={})
24
+ all_named_routes(opts).reject do |route|
25
+ opts.has_key?(:resource_id) && opts[:resource_id].to_s != route.resource.to_s
26
+ end.sort_by {|r| r.name.to_s}
27
+ end
28
+
29
+ private
30
+
31
+ # Translates an AR route into something a little more human-friendly, adding some extra
32
+ # relationships as it goes and cutting out the stuff we're not interested in--for example,
33
+ # formatted variants of regular routes.
34
+ def all_named_routes(opts={})
35
+ @all_named_routes ||= ActionController::Routing::Routes.named_routes.routes.collect do |name, route|
36
+ verb = route.conditions[:method].to_s.upcase
37
+ segs = route.segments.join
38
+ new(
39
+ :name => name,
40
+ :verb => verb,
41
+ :pattern => segs,
42
+ :action => route.requirements[:action],
43
+ :controller => route.requirements[:controller]
44
+ )
45
+ end.reject do |route|
46
+ route.formatted?
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ def initialize(opts={})
53
+ @verb = opts[:verb]
54
+ @name = opts[:name]
55
+ @pattern = opts[:pattern]
56
+ @action = opts[:action]
57
+ @controller = ResourceFull::Base.controller_for(opts[:controller])
58
+ end
59
+
60
+ def to_xml(opts={})
61
+ {
62
+ :resource => resource,
63
+ :verb => verb,
64
+ :name => name,
65
+ :pattern => pattern,
66
+ :action => action
67
+ }.to_xml(opts.merge(:root => "route"))
68
+ end
69
+
70
+ def formatted?
71
+ name.to_s =~ /^formatted/
72
+ end
73
+
74
+ def resource
75
+ controller.controller_name
76
+ end
77
+
78
+ def resourced?
79
+ controller.ancestors.include?(ResourceFull::Base)
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,337 @@
1
+ module ResourceFull
2
+ module Query
3
+ class << self
4
+ def included(base)
5
+ super(base)
6
+ base.send :extend, ClassMethods
7
+ base.queryable_with :limit, :scope => lambda {|limit| { :limit => limit }}
8
+ base.queryable_with :offset, :scope => lambda {|offset| { :offset => offset }}
9
+ base.queryable_with_order
10
+ end
11
+ end
12
+
13
+ # A Parameter represents the information necessary to describe a query relationship. It's inherently
14
+ # tied to ActiveRecord at the moment, unfortunately. Objects of this class should not be instantiated
15
+ # directly; instead, use the +queryable_with+ method.
16
+ class Parameter
17
+ attr_reader :name, :resource
18
+
19
+ def initialize(name, resource, opts={})
20
+ @name = name
21
+ @resource = resource
22
+ @fuzzy = opts[:fuzzy] || false
23
+ @allow_nil = opts[:allow_nil] || false
24
+ @default_value = opts[:default]
25
+ end
26
+
27
+ def fuzzy?; @fuzzy; end
28
+ def allow_nil?; @allow_nil; end
29
+
30
+ def to_xml(opts={})
31
+ {
32
+ :name => name.to_s,
33
+ :resource => resource.model_name.pluralize,
34
+ :fuzzy => fuzzy?,
35
+ }.to_xml(opts.merge(:root => "parameter"))
36
+ end
37
+
38
+ def applicable_to?(request_params)
39
+ if allow_nil?
40
+ request_params.has_key?(self.name.to_s) || request_params.has_key?(self.name.to_s.pluralize)
41
+ else
42
+ not param_values_for(request_params).blank?
43
+ end
44
+ end
45
+
46
+ def find(finder, request_params)
47
+ raise NotImplementedError, "Subclasses implement this behavior."
48
+ end
49
+
50
+ def subclass(new_resource)
51
+ raise NotImplementedError, "Subclasses implement this behavior."
52
+ end
53
+
54
+ protected
55
+
56
+ def param_values_for(params)
57
+ values = (params[self.name.to_s] || params[self.name.to_s.pluralize] || @default_value || '')
58
+ values = values.to_s.split(',') unless values.is_a?(Array)
59
+ values.map! {|value| "%#{value}%" } if fuzzy?
60
+ values
61
+ end
62
+
63
+ def table_for(opts={})
64
+ if opts.has_key?(:table)
65
+ opts[:table]
66
+ elsif opts.has_key?(:from)
67
+ infer_model_from(resource.model_class, opts[:from]).table_name
68
+ else
69
+ resource.model_class.table_name
70
+ end
71
+ end
72
+
73
+ def infer_model_from(model, join)
74
+ if join.is_a? Symbol
75
+ model.reflect_on_association(join).klass
76
+ elsif join.is_a? Hash
77
+ new_model = model.reflect_on_association(join.keys.first).klass
78
+ infer_model_from new_model, join.values.first
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ class CustomParameter < Parameter
85
+ attr_reader :table, :columns, :include
86
+
87
+ def initialize(name, resource, opts={})
88
+ super(name, resource, opts)
89
+
90
+ @table = table_for(opts)
91
+ @columns = columns_for(name, resource, opts)
92
+ @negated = opts[:negated] || false
93
+ @include = opts[:from] || []
94
+ end
95
+
96
+ def allow_nil?; @allow_nil; end
97
+ def negated?; @negated; end
98
+
99
+ def find(finder, request_params)
100
+ return finder unless applicable_to?(request_params)
101
+ finder.scoped :conditions => conditions_for(request_params), :include => @include
102
+ end
103
+
104
+ def subclass(new_resource)
105
+ new_table = if @table == @resource.model_class.table_name
106
+ new_resource.model_class.table_name
107
+ else @table end
108
+
109
+ self.class.new(@name, new_resource,
110
+ :fuzzy => @fuzzy,
111
+ :allow_nil => @allow_nil,
112
+ :table => new_table,
113
+ :columns => @columns,
114
+ :from => @include,
115
+ :negated => @negated
116
+ )
117
+ end
118
+
119
+ private
120
+
121
+ def conditions_for(params)
122
+ values = param_values_for(params)
123
+ unless values.empty?
124
+ final_query_string = if negated?
125
+ values.collect { |value| negated_query_string(value) }.join(" AND ")
126
+ else
127
+ values.collect { |value| query_string(value) }.join(" OR ")
128
+ end
129
+
130
+ final_values = values.sum([]) { |value| Array.new(columns.size, value) }
131
+
132
+ [ final_query_string ] + final_values
133
+ else
134
+ if (allow_nil? && params.has_key?(self.name) && params[self.name].blank?)
135
+ [query_string(params[self.name])]
136
+ else
137
+ []
138
+ end
139
+ end
140
+ end
141
+
142
+ def columns_for(name, resource, opts={})
143
+ if opts.has_key?(:columns)
144
+ opts[:columns]
145
+ elsif opts.has_key?(:column)
146
+ [ opts[:column] ]
147
+ elsif opts[:resource_identifier] && opts.has_key?(:from)
148
+ [ ResourceFull::Base.controller_for(infer_model_from(resource.model_class, opts[:from]).name.pluralize).resource_identifier ]
149
+ else
150
+ [ name ]
151
+ end
152
+ end
153
+
154
+ def query_string(value)
155
+ columns.collect do |column|
156
+ # Convert to a column name if column is a proc. TODO There must be a cleaner way to do this.
157
+ column = column.call(value) if column.is_a?(Proc)
158
+ if fuzzy?
159
+ "(#{table}.#{column} LIKE ?)"
160
+ elsif !value.blank?
161
+ "(#{table}.#{column} = ?)"
162
+ elsif allow_nil?
163
+ "(COALESCE(#{table}.#{column},'')='')"
164
+ end
165
+ end.join(" OR ")
166
+ end
167
+
168
+ def negated_query_string(value)
169
+ columns.collect do |column|
170
+ # Convert to a column name if column is a proc. TODO There must be a cleaner way to do this.
171
+ column = column.call(value) if column.is_a?(Proc)
172
+ if fuzzy?
173
+ "(#{table}.#{column} NOT LIKE ? OR #{table}.#{column} IS NULL)"
174
+ elsif !value.blank?
175
+ "(#{table}.#{column} != ? OR #{table}.#{column} IS NULL)"
176
+ end
177
+ end.join(" AND ")
178
+ end
179
+ end
180
+
181
+ class ScopedParameter < Parameter
182
+ attr_reader :scope
183
+
184
+ def initialize(name, resource, opts={})
185
+ super(name, resource, opts)
186
+ @scope = opts[:scope]
187
+ end
188
+
189
+ def method_scoped?; @scope.is_a?(Symbol); end
190
+ def proc_scoped?; @scope.is_a?(Proc); end
191
+ def hash_scoped?; @scope.is_a?(Hash); end
192
+
193
+ def find(finder, request_params)
194
+ return finder unless applicable_to?(request_params)
195
+
196
+ if proc_scoped?
197
+ finder.scoped scope.call(*param_values_for(request_params))
198
+ elsif hash_scoped?
199
+ finder.scoped scope
200
+ else
201
+ finder.send(scope, *param_values_for(request_params))
202
+ end
203
+ end
204
+
205
+ def subclass(new_resource)
206
+ self.class.new @name, new_resource, :scope => @scope
207
+ end
208
+
209
+ end
210
+
211
+ class OrderParameter < Parameter
212
+
213
+ def applicable_to?(request_params)
214
+ request_params.has_key?(:order_by)
215
+ end
216
+
217
+ def natural_sort_for(opts)
218
+ if opts.has_key?(:natural_sort)
219
+ opts[:natural_sort]
220
+ else
221
+ false
222
+ end
223
+ end
224
+
225
+ def find(finder, request_params)
226
+ return finder unless applicable_to?(request_params)
227
+
228
+ order_by = request_params[:order_by]
229
+ order_direction = request_params[:order_direction] || "asc"
230
+ sort_params = resource.orderables[order_by.to_sym] || {}
231
+ table = table_for(sort_params)
232
+ column = sort_params[:column] || order_by
233
+
234
+ order_params = returning({}) do |hash|
235
+ hash[:include] = sort_params[:from]
236
+ end
237
+
238
+ if natural_sort_for(sort_params)
239
+ # to use this natural sort you must follow these instructions: http://www.ciarpame.com/2008/06/28/true-mysql-natural-order-by-trick/
240
+ finder.scoped order_params.merge( :order => "natsort_canon(#{table}.#{column}, 'natural') #{order_direction}" )
241
+ else
242
+ finder.scoped order_params.merge( :order => "#{table}.#{column} #{order_direction}" )
243
+ end
244
+ end
245
+
246
+ def subclass(new_resource)
247
+ self.class.new(@name, new_resource)
248
+ end
249
+
250
+ end
251
+
252
+ module ClassMethods
253
+ # Indicates that the resource should be queryable with the given parameters, which will be pulled from
254
+ # the params hash on an index or count call. Accepts the following options:
255
+ #
256
+ # * :fuzzy => true : Use a LIKE query instead of =.
257
+ # * :columns / :column => ... : Override the default column, or provide a list of columns to query for this value.
258
+ # * :from => :join_name : Indicate that this value should be queried by joining on another model. Should use
259
+ # a valid relationship from this controller's exposed model (e.g., :account if belongs_to :account is specified.)
260
+ # * :resource_identifier => true : Try to look up the resource controller for this value and honor its
261
+ # specified resource identifier. Useful for nesting relationships.
262
+ # * :allow_nils => true : Indicates that a nil value for a parameter should be taken to literally indicate
263
+ # that null values should be returned. This may be changed in the future to expect the literal string 'null'
264
+ # or some other reasonable standin.
265
+ #
266
+ # Examples:
267
+ #
268
+ # queryable_with :user_id
269
+ # queryable_with :description, :fuzzy => true
270
+ # queryable_with :name, :columns => [:first_name, :last_name]
271
+ # queryable_with :street_address, :from => :address, :column => :street
272
+ #
273
+ # TODO No full-text search support.
274
+ def queryable_with(*args)
275
+ opts = args.extract_options!
276
+ opts.assert_valid_keys :default, :fuzzy, :column, :columns, :from, :table, :resource_identifier, :allow_nil, :negated, :scope
277
+ args.each do |param|
278
+ self.queryable_params << if opts.has_key?(:scope)
279
+ ResourceFull::Query::ScopedParameter.new(param, self, opts)
280
+ else
281
+ ResourceFull::Query::CustomParameter.new(param, self, opts)
282
+ end
283
+ end
284
+ end
285
+
286
+ # :nodoc:
287
+ def clear_queryable_params!
288
+ @queryable_params = []
289
+ end
290
+
291
+ # All queryable parameters. Objects are of type +ResourceFull::Query::Parameter+ or one of its subclasses.
292
+ def queryable_params
293
+ unless defined?(@queryable_params) && !@queryable_params.nil?
294
+ @queryable_params = []
295
+ if superclass.respond_to?(:queryable_params)
296
+ @queryable_params += superclass.queryable_params.collect {|param| param.subclass(self)}
297
+ end
298
+ end
299
+ @queryable_params
300
+ end
301
+
302
+ # Returns true if the controller is queryable with all of the named parameters.
303
+ def queryable_with?(*params)
304
+ (queryable_params.collect(&:name) & params.collect(&:to_sym)).size == params.size
305
+ end
306
+
307
+ # :nodoc:
308
+ def queryable_params=(params)
309
+ @queryable_params = params
310
+ end
311
+
312
+ def nests_within(*resources)
313
+ resources.each do |resource|
314
+ expected_nest_id = "#{resource.to_s.singularize}_id"
315
+ queryable_with expected_nest_id, :from => resource.to_sym, :resource_identifier => true
316
+ end
317
+ end
318
+
319
+ def orderable_by(*params)
320
+ opts = params.extract_options!
321
+ params.each do |param|
322
+ orderables[param] = opts
323
+ end
324
+ end
325
+
326
+ def queryable_with_order
327
+ unless queryable_with?(:order_by)
328
+ queryable_params << ResourceFull::Query::OrderParameter.new(:order_by, self)
329
+ end
330
+ end
331
+
332
+ def orderables
333
+ read_inheritable_attribute(:orderables) || write_inheritable_hash(:orderables, {})
334
+ end
335
+ end
336
+ end
337
+ end
@@ -0,0 +1,50 @@
1
+ module ResourceFull
2
+ module Render
3
+ module HTML
4
+ protected
5
+
6
+ def show_html
7
+ self.model_object = send("find_#{model_name}")
8
+ rescue ActiveRecord::RecordNotFound => e
9
+ flash[:error] = e.message
10
+ end
11
+
12
+ def index_html
13
+ self.model_objects = send("find_all_#{model_name.pluralize}")
14
+ end
15
+
16
+ def create_html
17
+ self.model_object = send("create_#{model_name}")
18
+ if model_object.errors.empty?
19
+ flash[:info] = "Successfully created #{model_name.humanize} with ID of #{model_object.id}."
20
+ redirect_to :action => :index, :format => :html
21
+ else
22
+ render :action => "new"
23
+ end
24
+ end
25
+
26
+ def update_html
27
+ self.model_object = send("update_#{model_name}")
28
+ if model_object.errors.empty?
29
+ flash[:info] = "Successfully updated #{model_name.humanize} with ID of #{model_object.id}."
30
+ redirect_to :action => :index, :format => :html
31
+ else
32
+ render :action => "edit"
33
+ end
34
+ end
35
+
36
+ def destroy_html
37
+ self.model_object = send("destroy_#{model_name}")
38
+ flash[:info] = "Successfully destroyed #{model_name.humanize} with ID of #{params[:id]}."
39
+ redirect_to :action => :index, :format => :html
40
+ rescue ActiveRecord::RecordNotFound => e
41
+ flash[:error] = e.message
42
+ redirect_to :back
43
+ end
44
+
45
+ def new_html
46
+ self.model_object = send("new_#{model_name}")
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,75 @@
1
+ module ResourceFull
2
+ module Render
3
+ module JSON
4
+ protected
5
+
6
+ def json_class_name(obj)
7
+ obj.class.name.demodulize.underscore
8
+ end
9
+
10
+ def show_json_options
11
+ {}
12
+ end
13
+ def show_json
14
+ self.model_object = send("find_#{model_name}")
15
+ render :json => model_object.to_json(show_json_options)
16
+ rescue ActiveRecord::RecordNotFound => e
17
+ render :json => e.to_json , :status => :not_found
18
+ end
19
+
20
+ def index_json_options
21
+ {}
22
+ end
23
+ def index_json
24
+ self.model_objects = send("find_all_#{model_name.pluralize}")
25
+ render :json => model_objects.to_json(index_json_options)
26
+ end
27
+
28
+ def create_json_options
29
+ {}
30
+ end
31
+ def create_json
32
+ self.model_object = send("create_#{model_name}")
33
+ if model_object.errors.empty?
34
+ render :json => model_object.to_json(create_json_options), :status => :created, :location => send("#{model_name}_url", model_object.id)
35
+ else
36
+ json_data = model_object.attributes
37
+ json_data[:errors] = {:list => model_object.errors,
38
+ :full_messages => model_object.errors.full_messages}
39
+ render :json => {json_class_name(model_object) => json_data}.to_json , :status => status_for(model_object.errors)
40
+ end
41
+ end
42
+
43
+ def update_json_options
44
+ {}
45
+ end
46
+ def update_json
47
+ self.model_object = send("update_#{model_name}")
48
+ if model_object.errors.empty?
49
+ render :json => model_object.to_json(update_json_options)
50
+ else
51
+ json_data = model_object.attributes
52
+ json_data[:errors] = {:list => model_object.errors,
53
+ :full_messages => model_object.errors.full_messages}
54
+ render :json => {json_class_name(model_object) => json_data}.to_json , :status => status_for(model_object.errors)
55
+ end
56
+ rescue ActiveRecord::RecordNotFound => e
57
+ render :json => e.to_json , :status => :not_found
58
+ end
59
+
60
+ def destroy_json
61
+ self.model_object = send("destroy_#{model_name}")
62
+ head :ok
63
+ rescue ActiveRecord::RecordNotFound => e
64
+ render :json => e.to_json , :status => :not_found
65
+ end
66
+
67
+ def new_json_options
68
+ {}
69
+ end
70
+ def new_json
71
+ render :json => send("new_#{model_name}").to_json(new_json_options)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,65 @@
1
+ module ResourceFull
2
+ module Render
3
+ module XML
4
+ protected
5
+
6
+ def show_xml_options
7
+ {}
8
+ end
9
+ def show_xml
10
+ self.model_object = send("find_#{model_name}")
11
+ render :xml => model_object.to_xml(show_xml_options)
12
+ rescue ActiveRecord::RecordNotFound => e
13
+ render :xml => e.to_xml, :status => :not_found
14
+ end
15
+
16
+ def index_xml_options
17
+ {}
18
+ end
19
+ def index_xml
20
+ self.model_objects = send("find_all_#{model_name.pluralize}")
21
+ render :xml => model_objects.to_xml(index_xml_options)
22
+ end
23
+
24
+ def create_xml_options
25
+ {}
26
+ end
27
+ def create_xml
28
+ self.model_object = send("create_#{model_name}")
29
+ if model_object.errors.empty?
30
+ render :xml => model_object.to_xml(create_xml_options), :status => :created, :location => send("#{model_name}_url", model_object.id)
31
+ else
32
+ render :xml => model_object.errors.to_xml, :status => status_for(model_object.errors)
33
+ end
34
+ end
35
+
36
+ def update_xml_options
37
+ {}
38
+ end
39
+ def update_xml
40
+ self.model_object = send("update_#{model_name}")
41
+ if model_object.errors.empty?
42
+ render :xml => model_object.to_xml(update_xml_options)
43
+ else
44
+ render :xml => model_object.errors.to_xml, :status => status_for(model_object.errors)
45
+ end
46
+ rescue ActiveRecord::RecordNotFound => e
47
+ render :xml => e.to_xml, :status => :not_found
48
+ end
49
+
50
+ def destroy_xml
51
+ self.model_object = send("destroy_#{model_name}")
52
+ head :ok
53
+ rescue ActiveRecord::RecordNotFound => e
54
+ render :xml => e.to_xml, :status => :not_found
55
+ end
56
+
57
+ def new_xml_options
58
+ {}
59
+ end
60
+ def new_xml
61
+ render :xml => send("new_#{model_name}").to_xml(new_xml_options)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,63 @@
1
+ module ResourceFull
2
+ module Render
3
+ include ResourceFull::Render::HTML
4
+ include ResourceFull::Render::JSON
5
+ include ResourceFull::Render::XML
6
+
7
+ def self.included(controller)
8
+ controller.rescue_from Exception, :with => :handle_generic_exception_with_correct_response_format
9
+ end
10
+
11
+ private
12
+
13
+ CONFLICT_MESSAGE = if defined?(ActiveRecord::Errors)
14
+ if ([Rails::VERSION::MAJOR, Rails::VERSION::MINOR] <=> [2,1]) >= 0 # if the rails version is 2.1 or greater...Í
15
+ (I18n.translate 'activerecord.errors.messages')[:taken]
16
+ else
17
+ ActiveRecord::Errors.default_error_messages[:taken]
18
+ end
19
+ else
20
+ "has already been taken"
21
+ end
22
+
23
+ def status_for(errors)
24
+ if errors.any? { |message| message.include? CONFLICT_MESSAGE }
25
+ :conflict
26
+ else :unprocessable_entity end
27
+ end
28
+
29
+ def handle_generic_exception_with_correct_response_format(exception)
30
+ if request.format.xml?
31
+ if defined?(ExceptionNotifiable) && defined?(ExceptionNotifier) && self.is_a?(ExceptionNotifiable) && !(consider_all_requests_local || local_request?)
32
+ deliverer = self.class.exception_data
33
+ data = case deliverer
34
+ when nil then {}
35
+ when Symbol then send(deliverer)
36
+ when Proc then deliverer.call(self)
37
+ end
38
+
39
+ ExceptionNotifier.deliver_exception_notification(exception, self,
40
+ request, data)
41
+ end
42
+ logger.error exception.message + "\n" + exception.clean_backtrace.collect {|s| "\t#{s}\n"}.join
43
+ render :xml => exception.to_xml, :status => :server_error
44
+ elsif request.format.json?
45
+ if defined?(ExceptionNotifiable) && defined?(ExceptionNotifier) && self.is_a?(ExceptionNotifiable) && !(consider_all_requests_local || local_request?)
46
+ deliverer = self.class.exception_data
47
+ data = case deliverer
48
+ when nil then {}
49
+ when Symbol then send(deliverer)
50
+ when Proc then deliverer.call(self)
51
+ end
52
+
53
+ ExceptionNotifier.deliver_exception_notification(exception, self,
54
+ request, data)
55
+ end
56
+ logger.error exception.message + "\n" + exception.clean_backtrace.collect {|s| "\t#{s}\n"}.join
57
+ render :json => exception.to_json, :status => :server_error
58
+ else
59
+ raise exception
60
+ end
61
+ end
62
+ end
63
+ end