Froze rails gems
[depot.git] / vendor / rails / activeresource / lib / active_resource / validations.rb
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 support validation and errors with Active Resource objects. The module overrides
220 # Base#save to rescue ActiveResource::ResourceInvalid exceptions and parse the errors returned
221 # in the web service response. The module also adds an +errors+ collection that mimics the interface
222 # of the errors provided by ActiveRecord::Errors.
223 #
224 # ==== Example
225 #
226 # Consider a Person resource on the server requiring both a +first_name+ and a +last_name+ with a
227 # <tt>validates_presence_of :first_name, :last_name</tt> declaration in the model:
228 #
229 # person = Person.new(:first_name => "Jim", :last_name => "")
230 # person.save # => false (server returns an HTTP 422 status code and errors)
231 # person.valid? # => false
232 # person.errors.empty? # => false
233 # person.errors.count # => 1
234 # person.errors.full_messages # => ["Last name can't be empty"]
235 # person.errors.on(:last_name) # => "can't be empty"
236 # person.last_name = "Halpert"
237 # person.save # => true (and person is now saved to the remote service)
238 #
239 module Validations
240 def self.included(base) # :nodoc:
241 base.class_eval do
242 alias_method_chain :save, :validation
243 end
244 end
245
246 # Validate a resource and save (POST) it to the remote web service.
247 def save_with_validation
248 save_without_validation
249 true
250 rescue ResourceInvalid => error
251 errors.from_xml(error.response.body)
252 false
253 end
254
255 # Checks for errors on an object (i.e., is resource.errors empty?).
256 #
257 # ==== Examples
258 # my_person = Person.create(params[:person])
259 # my_person.valid?
260 # # => true
261 #
262 # my_person.errors.add('login', 'can not be empty') if my_person.login == ''
263 # my_person.valid?
264 # # => false
265 def valid?
266 errors.empty?
267 end
268
269 # Returns the Errors object that holds all information about attribute error messages.
270 def errors
271 @errors ||= Errors.new(self)
272 end
273 end
274 end