resource_full 0.7.6

Sign up to get free protection for your applications and to get access to all the features.
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