Skip to content
This repository
Browse code

Basic validation support [Rick Olson]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@5068 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit 8d9e6609f8f67e55bba1f9bdbea62af22360dd3c 1 parent 7c4b6a5
risk danger olson authored September 08, 2006
6  activeresource/CHANGELOG
... ...
@@ -1,5 +1,11 @@
1 1
 *SVN*
2 2
 
  3
+* Basic validation support [Rick Olson]
  4
+
  5
+  Parses the xml response of ActiveRecord::Errors#to_xml with a similar interface to ActiveRecord::Errors.  
  6
+  
  7
+    render :xml => @person.errors.to_xml, :status => '400 Validation Error'
  8
+
3 9
 * Deep hashes are converted into collections of resources.  [Jeremy Kemper]
4 10
     Person.new :name => 'Bob',
5 11
                :address => { :id => 1, :city => 'Portland' },
9  activeresource/lib/active_resource.rb
@@ -35,4 +35,11 @@
35 35
 end
36 36
 
37 37
 require 'active_resource/base'
38  
-require 'active_resource/struct'
  38
+require 'active_resource/struct'
  39
+require 'active_resource/validations'
  40
+
  41
+module ActiveResource
  42
+  Base.class_eval do
  43
+    include Validations
  44
+  end
  45
+end
4  activeresource/lib/active_resource/connection.rb
@@ -71,7 +71,9 @@ def handle_response(response)
71 71
             response
72 72
           when 404
73 73
             raise(ResourceNotFound.new(response))
74  
-          when 400...500
  74
+          when 400
  75
+            raise(ResourceInvalid.new(response))
  76
+          when 401...500
75 77
             raise(ClientError.new(response))
76 78
           when 500...600
77 79
             raise(ServerError.new(response))
125  activeresource/lib/active_resource/validations.rb
... ...
@@ -0,0 +1,125 @@
  1
+module ActiveResource
  2
+  class ResourceInvalid < ClientError
  3
+  end
  4
+
  5
+  class Errors
  6
+    include Enumerable
  7
+    attr_reader :errors
  8
+
  9
+    delegate :empty?, :to => :errors
  10
+    
  11
+    def initialize(base) # :nodoc:
  12
+      @base, @errors = base, {}
  13
+    end
  14
+
  15
+    def add_to_base(msg)
  16
+      add(:base, msg)
  17
+    end
  18
+
  19
+    def add(attribute, msg)
  20
+      @errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
  21
+      @errors[attribute.to_s] << msg
  22
+    end
  23
+
  24
+    # Returns true if the specified +attribute+ has errors associated with it.
  25
+    def invalid?(attribute)
  26
+      !@errors[attribute.to_s].nil?
  27
+    end
  28
+
  29
+    # * Returns nil, if no errors are associated with the specified +attribute+.
  30
+    # * Returns the error message, if one error is associated with the specified +attribute+.
  31
+    # * Returns an array of error messages, if more than one error is associated with the specified +attribute+.
  32
+    def on(attribute)
  33
+      errors = @errors[attribute.to_s]
  34
+      return nil if errors.nil?
  35
+      errors.size == 1 ? errors.first : errors
  36
+    end
  37
+    
  38
+    alias :[] :on
  39
+
  40
+    # Returns errors assigned to base object through add_to_base according to the normal rules of on(attribute).
  41
+    def on_base
  42
+      on(:base)
  43
+    end
  44
+
  45
+    # Yields each attribute and associated message per error added.
  46
+    def each
  47
+      @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
  48
+    end
  49
+
  50
+    # Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
  51
+    # through iteration as "First name can't be empty".
  52
+    def each_full
  53
+      full_messages.each { |msg| yield msg }
  54
+    end
  55
+
  56
+    # Returns all the full error messages in an array.
  57
+    def full_messages
  58
+      full_messages = []
  59
+
  60
+      @errors.each_key do |attr|
  61
+        @errors[attr].each do |msg|
  62
+          next if msg.nil?
  63
+
  64
+          if attr == "base"
  65
+            full_messages << msg
  66
+          else
  67
+            full_messages << [attr.humanize, msg].join(' ')
  68
