From c2f90d6530dfd0ed68df9f4c429d0f498235e1d4 Mon Sep 17 00:00:00 2001 From: taryn Date: Wed, 19 Aug 2009 11:57:50 +0100 Subject: Added validations to ActiveResource. Added a smoke test to see if we can add a validation and use it, and add a validates callback and use it. Signed-off-by: Joshua Peek --- activeresource/lib/active_resource/validations.rb | 64 +++++++++++++++++------ 1 file changed, 49 insertions(+), 15 deletions(-) (limited to 'activeresource/lib/active_resource') diff --git a/activeresource/lib/active_resource/validations.rb b/activeresource/lib/active_resource/validations.rb index 4ff7be6a9e..d4d282e273 100644 --- a/activeresource/lib/active_resource/validations.rb +++ b/activeresource/lib/active_resource/validations.rb @@ -8,8 +8,10 @@ module ActiveResource # to determine whether the object in a valid state to be saved. See usage example in Validations. class Errors < ActiveModel::Errors # Grabs errors from an array of messages (like ActiveRecord::Validations) - def from_array(messages) - clear + # The second parameter directs the errors cache to be cleared (default) + # or not (by passing true) + def from_array(messages, save_cache = false) + clear unless save_cache humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) } messages.each do |message| attr_message = humanized_attributes.keys.detect do |attr_name| @@ -22,16 +24,16 @@ module ActiveResource end end - # Grabs errors from the json response. - def from_json(json) + # Grabs errors from a json response. + def from_json(json, save_cache = false) array = ActiveSupport::JSON.decode(json)['errors'] rescue [] - from_array array + from_array array, save_cache end - # Grabs errors from the XML response. - def from_xml(xml) + # Grabs errors from an XML response. + def from_xml(xml, save_cache = false) array = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue [] - from_array array + from_array array, save_cache end end @@ -57,26 +59,55 @@ module ActiveResource # module Validations extend ActiveSupport::Concern + include ActiveModel::Validations + extend ActiveModel::Validations::ClassMethods included do alias_method_chain :save, :validation end # Validate a resource and save (POST) it to the remote web service. - def save_with_validation - save_without_validation - true + # If any local validations fail - the save (POST) will not be attempted. + def save_with_validation(perform_validation = true) + # clear the remote validations so they don't interfere with the local + # ones. Otherwise we get an endless loop and can never change the + # fields so as to make the resource valid + @remote_errors = nil + if perform_validation && valid? || !perform_validation + save_without_validation + true + else + false + end rescue ResourceInvalid => error - case error.response['Content-Type'] + # cache the remote errors because every call to valid? clears + # all errors. We must keep a copy to add these back after local + # validations + @remote_errors = error + load_remote_errors(@remote_errors, true) + false + end + + + # Loads the set of remote errors into the object's Errors based on the + # content-type of the error-block received + def load_remote_errors(remote_errors, save_cache = false ) #:nodoc: + case remote_errors.response['Content-Type'] when 'application/xml' - errors.from_xml(error.response.body) + errors.from_xml(remote_errors.response.body, save_cache) when 'application/json' - errors.from_json(error.response.body) + errors.from_json(remote_errors.response.body, save_cache) end - false end # Checks for errors on an object (i.e., is resource.errors empty?). + # + # Runs all the specified local validations and returns true if no errors + # were added, otherwise false. + # Runs local validations (eg those on your Active Resource model), and + # also any errors returned from the remote system the last time we + # saved. + # Remote errors can only be cleared by trying to re-save the resource. # # ==== Examples # my_person = Person.create(params[:person]) @@ -86,7 +117,10 @@ module ActiveResource # my_person.errors.add('login', 'can not be empty') if my_person.login == '' # my_person.valid? # # => false + # def valid? + super + load_remote_errors(@remote_errors, true) if defined?(@remote_errors) && @remote_errors.present? errors.empty? end -- cgit v1.2.3 From 4dc05bc8a9824b9404cebecaba28f9f248f9995e Mon Sep 17 00:00:00 2001 From: taryn Date: Wed, 19 Aug 2009 12:20:30 +0100 Subject: Swallow ResourceNotFound error on find_every Active Record does not explode with RecordNotFound if you go looking for a collection of objects - it just returns nil. Thus Active Resource should also not explode. After all - finding no objects that match a set of conditions is not exceptional behaviour - unlike looking for a specific object with a given id (which you'd expect to exist). I've also added documentation to +find+ to reflect this. Signed-off-by: Joshua Peek --- activeresource/lib/active_resource/base.rb | 39 ++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 10 deletions(-) (limited to 'activeresource/lib/active_resource') diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 88de8b1c66..293ba75ee0 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -585,6 +585,19 @@ module ActiveResource # # StreetAddress.find(1, :params => { :person_id => 1 }) # # => GET /people/1/street_addresses/1.xml + # + # == Failure or missing data + # A failure to find the requested object raises a ResourceNotFound + # exception if the find was called with an id. + # With any other scope, find returns nil when no data is returned. + # + # Person.find(1) + # # => raises ResourcenotFound + # + # Person.find(:all) + # Person.find(:first) + # Person.find(:last) + # # => nil def find(*arguments) scope = arguments.slice!(0) options = arguments.slice!(0) || {} @@ -638,16 +651,22 @@ module ActiveResource private # Find every resource def find_every(options) - case from = options[:from] - when Symbol - instantiate_collection(get(from, options[:params])) - when String - path = "#{from}#{query_string(options[:params])}" - instantiate_collection(connection.get(path, headers) || []) - else - prefix_options, query_options = split_options(options[:params]) - path = collection_path(prefix_options, query_options) - instantiate_collection( (connection.get(path, headers) || []), prefix_options ) + begin + case from = options[:from] + when Symbol + instantiate_collection(get(from, options[:params])) + when String + path = "#{from}#{query_string(options[:params])}" + instantiate_collection(connection.get(path, headers) || []) + else + prefix_options, query_options = split_options(options[:params]) + path = collection_path(prefix_options, query_options) + instantiate_collection( (connection.get(path, headers) || []), prefix_options ) + end + rescue ActiveResource::ResourceNotFound + # Swallowing ResourceNotFound exceptions and return nil - as per + # ActiveRecord. + nil end end -- cgit v1.2.3 From 328ba3b333777bbc1269cbe0e9f590c845006c9d Mon Sep 17 00:00:00 2001 From: taryn Date: Wed, 19 Aug 2009 12:25:05 +0100 Subject: Added save! which raises ResourceInvalid unless valid? Similar to Active Record - it will raise ActiveResouce::ResourceInvalid if the resource is not valid (ie if valid? returns false) However - does not raise ActiveResource::ResourceNotFound if the callbacks fail (callbacks have not yet been implemented) - it will just try to save and raise if the callbacks all fail. This is not ideal behaviour - but will do until we decide to change the behaviour of save_with_validations to actually raise (rather than catch) the ResourceInvalid exception. Signed-off-by: Joshua Peek --- activeresource/lib/active_resource/base.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'activeresource/lib/active_resource') diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 293ba75ee0..f27febb7ef 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -893,6 +893,23 @@ module ActiveResource def save new? ? create : update end + + # Saves the resource. + # + # If the resource is new, it is created via +POST+, otherwise the + # existing resource is updated via +PUT+. + # + # With save! validations always run. If any of them fail + # ActiveResource::ResourceInvalid gets raised, and nothing is POSTed to + # the remote system. + # See ActiveResource::Validations for more information. + # + # There's a series of callbacks associated with save!. If any + # of the before_* callbacks return +false+ the action is + # cancelled and save! raises ActiveResource::ResourceInvalid. + def save! + save || raise(ResourceInvalid.new(self)) + end # Deletes the resource from the remote service. # -- cgit v1.2.3