resource_full 0.7.6

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 (35) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +100 -0
  3. data/Rakefile +19 -0
  4. data/lib/resource_full/base.rb +140 -0
  5. data/lib/resource_full/controllers/resources.rb +16 -0
  6. data/lib/resource_full/controllers/resources_controller.rb +26 -0
  7. data/lib/resource_full/controllers/routes_controller.rb +16 -0
  8. data/lib/resource_full/core_extensions/api.rb +26 -0
  9. data/lib/resource_full/core_extensions/exception.rb +25 -0
  10. data/lib/resource_full/core_extensions/from_json.rb +13 -0
  11. data/lib/resource_full/core_extensions/module.rb +13 -0
  12. data/lib/resource_full/dispatch.rb +196 -0
  13. data/lib/resource_full/models/resourced_route.rb +84 -0
  14. data/lib/resource_full/query.rb +337 -0
  15. data/lib/resource_full/render/html.rb +74 -0
  16. data/lib/resource_full/render/json.rb +107 -0
  17. data/lib/resource_full/render/xml.rb +106 -0
  18. data/lib/resource_full/render.rb +63 -0
  19. data/lib/resource_full/retrieve.rb +87 -0
  20. data/lib/resource_full/version.rb +9 -0
  21. data/lib/resource_full.rb +31 -0
  22. data/spec/resource_full/base_spec.rb +88 -0
  23. data/spec/resource_full/controllers/resources_spec.rb +30 -0
  24. data/spec/resource_full/dispatch_spec.rb +274 -0
  25. data/spec/resource_full/models/resourced_route_spec.rb +62 -0
  26. data/spec/resource_full/query/parameter_spec.rb +61 -0
  27. data/spec/resource_full/query_spec.rb +462 -0
  28. data/spec/resource_full/render/html_spec.rb +4 -0
  29. data/spec/resource_full/render/json_spec.rb +258 -0
  30. data/spec/resource_full/render/xml_spec.rb +378 -0
  31. data/spec/resource_full/render_spec.rb +5 -0
  32. data/spec/resource_full/retrieve_spec.rb +184 -0
  33. data/spec/spec.opts +4 -0
  34. data/spec/spec_helper.rb +134 -0
  35. metadata +156 -0