+          end
  69
+        end
  70
+      end
  71
+      full_messages
  72
+    end
  73
+
  74
+    def clear
  75
+      @errors = {}
  76
+    end
  77
+
  78
+    # Returns the total number of errors added. Two errors added to the same attribute will be counted as such
  79
+    # with this as well.
  80
+    def size
  81
+      @errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
  82
+    end
  83
+
  84
+    alias_method :count, :size
  85
+    alias_method :length, :size
  86
+    
  87
+    def from_xml(xml)
  88
+      clear
  89
+      humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) }
  90
+      messages = Hash.create_from_xml(xml)['errors']['error'] rescue []
  91
+      messages.each do |message|
  92
+        attr_message = humanized_attributes.keys.detect do |attr_name|
  93
+          if message[0, attr_name.size + 1] == "#{attr_name} "
  94
+            add humanized_attributes[attr_name], message[(attr_name.size + 1)..-1]
  95
+          end
  96
+        end
  97
+        
  98
+        add_to_base message if attr_message.nil?
  99
+      end
  100
+    end
  101
+  end
  102
+  
  103
+  module Validations
  104
+    def self.included(base) # :nodoc:
  105
+      base.class_eval do
  106
+        alias_method_chain :save, :validation
  107
+      end
  108
+    end
  109
+
  110
+    def save_with_validation
  111
+      save_without_validation
  112
+    rescue ResourceInvalid
  113
+      errors.from_xml($!.response.body)
  114
+    end
  115
+
  116
+    def valid?
  117
+      errors.empty?
  118
+    end
  119
+
  120
+    # Returns the Errors object that holds all information about attribute error messages.
  121
+    def errors
  122
+      @errors ||= Errors.new(self)
  123
+    end
  124
+  end
  125
+end
38  activeresource/test/base_errors_test.rb
... ...
@@ -0,0 +1,38 @@
  1
+require "#{File.dirname(__FILE__)}/abstract_unit"
  2
+require "fixtures/person"
  3
+
  4
+class BaseErrorsTest < Test::Unit::TestCase
  5
+  def setup
  6
+    ActiveResource::HttpMock.respond_to do |mock|
  7
+      mock.post "/people", "<?xml version=\"1.0\" encoding=\"UTF-8\"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>", 400
  8
+    end
  9
+    @exception = nil
  10
+    @person    = Person.new(:name => '', :age => '')
  11
+    @person.save
  12
+  rescue ActiveResource::ResourceInvalid
  13
+    @exception = $!
  14
+  end
  15
+  
  16
+  def test_should_mark_as_invalid
  17
+    assert !@person.valid?
  18
+  end
  19
+  
  20
+  def test_should_parse_xml_errors
  21
+    assert_kind_of ActiveResource::Errors, @person.errors
  22
+    assert_equal 4, @person.errors.size
  23
+  end
  24
+
  25
+  def test_should_parse_errors_to_individual_attributes
  26
+    assert_equal "can't be blank", @person.errors.on(:age)
  27
+    assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name]
  28
+    assert_equal "Person quota full for today.", @person.errors.on_base
  29
+  end
  30
+
  31
+  def test_should_format_full_errors
  32
+    full = @person.errors.full_messages
  33
+    assert full.include?("Age can't be blank")
  34
+    assert full.include?("Name can't be blank")
  35
+    assert full.include?("Name must start with a letter")
  36
+    assert full.include?("Person quota full for today.")
  37
+  end
  38
+end
5  activeresource/test/connection_test.rb
@@ -17,8 +17,11 @@ def test_handle_response
17 17
     # 404 is a missing resource.
18 18
     assert_response_raises ActiveResource::ResourceNotFound, 404
19 19
 
  20
+    # 400 is a validation error
  21
+    assert_response_raises ActiveResource::ResourceInvalid, 400
  22
+
20 23
     # 4xx are client errors.
21  
-    [400, 499].each do |code|
  24
+    [401, 499].each do |code|
22 25
       assert_response_raises ActiveResource::ClientError, code
23 26
     end
24 27
 

0 notes on commit 8d9e660

Please sign in to comment.
Something went wrong with that request. Please try again.