/ 25.Mar.2008 at 06:24pm
Last week I presented ActiveResource’s capabilities to some friends.
In summary, it’s a great library, but not perfect just yet, and should improve in the next versions. On the other hand, the majority of ‘REST’ APIs available – as they say – are not actually RESTful. Flickr and YouTube come to mind. Check out this link to learn on how to talk to Twitter. This other link to learn how to extend ActiveResource for non-REST APIs and this link to understand how to consume YouTube feeds.
But besides that I found out a small surprise: ActiveResource is documented in a way to imply that it has working client-side validations, but it’s not fully implemented! So I decided to investigate what would it take to have it working.
The idea is that in the REST API you can POST to an URL to call a ‘create’ method. But there is no standard equivalent for a ‘valid?’. One way of checking your ActiveResource object prior to posting is implementing class level validations in a similar way you validate ActiveRecord instances.
The snippet that needs attention is around line 219 in this file na versão 2.0.2.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
class Person < ActiveResource::Base self.site = "http://www.localhost.com:3000/" protected def validate errors.add_on_empty %w( first_name last_name ) errors.add("phone_number", "has invalid format") unless phone_number =~ /[0-9]*/ end # is only run the first time a new object is saved def validate_on_create unless valid_member?(self) errors.add("membership_discount", "has expired") end end def validate_on_update errors.add_to_base("No changes have occurred") if unchanged_attributes? end end person = Person.new("first_name" => "Jim", "phone_number" => "I will not tell you.") person.save # => false (and doesn't do the save) person.errors.empty? # => false person.errors.count # => 2 person.errors.on "last_name" # => "can't be empty" person.attributes = { "last_name" => "Halpert", "phone_number" => "555-5555" } person.save # => true (and person is now saved to the remote service) |
As we can see, it documents a simplification of what we have in ActiveRecord’s validation. It would do. But take a look a few lines below how it’s actually implemented:
1 2 3 4 5 6 7 8 |
def save_with_validation save_without_validation true rescue ResourceInvalid => error errors.from_xml(error.response.body) false end |
Get it? It doesn’t call any validation and falls back to call the original non-validated method (see documentation about alias_method_chain for better understanding).
It is not clear why it’s left this way because the validation.rb module seems to have everything ready for things to work as advertised in the documentation. Ticket 10985 says the same thing but it’s still opened.
So, if you do need this feature now, it’s not difficult to have it working. We will need a small monkey patching. At config/environment.rb (or some other file in config/initializers) put this in:
1 2 |
require 'active_resource_validation'
 |
And create the lib/active_resource_validation.rb file with the following content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
module ActiveResource::RealValidation
 def self.included(base) # :nodoc:
 base.class_eval { alias_method_chain :save, :real_validation }
 end

 def save_with_real_validation # :nodoc:
 validate if respond_to? :validate
 validate_on_create if new? && respond_to?(:validate_on_create)
 validate_on_update if !new? && respond_to?(:validate_on_update)
 valid? ? save_without_validation : false
 rescue ResourceInvalid => error
 errors.from_xml(error.response.body)
 false
 end end
 # solves http://dev.rubyonrails.org/ticket/10985
 ActiveResource::Base.send :include, ActiveResource::RealValidation
 |
That’s it, now validations should work correctly. I took a look at the Rails trunk and this is not implemented up until today at least.
3 Comments
I could only think that things get overlooked when debugging large APIs. Although, you would think SOMEONE would notice this and patch it quickly…
damon stephenson / 28.Mar.2008 at 12:36am
You should not override validate_* methods, but call them:
validate_on_create :must_have_unique_street_1_and_post_code def must_have_unique_street_1_and_post_code if Address.find_by_street_1_and_post_code(street_1, post_code) errors.add_to_base(‘Street address and post code already exist in the database’) end
yura / 08.Apr.2008 at 03:18pm
Seems like development of validations on ActiveResource is back on track by DHH himself as we can see here and here
akitaonrails / 09.Apr.2008 at 11:09pm
Leave a Comment