@@ -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.demodulize.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,74 @@
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
+ rescue => e
11
+ flash[:error] = e.message
12
+ redirect_to :back
13
+ end
14
+
15
+ def index_html
16
+ self.model_objects = send("find_all_#{model_name.pluralize}")
17
+ end
18
+
19
+ def count_html
20
+ send("count_all_#{model_name.pluralize}")
21
+ end
22
+
23
+ def new_html
24
+ self.model_object = send("new_#{model_name}")
25
+ end
26
+
27
+ def create_html
28
+ self.model_object = transactional_create_model_object
29
+ if model_object.errors.empty?
30
+ flash[:info] = "Successfully created #{model_name.humanize} with ID of #{model_object.id}."
31
+ redirect_to :action => :index, :format => :html
32
+ else
33
+ render :action => "new"
34
+ end
35
+ rescue => e
36
+ flash[:error] = e.message
37
+ redirect_to :back
38
+ end
39
+
40
+ def edit_html
41
+ self.model_object = send("edit_#{model_name}")
42
+ end
43
+
44
+ def update_html
45
+ self.model_object = transactional_update_model_object
46
+ if model_object.errors.empty?
47
+ flash[:info] = "Successfully updated #{model_name.humanize} with ID of #{model_object.id}."
48
+ redirect_to :action => :index, :format => :html
49
+ else
50
+ render :action => "edit"
51
+ end
52
+ rescue => e
53
+ flash[:error] = e.message
54
+ redirect_to :back
55
+ end
56
+
57
+ def destroy_html
58
+ self.model_object = transactional_destroy_model_object
59
+ if model_object.errors.empty?
60
+ flash[:info] = "Successfully destroyed #{model_name.humanize} with ID of #{params[:id]}."
61
+ redirect_to :action => :index, :format => :html
62
+ else
63
+ flash[:error] = model_object.errors
64
+ end
65
+ rescue ActiveRecord::RecordNotFound => e
66
+ flash[:error] = e.message
67
+ redirect_to :back
68
+ rescue => e
69
+ flash[:error] = e.message
70
+ redirect_to :back
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,107 @@
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
+ rescue => e
19
+ handle_generic_error_in_json(e)
20
+ end
21
+
22
+ def index_json_options
23
+ {}
24
+ end
25
+ def index_json
26
+ self.model_objects = send("find_all_#{model_name.pluralize}")
27
+ render :json => model_objects.to_json(index_json_options)
28
+ end
29
+
30
+ def count_json
31
+ count = send("count_all_#{model_name.pluralize}")
32
+ render :json => {"count" => count}.to_json
33
+ end
34
+
35
+ def new_json_options
36
+ {}
37
+ end
38
+ def new_json
39
+ render :json => send("new_#{model_name}").to_json(new_json_options)
40
+ end
41
+
42
+ def create_json_options
43
+ {}
44
+ end
45
+ def create_json
46
+ self.model_object = transactional_create_model_object
47
+ if model_object.errors.empty?
48
+ render :json => model_object.to_json(create_json_options), :status => :created, :location => send("#{model_name}_url", model_object.id)
49
+ else
50
+ json_data = model_object.attributes
51
+ json_data[:errors] = {:list => model_object.errors,
52
+ :full_messages => model_object.errors.full_messages}
53
+ render :json => {json_class_name(model_object) => json_data}.to_json, :status => status_for(model_object.errors)
54
+ end
55
+ rescue => e
56
+ handle_generic_error_in_json(e)
57
+ end
58
+
59
+ def edit_json_options
60
+ {}
61
+ end
62
+ def edit_json
63
+ render :json => send("edit_#{model_name}").to_json(edit_json_options)
64
+ end
65
+
66
+ def update_json_options
67
+ {}
68
+ end
69
+ def update_json
70
+ self.model_object = transactional_update_model_object
71
+ if model_object.errors.empty?
72
+ render :json => model_object.to_json(update_json_options)
73
+ else
74
+ json_data = model_object.attributes
75
+ json_data[:errors] = {:list => model_object.errors,
76
+ :full_messages => model_object.errors.full_messages}
77
+ render :json => {json_class_name(model_object) => json_data}.to_json, :status => status_for(model_object.errors)
78
+ end
79
+ rescue ActiveRecord::RecordNotFound => e
80
+ render :json => e.to_json, :status => :not_found
81
+ rescue => e
82
+ handle_generic_error_in_json(e)
83
+ end
84
+
85
+ def destroy_json
86
+ self.model_object = transactional_destroy_model_object
87
+ if model_object.errors.empty?
88
+ head :ok
89
+ else
90
+ json_data = model_object.attributes
91
+ json_data[:errors] = {:list => model_object.errors,
92
+ :full_messages => model_object.errors.full_messages}
93
+ render :json => {json_class_name(model_object) => json_data}.to_json, :status => :unprocessable_entity
94
+ end
95
+ rescue ActiveRecord::RecordNotFound => e
96
+ render :json => e.to_json, :status => :not_found
97
+ rescue => e
98
+ handle_generic_error_in_json(e)
99
+ end
100
+
101
+ private
102
+ def handle_generic_error_in_json(exception)
103
+ render :json => exception, :status => :unprocessable_entity
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,106 @@
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({:root => model_name}.merge(show_xml_options))
12
+ rescue ActiveRecord::RecordNotFound => e
13
+ render :xml => e.to_xml, :status => :not_found
14
+ rescue => e
15
+ handle_generic_error_in_xml(e)
16
+ end
17
+
18
+ def index_xml_options
19
+ {}
20
+ end
21
+ def index_xml
22
+ self.model_objects = send("find_all_#{model_name.pluralize}")
23
+ root_tag = model_objects.all? { |e| e.is_a?(model_objects.first.class) && model_objects.first.class.to_s != "Hash" } ? model_objects.first.class.name.demodulize.underscore.pluralize : model_name.pluralize
24
+ render :xml => model_objects.to_xml({:root => root_tag}.merge(index_xml_options))
25
+ end
26
+
27
+ # Renders the number of objects in the database, in the following form:
28
+ #
29
+ # <count type="integer">34</count>
30
+ #
31
+ # This accepts the same queryable parameters as the index method.
32
+ #
33
+ # N.B. This may be highly specific to my previous experience and may go away
34
+ # in previous releases.
35
+ def count_xml
36
+ xml = Builder::XmlMarkup.new :indent => 2
37
+ xml.instruct!
38
+ render :xml => xml.count(send("count_all_#{model_name.pluralize}"))
39
+ ensure
40
+ xml = nil
41
+ end
42
+
43
+ def new_xml_options
44
+ {}
45
+ end
46
+ def new_xml
47
+ render :xml => send("new_#{model_name}").to_xml({:root => model_name}.merge(new_xml_options))
48
+ end
49
+
50
+ def create_xml_options
51
+ {}
52
+ end
53
+ def create_xml
54
+ self.model_object = transactional_create_model_object
55
+ if model_object.errors.empty?
56
+ render :xml => model_object.to_xml({:root => model_name}.merge(create_xml_options)), :status => :created, :location => send("#{model_name}_url", model_object.id)
57
+ else
58
+ render :xml => model_object.errors.to_xml, :status => status_for(model_object.errors)
59
+ end
60
+ rescue => e
61
+ handle_generic_error_in_xml(e)
62
+ end
63
+
64
+ def edit_xml_options
65
+ {}
66
+ end
67
+ def edit_xml
68
+ render :xml => send("edit_#{model_name}").to_xml({:root => model_name}.merge(edit_xml_options))
69
+ end
70
+
71
+ def update_xml_options
72
+ {}
73
+ end
74
+ def update_xml
75
+ self.model_object = transactional_update_model_object
76
+ if model_object.errors.empty?
77
+ render :xml => model_object.to_xml({:root => model_name}.merge(update_xml_options))
78
+ else
79
+ render :xml => model_object.errors.to_xml, :status => status_for(model_object.errors)
80
+ end
81
+ rescue ActiveRecord::RecordNotFound => e
82
+ render :xml => e.to_xml, :status => :not_found
83
+ rescue => e
84
+ handle_generic_error_in_xml(e)
85
+ end
86
+
87
+ def destroy_xml
88
+ self.model_object = transactional_destroy_model_object
89
+ if model_object.errors.empty?
90
+ head :ok
91
+ else
92
+ render :xml => model_object.errors, :status => :unprocessable_entity
93
+ end
94
+ rescue ActiveRecord::RecordNotFound => e
95
+ render :xml => e.to_xml, :status => :not_found
96
+ rescue => e
97
+ handle_generic_error_in_xml(e)
98
+ end
99
+
100
+ private
101
+ def handle_generic_error_in_xml(exception)
102
+ render :xml => exception, :status => :unprocessable_entity
103
+ end
104
+ end
105
+ end
106
+ end