root/trunk/activeresource/lib/active_resource/validations.rb
| Revision 7098, 10.0 kB (checked in by david, 3 years ago) |
|---|
| Line | |
|---|---|
| 1 | module ActiveResource |
| 2 | class ResourceInvalid < ClientError #:nodoc: |
| 3 | end |
| 4 | |
| 5 | # Active Resource validation is reported to and from this object, which is used by Base#save |
| 6 | # to determine whether the object in a valid state to be saved. See usage example in Validations. |
| 7 | class Errors |
| 8 | include Enumerable |
| 9 | attr_reader :errors |
| 10 | |
| 11 | delegate :empty?, :to => :errors |
| 12 | |
| 13 | def initialize(base) # :nodoc: |
| 14 | @base, @errors = base, {} |
| 15 | end |
| 16 | |
| 17 | # Add an error to the base Active Resource object rather than an attribute. |
| 18 | # |
| 19 | # ==== Examples |
| 20 | # my_folder = Folder.find(1) |
| 21 | # my_folder.errors.add_to_base("You can't edit an existing folder") |
| 22 | # my_folder.errors.on_base |
| 23 | # # => "You can't edit an existing folder" |
| 24 | # |
| 25 | # my_folder.errors.add_to_base("This folder has been tagged as frozen") |
| 26 | # my_folder.valid? |
| 27 | # # => false |
| 28 | # my_folder.errors.on_base |
| 29 | # # => ["You can't edit an existing folder", "This folder has been tagged as frozen"] |
| 30 | # |
| 31 | def add_to_base(msg) |
| 32 | add(:base, msg) |
| 33 | end |
| 34 | |
| 35 | # Adds an error to an Active Resource object's attribute (named for the +attribute+ parameter) |
| 36 | # with the error message in +msg+. |
| 37 | # |
| 38 | # ==== Examples |
| 39 | # my_resource = Node.find(1) |
| 40 | # my_resource.errors.add('name', 'can not be "base"') if my_resource.name == 'base' |
| 41 | # my_resource.errors.on('name') |
| 42 | # # => 'can not be "base"!' |
| 43 | # |
| 44 | # my_resource.errors.add('desc', 'can not be blank') if my_resource.desc == '' |
| 45 | # my_resource.valid? |
| 46 | # # => false |
| 47 | # my_resource.errors.on('desc') |
| 48 | # # => 'can not be blank!' |
| 49 | # |
| 50 | def add(attribute, msg) |
| 51 | @errors[attribute.to_s] = [] if @errors[attribute.to_s].nil? |
| 52 | @errors[attribute.to_s] << msg |
| 53 | end |
| 54 | |
| 55 | # Returns true if the specified +attribute+ has errors associated with it. |
| 56 | # |
| 57 | # ==== Examples |
| 58 | # my_resource = Disk.find(1) |
| 59 | # my_resource.errors.add('location', 'must be Main') unless my_resource.location == 'Main' |
| 60 | # my_resource.errors.on('location') |
| 61 | # # => 'must be Main!' |
| 62 | # |
| 63 | # my_resource.errors.invalid?('location') |
| 64 | # # => true |
| 65 | # my_resource.errors.invalid?('name') |
| 66 | # # => false |
| 67 | def invalid?(attribute) |
| 68 | !@errors[attribute.to_s].nil? |
| 69 | end |
| 70 | |
| 71 | # A method to return the errors associated with +attribute+, which returns nil, if no errors are |
| 72 | # associated with the specified +attribute+, the error message if one error is associated with the specified +attribute+, |
| 73 | # or an array of error messages if more than one error is associated with the specified +attribute+. |
| 74 | # |
| 75 | # ==== Examples |
| 76 | # my_person = Person.new(params[:person]) |
| 77 | # my_person.errors.on('login') |
| 78 | # # => nil |
| 79 | # |
| 80 | # my_person.errors.add('login', 'can not be empty') if my_person.login == '' |
| 81 | # my_person.errors.on('login') |
| 82 | # # => 'can not be empty' |
| 83 | # |
| 84 | # my_person.errors.add('login', 'can not be longer than 10 characters') if my_person.login.length > 10 |
| 85 | # my_person.errors.on('login') |
| 86 | # # => ['can not be empty', 'can not be longer than 10 characters'] |
| 87 | def on(attribute) |
| 88 | errors = @errors[attribute.to_s] |
| 89 | return nil if errors.nil? |
| 90 | errors.size == 1 ? errors.first : errors |
| 91 | end |
| 92 | |
| 93 | alias :[] :on |
| 94 | |
| 95 | # A method to return errors assigned to +base+ object through add_to_base, which returns nil, if no errors are |
| 96 | # associated with the specified +attribute+, the error message if one error is associated with the specified +attribute+, |
| 97 | # or an array of error messages if more than one error is associated with the specified +attribute+. |
| 98 | # |
| 99 | # ==== Examples |
| 100 | # my_account = Account.find(1) |
| 101 | # my_account.errors.on_base |
| 102 | # # => nil |
| 103 | # |
| 104 | # my_account.errors.add_to_base("This account is frozen") |
| 105 | # my_account.errors.on_base |
| 106 | # # => "This account is frozen" |
| 107 | # |
| 108 | # my_account.errors.add_to_base("This account has been closed") |
| 109 | # my_account.errors.on_base |
| 110 | # # => ["This account is frozen", "This account has been closed"] |
| 111 | # |
| 112 | def on_base |
| 113 | on(:base) |
| 114 | end |
| 115 | |
| 116 | # Yields each attribute and associated message per error added. |
| 117 | # |
| 118 | # ==== Examples |
| 119 | # my_person = Person.new(params[:person]) |
| 120 | # |
| 121 | # my_person.errors.add('login', 'can not be empty') if my_person.login == '' |
| 122 | # my_person.errors.add('password', 'can not be empty') if my_person.password == '' |
| 123 | # messages = '' |
| 124 | # my_person.errors.each {|attr, msg| messages += attr.humanize + " " + msg + "<br />"} |
| 125 | # messages |
| 126 | # # => "Login can not be empty<br />Password can not be empty<br />" |
| 127 | # |
| 128 | def each |
| 129 | @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } } |
| 130 | end |
| 131 | |
| 132 | # Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned |
| 133 | # through iteration as "First name can't be empty". |
| 134 | # |
| 135 | # ==== Examples |
| 136 | # my_person = Person.new(params[:person]) |
| 137 | # |
| 138 | # my_person.errors.add('login', 'can not be empty') if my_person.login == '' |
| 139 | # my_person.errors.add('password', 'can not be empty') if my_person.password == '' |
| 140 | # messages = '' |
| 141 | # my_person.errors.each_full {|msg| messages += msg + "<br/>"} |
| 142 | # messages |
| 143 | # # => "Login can not be empty<br />Password can not be empty<br />" |
| 144 | # |
| 145 | def each_full |
| 146 | full_messages.each { |msg| yield msg } |
| 147 | end |
| 148 | |
| 149 | # Returns all the full error messages in an array. |
| 150 | # |
| 151 | # ==== Examples |
| 152 | # my_person = Person.new(params[:person]) |
| 153 | # |
| 154 | # my_person.errors.add('login', 'can not be empty') if my_person.login == '' |
| 155 | # my_person.errors.add('password', 'can not be empty') if my_person.password == '' |
| 156 | # messages = '' |
| 157 | # my_person.errors.full_messages.each {|msg| messages += msg + "<br/>"} |
| 158 | # messages |
| 159 | # # => "Login can not be empty<br />Password can not be empty<br />" |
| 160 | # |
| 161 | def full_messages |
| 162 | full_messages = [] |
| 163 | |
| 164 | @errors.each_key do |attr| |
| 165 | @errors[attr].each do |msg| |
| 166 | next if msg.nil? |
| 167 | |
| 168 | if attr == "base" |
| 169 | full_messages << msg |
| 170 | else |
| 171 | full_messages << [attr.humanize, msg].join(' ') |
| 172 | end |
| 173 | end |
| 174 | end |
| 175 | full_messages |
| 176 | end |
| 177 | |
| 178 | def clear |
| 179 | @errors = {} |
| 180 | end |
| 181 | |
| 182 | # Returns the total number of errors added. Two errors added to the same attribute will be counted as such |
| 183 | # with this as well. |
| 184 | # |
| 185 | # ==== Examples |
| 186 | # my_person = Person.new(params[:person]) |
| 187 | # my_person.errors.size |
| 188 | # # => 0 |
| 189 | # |
| 190 | # my_person.errors.add('login', 'can not be empty') if my_person.login == '' |
| 191 | # my_person.errors.add('password', 'can not be empty') if my_person.password == '' |
| 192 | # my_person.error.size |
| 193 | # # => 2 |
| 194 | # |
| 195 | def size |
| 196 | @errors.values.inject(0) { |error_count, attribute| error_count + attribute.size } |
| 197 | end |
| 198 | |
| 199 | alias_method :count, :size |
| 200 | alias_method :length, :size |
| 201 | |
| 202 | # Grabs errors from the XML response. |
| 203 | def from_xml(xml) |
| 204 | clear |
| 205 | humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) } |
| 206 | messages = Hash.from_xml(xml)['errors']['error'] rescue [] |
| 207 | messages.each do |message| |
| 208 | attr_message = humanized_attributes.keys.detect do |attr_name| |
| 209 | if message[0, attr_name.size + 1] == "#{attr_name} " |
| 210 | add humanized_attributes[attr_name], message[(attr_name.size + 1)..-1] |
| 211 | end |
| 212 | end |
| 213 | |
| 214 | add_to_base message if attr_message.nil? |
| 215 | end |
| 216 | end |
| 217 | end |
| 218 | |
| 219 | # Module to allow validation of ActiveResource objects, which creates an Errors instance for every resource. |
| 220 | # Methods are implemented by overriding +Base#validate+ or its variants Each of these methods can inspect |
| 221 | # the state of the object, which usually means ensuring that a number of attributes have a certain value |
| 222 | # (such as not empty, within a given range, matching a certain regular expression and so on). |
| 223 | # |
| 224 | # ==== Example |
| 225 | # |
| 226 | # class Person < ActiveResource::Base |
| 227 | # self.site = "http://www.localhost.com:3000/" |
| 228 | # protected |
| 229 | # def validate |
| 230 | # errors.add_on_empty %w( first_name last_name ) |
| 231 | # errors.add("phone_number", "has invalid format") unless phone_number =~ /[0-9]*/ |
| 232 | # end |
| 233 | # |
| 234 | # def validate_on_create # is only run the first time a new object is saved |
| 235 | # unless valid_member?(self) |
| 236 | # errors.add("membership_discount", "has expired") |
| 237 | # end |
| 238 | # end |
| 239 | # |
| 240 | # def validate_on_update |
| 241 | # errors.add_to_base("No changes have occurred") if unchanged_attributes? |
| 242 | # end |
| 243 | # end |
| 244 | # |
| 245 | # person = Person.new("first_name" => "Jim", "phone_number" => "I will not tell you.") |
| 246 | # person.save # => false (and doesn't do the save) |
| 247 | # person.errors.empty? # => false |
| 248 | # person.errors.count # => 2 |
| 249 | # person.errors.on "last_name" # => "can't be empty" |
| 250 | # person.attributes = { "last_name" => "Halpert", "phone_number" => "555-5555" } |
| 251 | # person.save # => true (and person is now saved to the remote service) |
| 252 | # |
| 253 | module Validations |
| 254 | def self.included(base) # :nodoc: |
| 255 | base.class_eval do |
| 256 | alias_method_chain :save, :validation |
| 257 | end |
| 258 | end |
| 259 | |
| 260 | # Validate a resource and save (POST) it to the remote web service. |
| 261 | def save_with_validation |
| 262 | save_without_validation |
| 263 | true |
| 264 | rescue ResourceInvalid => error |
| 265 | errors.from_xml(error.response.body) |
| 266 | false |
| 267 | end |
| 268 | |
| 269 | # Checks for errors on an object (i.e., is resource.errors empty?). |
| 270 | # |
| 271 | # ==== Examples |
| 272 | # my_person = Person.create(params[:person]) |
| 273 | # my_person.valid? |
| 274 | # # => true |
| 275 | # |
| 276 | # my_person.errors.add('login', 'can not be empty') if my_person.login == '' |
| 277 | # my_person.valid? |
| 278 | # # => false |
| 279 | def valid? |
| 280 | errors.empty? |
| 281 | end |
| 282 | |
| 283 | # Returns the Errors object that holds all information about attribute error messages. |
| 284 | def errors |
| 285 | @errors ||= Errors.new(self) |
| 286 | end |
| 287 | end |
| 288 | end |
Note: See TracBrowser for help on using the browser.