activeresource 2.0.5 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activeresource might be problematic. Click here for more details.
- data/CHANGELOG +6 -3
- data/Rakefile +4 -4
- data/lib/active_resource/base.rb +257 -138
- data/lib/active_resource/connection.rb +41 -6
- data/lib/active_resource/custom_methods.rb +7 -7
- data/lib/active_resource/formats/xml_format.rb +1 -1
- data/lib/active_resource/http_mock.rb +76 -5
- data/lib/active_resource/validations.rb +2 -2
- data/lib/active_resource/version.rb +2 -2
- data/test/abstract_unit.rb +13 -1
- data/test/authorization_test.rb +42 -1
- data/test/base/custom_methods_test.rb +6 -3
- data/test/base/equality_test.rb +1 -1
- data/test/base/load_test.rb +1 -1
- data/test/base_errors_test.rb +1 -1
- data/test/base_test.rb +364 -27
- data/test/connection_test.rb +22 -1
- data/test/format_test.rb +2 -2
- data/test/setter_trap.rb +1 -1
- metadata +4 -5
data/CHANGELOG
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
-
*2.0
|
1
|
+
*2.1.0 (May 31st, 2008)*
|
2
2
|
|
3
|
-
*
|
3
|
+
* Fixed response logging to use length instead of the entire thing (seangeo) [#27]
|
4
4
|
|
5
|
+
* Fixed that to_param should be used and honored instead of hardcoding the id #11406 [gspiers]
|
5
6
|
|
6
|
-
*
|
7
|
+
* Improve documentation. [Radar, Jan De Poorter, chuyeow, xaviershay, danger, miloops, Xavier Noria, Sunny Ripert]
|
8
|
+
|
9
|
+
* Use HEAD instead of GET in exists? [bscofield]
|
7
10
|
|
8
11
|
* Fix small documentation typo. Closes #10670 [l.guidi]
|
9
12
|
|
data/Rakefile
CHANGED
@@ -5,7 +5,7 @@ require 'rake/rdoctask'
|
|
5
5
|
require 'rake/packagetask'
|
6
6
|
require 'rake/gempackagetask'
|
7
7
|
require 'rake/contrib/sshpublisher'
|
8
|
-
|
8
|
+
|
9
9
|
require File.join(File.dirname(__FILE__), 'lib', 'active_resource', 'version')
|
10
10
|
|
11
11
|
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
@@ -64,7 +64,7 @@ spec = Gem::Specification.new do |s|
|
|
64
64
|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
65
65
|
end
|
66
66
|
|
67
|
-
s.add_dependency('activesupport', '= 2.0
|
67
|
+
s.add_dependency('activesupport', '= 2.1.0' + PKG_BUILD)
|
68
68
|
|
69
69
|
s.require_path = 'lib'
|
70
70
|
s.autorequire = 'active_resource'
|
@@ -114,8 +114,8 @@ end
|
|
114
114
|
|
115
115
|
desc "Publish the beta gem"
|
116
116
|
task :pgem => [:package] do
|
117
|
-
Rake::SshFilePublisher.new("
|
118
|
-
`ssh
|
117
|
+
Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
118
|
+
`ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
|
119
119
|
end
|
120
120
|
|
121
121
|
desc "Publish the API documentation"
|
data/lib/active_resource/base.rb
CHANGED
@@ -14,12 +14,19 @@ module ActiveResource
|
|
14
14
|
# Person maps to the resources people, very similarly to Active Record) and a +site+ value, which holds the
|
15
15
|
# URI of the resources.
|
16
16
|
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
17
|
+
# class Person < ActiveResource::Base
|
18
|
+
# self.site = "http://api.people.com:3000/"
|
19
|
+
# end
|
20
20
|
#
|
21
21
|
# Now the Person class is mapped to RESTful resources located at <tt>http://api.people.com:3000/people/</tt>, and
|
22
|
-
# you can now use Active Resource's lifecycles methods to manipulate resources.
|
22
|
+
# you can now use Active Resource's lifecycles methods to manipulate resources. In the case where you already have
|
23
|
+
# an existing model with the same name as the desired RESTful resource you can set the +element_name+ value.
|
24
|
+
#
|
25
|
+
# class PersonResource < ActiveResource::Base
|
26
|
+
# self.site = "http://api.people.com:3000/"
|
27
|
+
# self.element_name = "person"
|
28
|
+
# end
|
29
|
+
#
|
23
30
|
#
|
24
31
|
# == Lifecycle methods
|
25
32
|
#
|
@@ -27,18 +34,18 @@ module ActiveResource
|
|
27
34
|
# from REST web services.
|
28
35
|
#
|
29
36
|
# ryan = Person.new(:first => 'Ryan', :last => 'Daigle')
|
30
|
-
# ryan.save
|
31
|
-
# ryan.id
|
32
|
-
# Person.exists?(ryan.id)
|
33
|
-
# ryan.exists?
|
37
|
+
# ryan.save # => true
|
38
|
+
# ryan.id # => 2
|
39
|
+
# Person.exists?(ryan.id) # => true
|
40
|
+
# ryan.exists? # => true
|
34
41
|
#
|
35
42
|
# ryan = Person.find(1)
|
36
|
-
# #
|
43
|
+
# # Resource holding our newly created Person object
|
37
44
|
#
|
38
45
|
# ryan.first = 'Rizzle'
|
39
|
-
# ryan.save
|
46
|
+
# ryan.save # => true
|
40
47
|
#
|
41
|
-
# ryan.destroy
|
48
|
+
# ryan.destroy # => true
|
42
49
|
#
|
43
50
|
# As you can see, these are very similar to Active Record's lifecycle methods for database records.
|
44
51
|
# You can read more about each of these methods in their respective documentation.
|
@@ -72,29 +79,39 @@ module ActiveResource
|
|
72
79
|
#
|
73
80
|
# You can validate resources client side by overriding validation methods in the base class.
|
74
81
|
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
+
# class Person < ActiveResource::Base
|
83
|
+
# self.site = "http://api.people.com:3000/"
|
84
|
+
# protected
|
85
|
+
# def validate
|
86
|
+
# errors.add("last", "has invalid characters") unless last =~ /[a-zA-Z]*/
|
87
|
+
# end
|
88
|
+
# end
|
82
89
|
#
|
83
90
|
# See the ActiveResource::Validations documentation for more information.
|
84
91
|
#
|
85
92
|
# == Authentication
|
86
93
|
#
|
87
94
|
# Many REST APIs will require authentication, usually in the form of basic
|
88
|
-
# HTTP authentication. Authentication can be specified by
|
89
|
-
#
|
95
|
+
# HTTP authentication. Authentication can be specified by:
|
96
|
+
# * putting the credentials in the URL for the +site+ variable.
|
90
97
|
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
98
|
+
# class Person < ActiveResource::Base
|
99
|
+
# self.site = "http://ryan:password@api.people.com:3000/"
|
100
|
+
# end
|
94
101
|
#
|
102
|
+
# * defining +user+ and/or +password+ variables
|
103
|
+
#
|
104
|
+
# class Person < ActiveResource::Base
|
105
|
+
# self.site = "http://api.people.com:3000/"
|
106
|
+
# self.user = "ryan"
|
107
|
+
# self.password = "password"
|
108
|
+
# end
|
109
|
+
#
|
95
110
|
# For obvious security reasons, it is probably best if such services are available
|
96
111
|
# over HTTPS.
|
97
112
|
#
|
113
|
+
# Note: Some values cannot be provided in the URL passed to site. e.g. email addresses
|
114
|
+
# as usernames. In those situations you should use the seperate user and password option.
|
98
115
|
# == Errors & Validation
|
99
116
|
#
|
100
117
|
# Error handling and validation is handled in much the same manner as you're used to seeing in
|
@@ -108,18 +125,17 @@ module ActiveResource
|
|
108
125
|
# exception.
|
109
126
|
#
|
110
127
|
# # GET http://api.people.com:3000/people/999.xml
|
111
|
-
# ryan = Person.find(999) #
|
112
|
-
# # => Response = 404
|
128
|
+
# ryan = Person.find(999) # 404, raises ActiveResource::ResourceNotFound
|
113
129
|
#
|
114
|
-
# <tt>404</tt> is just one of the HTTP error response codes that
|
130
|
+
# <tt>404</tt> is just one of the HTTP error response codes that Active Resource will handle with its own exception. The
|
115
131
|
# following HTTP response codes will also result in these exceptions:
|
116
132
|
#
|
117
|
-
# 200 -
|
118
|
-
# 404
|
119
|
-
# 409
|
120
|
-
# 422
|
121
|
-
# 401 -
|
122
|
-
# 500 -
|
133
|
+
# * 200..399 - Valid response, no exception
|
134
|
+
# * 404 - ActiveResource::ResourceNotFound
|
135
|
+
# * 409 - ActiveResource::ResourceConflict
|
136
|
+
# * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors)
|
137
|
+
# * 401..499 - ActiveResource::ClientError
|
138
|
+
# * 500..599 - ActiveResource::ServerError
|
123
139
|
#
|
124
140
|
# These custom exceptions allow you to deal with resource errors more naturally and with more precision
|
125
141
|
# rather than returning a general HTTP error. For example:
|
@@ -140,8 +156,8 @@ module ActiveResource
|
|
140
156
|
# then fail (with a <tt>false</tt> return value) and the validation errors can be accessed on the resource in question.
|
141
157
|
#
|
142
158
|
# ryan = Person.find(1)
|
143
|
-
# ryan.first
|
144
|
-
# ryan.save
|
159
|
+
# ryan.first # => ''
|
160
|
+
# ryan.save # => false
|
145
161
|
#
|
146
162
|
# # When
|
147
163
|
# # PUT http://api.people.com:3000/people/1.xml
|
@@ -151,19 +167,57 @@ module ActiveResource
|
|
151
167
|
# # <errors type="array"><error>First cannot be empty</error></errors>
|
152
168
|
# #
|
153
169
|
#
|
154
|
-
# ryan.errors.invalid?(:first)
|
155
|
-
# ryan.errors.full_messages
|
170
|
+
# ryan.errors.invalid?(:first) # => true
|
171
|
+
# ryan.errors.full_messages # => ['First cannot be empty']
|
156
172
|
#
|
157
173
|
# Learn more about Active Resource's validation features in the ActiveResource::Validations documentation.
|
158
174
|
#
|
175
|
+
# === Timeouts
|
176
|
+
#
|
177
|
+
# Active Resource relies on HTTP to access RESTful APIs and as such is inherently susceptible to slow or
|
178
|
+
# unresponsive servers. In such cases, your Active Resource method calls could timeout. You can control the
|
179
|
+
# amount of time before Active Resource times out with the +timeout+ variable.
|
180
|
+
#
|
181
|
+
# class Person < ActiveResource::Base
|
182
|
+
# self.site = "http://api.people.com:3000/"
|
183
|
+
# self.timeout = 5
|
184
|
+
# end
|
185
|
+
#
|
186
|
+
# This sets the +timeout+ to 5 seconds. You can adjust the +timeout+ to a value suitable for the RESTful API
|
187
|
+
# you are accessing. It is recommended to set this to a reasonably low value to allow your Active Resource
|
188
|
+
# clients (especially if you are using Active Resource in a Rails application) to fail-fast (see
|
189
|
+
# http://en.wikipedia.org/wiki/Fail-fast) rather than cause cascading failures that could incapacitate your
|
190
|
+
# server.
|
191
|
+
#
|
192
|
+
# When a timeout occurs, an ActiveResource::TimeoutError is raised. You should rescue from
|
193
|
+
# ActiveResource::TimeoutError in your Active Resource method calls.
|
194
|
+
#
|
195
|
+
# Internally, Active Resource relies on Ruby's Net::HTTP library to make HTTP requests. Setting +timeout+
|
196
|
+
# sets the <tt>read_timeout</tt> of the internal Net::HTTP instance to the same value. The default
|
197
|
+
# <tt>read_timeout</tt> is 60 seconds on most Ruby implementations.
|
159
198
|
class Base
|
160
199
|
# The logger for diagnosing and tracing Active Resource calls.
|
161
200
|
cattr_accessor :logger
|
162
201
|
|
163
202
|
class << self
|
164
203
|
# Gets the URI of the REST resources to map for this class. The site variable is required
|
165
|
-
#
|
204
|
+
# Active Resource's mapping to work.
|
166
205
|
def site
|
206
|
+
# Not using superclass_delegating_reader because don't want subclasses to modify superclass instance
|
207
|
+
#
|
208
|
+
# With superclass_delegating_reader
|
209
|
+
#
|
210
|
+
# Parent.site = 'http://anonymous@test.com'
|
211
|
+
# Subclass.site # => 'http://anonymous@test.com'
|
212
|
+
# Subclass.site.user = 'david'
|
213
|
+
# Parent.site # => 'http://david@test.com'
|
214
|
+
#
|
215
|
+
# Without superclass_delegating_reader (expected behaviour)
|
216
|
+
#
|
217
|
+
# Parent.site = 'http://anonymous@test.com'
|
218
|
+
# Subclass.site # => 'http://anonymous@test.com'
|
219
|
+
# Subclass.site.user = 'david' # => TypeError: can't modify frozen object
|
220
|
+
#
|
167
221
|
if defined?(@site)
|
168
222
|
@site
|
169
223
|
elsif superclass != Object && superclass.site
|
@@ -172,13 +226,51 @@ module ActiveResource
|
|
172
226
|
end
|
173
227
|
|
174
228
|
# Sets the URI of the REST resources to map for this class to the value in the +site+ argument.
|
175
|
-
# The site variable is required
|
229
|
+
# The site variable is required Active Resource's mapping to work.
|
176
230
|
def site=(site)
|
177
231
|
@connection = nil
|
178
|
-
|
232
|
+
if site.nil?
|
233
|
+
@site = nil
|
234
|
+
else
|
235
|
+
@site = create_site_uri_from(site)
|
236
|
+
@user = URI.decode(@site.user) if @site.user
|
237
|
+
@password = URI.decode(@site.password) if @site.password
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Gets the user for REST HTTP authentication.
|
242
|
+
def user
|
243
|
+
# Not using superclass_delegating_reader. See +site+ for explanation
|
244
|
+
if defined?(@user)
|
245
|
+
@user
|
246
|
+
elsif superclass != Object && superclass.user
|
247
|
+
superclass.user.dup.freeze
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Sets the user for REST HTTP authentication.
|
252
|
+
def user=(user)
|
253
|
+
@connection = nil
|
254
|
+
@user = user
|
179
255
|
end
|
180
256
|
|
181
|
-
#
|
257
|
+
# Gets the password for REST HTTP authentication.
|
258
|
+
def password
|
259
|
+
# Not using superclass_delegating_reader. See +site+ for explanation
|
260
|
+
if defined?(@password)
|
261
|
+
@password
|
262
|
+
elsif superclass != Object && superclass.password
|
263
|
+
superclass.password.dup.freeze
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# Sets the password for REST HTTP authentication.
|
268
|
+
def password=(password)
|
269
|
+
@connection = nil
|
270
|
+
@password = password
|
271
|
+
end
|
272
|
+
|
273
|
+
# Sets the format that attributes are sent and received in from a mime type reference:
|
182
274
|
#
|
183
275
|
# Person.format = :json
|
184
276
|
# Person.find(1) # => GET /people/1.json
|
@@ -186,7 +278,7 @@ module ActiveResource
|
|
186
278
|
# Person.format = ActiveResource::Formats::XmlFormat
|
187
279
|
# Person.find(1) # => GET /people/1.xml
|
188
280
|
#
|
189
|
-
# Default format is
|
281
|
+
# Default format is <tt>:xml</tt>.
|
190
282
|
def format=(mime_type_reference_or_format)
|
191
283
|
format = mime_type_reference_or_format.is_a?(Symbol) ?
|
192
284
|
ActiveResource::Formats[mime_type_reference_or_format] : mime_type_reference_or_format
|
@@ -194,18 +286,36 @@ module ActiveResource
|
|
194
286
|
write_inheritable_attribute("format", format)
|
195
287
|
connection.format = format if site
|
196
288
|
end
|
197
|
-
|
198
|
-
# Returns the current format, default is ActiveResource::Formats::XmlFormat
|
289
|
+
|
290
|
+
# Returns the current format, default is ActiveResource::Formats::XmlFormat.
|
199
291
|
def format # :nodoc:
|
200
292
|
read_inheritable_attribute("format") || ActiveResource::Formats[:xml]
|
201
293
|
end
|
202
294
|
|
295
|
+
# Sets the number of seconds after which requests to the REST API should time out.
|
296
|
+
def timeout=(timeout)
|
297
|
+
@connection = nil
|
298
|
+
@timeout = timeout
|
299
|
+
end
|
300
|
+
|
301
|
+
# Gets tthe number of seconds after which requests to the REST API should time out.
|
302
|
+
def timeout
|
303
|
+
if defined?(@timeout)
|
304
|
+
@timeout
|
305
|
+
elsif superclass != Object && superclass.timeout
|
306
|
+
superclass.timeout
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
203
310
|
# An instance of ActiveResource::Connection that is the base connection to the remote service.
|
204
311
|
# The +refresh+ parameter toggles whether or not the connection is refreshed at every request
|
205
312
|
# or not (defaults to <tt>false</tt>).
|
206
313
|
def connection(refresh = false)
|
207
314
|
if defined?(@connection) || superclass == Object
|
208
315
|
@connection = Connection.new(site, format) if refresh || @connection.nil?
|
316
|
+
@connection.user = user if user
|
317
|
+
@connection.password = password if password
|
318
|
+
@connection.timeout = timeout if timeout
|
209
319
|
@connection
|
210
320
|
else
|
211
321
|
superclass.connection
|
@@ -266,9 +376,9 @@ module ActiveResource
|
|
266
376
|
# will split from the prefix options.
|
267
377
|
#
|
268
378
|
# ==== Options
|
269
|
-
# +prefix_options
|
379
|
+
# +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
|
270
380
|
# would yield a URL like <tt>/accounts/19/purchases.xml</tt>).
|
271
|
-
# +query_options
|
381
|
+
# +query_options+ - A hash to add items to the query string for the request.
|
272
382
|
#
|
273
383
|
# ==== Examples
|
274
384
|
# Post.element_path(1)
|
@@ -285,16 +395,16 @@ module ActiveResource
|
|
285
395
|
#
|
286
396
|
def element_path(id, prefix_options = {}, query_options = nil)
|
287
397
|
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
|
288
|
-
"#{prefix(prefix_options)}#{collection_name}/#{id}.#{format.extension}#{query_string(query_options)}"
|
398
|
+
"#{prefix(prefix_options)}#{collection_name}/#{id}.#{format.extension}#{query_string(query_options)}"
|
289
399
|
end
|
290
400
|
|
291
401
|
# Gets the collection path for the REST resources. If the +query_options+ parameter is omitted, Rails
|
292
402
|
# will split from the +prefix_options+.
|
293
403
|
#
|
294
404
|
# ==== Options
|
295
|
-
# +prefix_options
|
296
|
-
#
|
297
|
-
# +query_options
|
405
|
+
# * +prefix_options+ - A hash to add a prefix to the request for nested URL's (e.g., <tt>:account_id => 19</tt>
|
406
|
+
# would yield a URL like <tt>/accounts/19/purchases.xml</tt>).
|
407
|
+
# * +query_options+ - A hash to add items to the query string for the request.
|
298
408
|
#
|
299
409
|
# ==== Examples
|
300
410
|
# Post.collection_path
|
@@ -330,39 +440,34 @@ module ActiveResource
|
|
330
440
|
# ==== Examples
|
331
441
|
# Person.create(:name => 'Jeremy', :email => 'myname@nospam.com', :enabled => true)
|
332
442
|
# my_person = Person.find(:first)
|
333
|
-
# my_person.email
|
334
|
-
# # => myname@nospam.com
|
443
|
+
# my_person.email # => myname@nospam.com
|
335
444
|
#
|
336
445
|
# dhh = Person.create(:name => 'David', :email => 'dhh@nospam.com', :enabled => true)
|
337
|
-
# dhh.valid?
|
338
|
-
# # =>
|
339
|
-
# dhh.new?
|
340
|
-
# # => false
|
446
|
+
# dhh.valid? # => true
|
447
|
+
# dhh.new? # => false
|
341
448
|
#
|
342
449
|
# # We'll assume that there's a validation that requires the name attribute
|
343
450
|
# that_guy = Person.create(:name => '', :email => 'thatguy@nospam.com', :enabled => true)
|
344
|
-
# that_guy.valid?
|
345
|
-
# # =>
|
346
|
-
# that_guy.new?
|
347
|
-
# # => true
|
348
|
-
#
|
451
|
+
# that_guy.valid? # => false
|
452
|
+
# that_guy.new? # => true
|
349
453
|
def create(attributes = {})
|
350
454
|
returning(self.new(attributes)) { |res| res.save }
|
351
455
|
end
|
352
456
|
|
353
|
-
# Core method for finding resources. Used similarly to Active Record's find method.
|
457
|
+
# Core method for finding resources. Used similarly to Active Record's +find+ method.
|
354
458
|
#
|
355
459
|
# ==== Arguments
|
356
460
|
# The first argument is considered to be the scope of the query. That is, how many
|
357
461
|
# resources are returned from the request. It can be one of the following.
|
358
462
|
#
|
359
|
-
#
|
360
|
-
#
|
361
|
-
#
|
463
|
+
# * <tt>:one</tt> - Returns a single resource.
|
464
|
+
# * <tt>:first</tt> - Returns the first resource found.
|
465
|
+
# * <tt>:all</tt> - Returns every resource that matches the request.
|
362
466
|
#
|
363
467
|
# ==== Options
|
364
|
-
#
|
365
|
-
#
|
468
|
+
#
|
469
|
+
# * <tt>:from</tt> - Sets the path or custom method that resources will be fetched from.
|
470
|
+
# * <tt>:params</tt> - Sets query and prefix (nested URL) parameters.
|
366
471
|
#
|
367
472
|
# ==== Examples
|
368
473
|
# Person.find(1)
|
@@ -409,19 +514,14 @@ module ActiveResource
|
|
409
514
|
# All options specify prefix and query parameters.
|
410
515
|
#
|
411
516
|
# ==== Examples
|
412
|
-
# Event.delete(2)
|
413
|
-
# # => DELETE /events/2
|
517
|
+
# Event.delete(2) # sends DELETE /events/2
|
414
518
|
#
|
415
519
|
# Event.create(:name => 'Free Concert', :location => 'Community Center')
|
416
|
-
# my_event = Event.find(:first)
|
417
|
-
# #
|
418
|
-
# Event.delete(my_event.id)
|
419
|
-
# # => DELETE /events/7
|
520
|
+
# my_event = Event.find(:first) # let's assume this is event with ID 7
|
521
|
+
# Event.delete(my_event.id) # sends DELETE /events/7
|
420
522
|
#
|
421
523
|
# # Let's assume a request to events/5/cancel.xml
|
422
|
-
# Event.delete(params[:id])
|
423
|
-
# # => DELETE /events/5
|
424
|
-
#
|
524
|
+
# Event.delete(params[:id]) # sends DELETE /events/5
|
425
525
|
def delete(id, options = {})
|
426
526
|
connection.delete(element_path(id, options))
|
427
527
|
end
|
@@ -430,13 +530,17 @@ module ActiveResource
|
|
430
530
|
#
|
431
531
|
# ==== Examples
|
432
532
|
# Note.create(:title => 'Hello, world.', :body => 'Nothing more for now...')
|
433
|
-
# Note.exists?(1)
|
434
|
-
# # => true
|
533
|
+
# Note.exists?(1) # => true
|
435
534
|
#
|
436
|
-
# Note.exists(1349)
|
437
|
-
# # => false
|
535
|
+
# Note.exists(1349) # => false
|
438
536
|
def exists?(id, options = {})
|
439
|
-
|
537
|
+
if id
|
538
|
+
prefix_options, query_options = split_options(options[:params])
|
539
|
+
path = element_path(id, prefix_options, query_options)
|
540
|
+
response = connection.head(path, headers)
|
541
|
+
response.code == 200
|
542
|
+
end
|
543
|
+
# id && !find_single(id, options).nil?
|
440
544
|
rescue ActiveResource::ResourceNotFound
|
441
545
|
false
|
442
546
|
end
|
@@ -518,7 +622,7 @@ module ActiveResource
|
|
518
622
|
attr_accessor :attributes #:nodoc:
|
519
623
|
attr_accessor :prefix_options #:nodoc:
|
520
624
|
|
521
|
-
# Constructor method for new resources; the optional +attributes+ parameter takes a
|
625
|
+
# Constructor method for new resources; the optional +attributes+ parameter takes a hash
|
522
626
|
# of attributes for the new resource.
|
523
627
|
#
|
524
628
|
# ==== Examples
|
@@ -535,20 +639,53 @@ module ActiveResource
|
|
535
639
|
load(attributes)
|
536
640
|
end
|
537
641
|
|
642
|
+
# Returns a clone of the resource that hasn't been assigned an +id+ yet and
|
643
|
+
# is treated as a new resource.
|
644
|
+
#
|
645
|
+
# ryan = Person.find(1)
|
646
|
+
# not_ryan = ryan.clone
|
647
|
+
# not_ryan.new? # => true
|
648
|
+
#
|
649
|
+
# Any active resource member attributes will NOT be cloned, though all other
|
650
|
+
# attributes are. This is to prevent the conflict between any +prefix_options+
|
651
|
+
# that refer to the original parent resource and the newly cloned parent
|
652
|
+
# resource that does not exist.
|
653
|
+
#
|
654
|
+
# ryan = Person.find(1)
|
655
|
+
# ryan.address = StreetAddress.find(1, :person_id => ryan.id)
|
656
|
+
# ryan.hash = {:not => "an ARes instance"}
|
657
|
+
#
|
658
|
+
# not_ryan = ryan.clone
|
659
|
+
# not_ryan.new? # => true
|
660
|
+
# not_ryan.address # => NoMethodError
|
661
|
+
# not_ryan.hash # => {:not => "an ARes instance"}
|
662
|
+
def clone
|
663
|
+
# Clone all attributes except the pk and any nested ARes
|
664
|
+
cloned = attributes.reject {|k,v| k == self.class.primary_key || v.is_a?(ActiveResource::Base)}.inject({}) do |attrs, (k, v)|
|
665
|
+
attrs[k] = v.clone
|
666
|
+
attrs
|
667
|
+
end
|
668
|
+
# Form the new resource - bypass initialize of resource with 'new' as that will call 'load' which
|
669
|
+
# attempts to convert hashes into member objects and arrays into collections of objects. We want
|
670
|
+
# the raw objects to be cloned so we bypass load by directly setting the attributes hash.
|
671
|
+
resource = self.class.new({})
|
672
|
+
resource.prefix_options = self.prefix_options
|
673
|
+
resource.send :instance_variable_set, '@attributes', cloned
|
674
|
+
resource
|
675
|
+
end
|
676
|
+
|
677
|
+
|
538
678
|
# A method to determine if the resource a new object (i.e., it has not been POSTed to the remote service yet).
|
539
679
|
#
|
540
680
|
# ==== Examples
|
541
681
|
# not_new = Computer.create(:brand => 'Apple', :make => 'MacBook', :vendor => 'MacMall')
|
542
|
-
# not_new.new?
|
543
|
-
# # => false
|
682
|
+
# not_new.new? # => false
|
544
683
|
#
|
545
684
|
# is_new = Computer.new(:brand => 'IBM', :make => 'Thinkpad', :vendor => 'IBM')
|
546
|
-
# is_new.new?
|
547
|
-
# # => true
|
685
|
+
# is_new.new? # => true
|
548
686
|
#
|
549
687
|
# is_new.save
|
550
|
-
# is_new.new?
|
551
|
-
# # => false
|
688
|
+
# is_new.new? # => false
|
552
689
|
#
|
553
690
|
def new?
|
554
691
|
id.nil?
|
@@ -564,13 +701,13 @@ module ActiveResource
|
|
564
701
|
attributes[self.class.primary_key] = id
|
565
702
|
end
|
566
703
|
|
567
|
-
# Allows
|
704
|
+
# Allows Active Resource objects to be used as parameters in Action Pack URL generation.
|
568
705
|
def to_param
|
569
706
|
id && id.to_s
|
570
707
|
end
|
571
708
|
|
572
709
|
# Test for equality. Resource are equal if and only if +other+ is the same object or
|
573
|
-
# is an instance of the same class, is not
|
710
|
+
# is an instance of the same class, is not <tt>new?</tt>, and has the same +id+.
|
574
711
|
#
|
575
712
|
# ==== Examples
|
576
713
|
# ryan = Person.create(:name => 'Ryan')
|
@@ -611,17 +748,13 @@ module ActiveResource
|
|
611
748
|
# ==== Examples
|
612
749
|
# my_invoice = Invoice.create(:customer => 'That Company')
|
613
750
|
# next_invoice = my_invoice.dup
|
614
|
-
# next_invoice.new?
|
615
|
-
# # => true
|
751
|
+
# next_invoice.new? # => true
|
616
752
|
#
|
617
753
|
# next_invoice.save
|
618
|
-
# next_invoice == my_invoice
|
619
|
-
# # => false (different id attributes)
|
754
|
+
# next_invoice == my_invoice # => false (different id attributes)
|
620
755
|
#
|
621
|
-
# my_invoice.customer
|
622
|
-
# # => That Company
|
623
|
-
# next_invoice.customer
|
624
|
-
# # => That Company
|
756
|
+
# my_invoice.customer # => That Company
|
757
|
+
# next_invoice.customer # => That Company
|
625
758
|
def dup
|
626
759
|
returning self.class.new do |resource|
|
627
760
|
resource.attributes = @attributes
|
@@ -636,16 +769,12 @@ module ActiveResource
|
|
636
769
|
#
|
637
770
|
# ==== Examples
|
638
771
|
# my_company = Company.new(:name => 'RoleModel Software', :owner => 'Ken Auer', :size => 2)
|
639
|
-
# my_company.new?
|
640
|
-
# #
|
641
|
-
# my_company.save
|
642
|
-
# # => POST /companies/ (create)
|
772
|
+
# my_company.new? # => true
|
773
|
+
# my_company.save # sends POST /companies/ (create)
|
643
774
|
#
|
644
|
-
# my_company.new?
|
645
|
-
# # => false
|
775
|
+
# my_company.new? # => false
|
646
776
|
# my_company.size = 10
|
647
|
-
# my_company.save
|
648
|
-
# # => PUT /companies/1 (update)
|
777
|
+
# my_company.save # sends PUT /companies/1 (update)
|
649
778
|
def save
|
650
779
|
new? ? create : update
|
651
780
|
end
|
@@ -656,20 +785,17 @@ module ActiveResource
|
|
656
785
|
# my_id = 3
|
657
786
|
# my_person = Person.find(my_id)
|
658
787
|
# my_person.destroy
|
659
|
-
# Person.find(my_id)
|
660
|
-
# # => 404 (Resource Not Found)
|
788
|
+
# Person.find(my_id) # 404 (Resource Not Found)
|
661
789
|
#
|
662
790
|
# new_person = Person.create(:name => 'James')
|
663
|
-
# new_id = new_person.id
|
664
|
-
# # => 7
|
791
|
+
# new_id = new_person.id # => 7
|
665
792
|
# new_person.destroy
|
666
|
-
# Person.find(new_id)
|
667
|
-
# # => 404 (Resource Not Found)
|
793
|
+
# Person.find(new_id) # 404 (Resource Not Found)
|
668
794
|
def destroy
|
669
795
|
connection.delete(element_path, self.class.headers)
|
670
796
|
end
|
671
797
|
|
672
|
-
# Evaluates to <tt>true</tt> if this resource is not
|
798
|
+
# Evaluates to <tt>true</tt> if this resource is not <tt>new?</tt> and is
|
673
799
|
# found on the remote service. Using this method, you can check for
|
674
800
|
# resources that may have been deleted between the object's instantiation
|
675
801
|
# and actions on it.
|
@@ -677,19 +803,16 @@ module ActiveResource
|
|
677
803
|
# ==== Examples
|
678
804
|
# Person.create(:name => 'Theodore Roosevelt')
|
679
805
|
# that_guy = Person.find(:first)
|
680
|
-
# that_guy.exists?
|
681
|
-
# # => true
|
806
|
+
# that_guy.exists? # => true
|
682
807
|
#
|
683
808
|
# that_lady = Person.new(:name => 'Paul Bean')
|
684
|
-
# that_lady.exists?
|
685
|
-
# # => false
|
809
|
+
# that_lady.exists? # => false
|
686
810
|
#
|
687
811
|
# guys_id = that_guy.id
|
688
812
|
# Person.delete(guys_id)
|
689
|
-
# that_guy.exists?
|
690
|
-
# # => false
|
813
|
+
# that_guy.exists? # => false
|
691
814
|
def exists?
|
692
|
-
!new? && self.class.exists?(
|
815
|
+
!new? && self.class.exists?(to_param, :params => prefix_options)
|
693
816
|
end
|
694
817
|
|
695
818
|
# A method to convert the the resource to an XML string.
|
@@ -697,13 +820,13 @@ module ActiveResource
|
|
697
820
|
# ==== Options
|
698
821
|
# The +options+ parameter is handed off to the +to_xml+ method on each
|
699
822
|
# attribute, so it has the same options as the +to_xml+ methods in
|
700
|
-
#
|
823
|
+
# Active Support.
|
701
824
|
#
|
702
|
-
# indent
|
703
|
-
# dasherize
|
704
|
-
#
|
705
|
-
# skip_instruct
|
706
|
-
#
|
825
|
+
# * <tt>:indent</tt> - Set the indent level for the XML output (default is +2+).
|
826
|
+
# * <tt>:dasherize</tt> - Boolean option to determine whether or not element names should
|
827
|
+
# replace underscores with dashes (default is <tt>false</tt>).
|
828
|
+
# * <tt>:skip_instruct</tt> - Toggle skipping the +instruct!+ call on the XML builder
|
829
|
+
# that generates the XML declaration (default is <tt>false</tt>).
|
707
830
|
#
|
708
831
|
# ==== Examples
|
709
832
|
# my_group = SubsidiaryGroup.find(:first)
|
@@ -725,30 +848,26 @@ module ActiveResource
|
|
725
848
|
#
|
726
849
|
# ==== Examples
|
727
850
|
# my_branch = Branch.find(:first)
|
728
|
-
# my_branch.name
|
729
|
-
# # => Wislon Raod
|
851
|
+
# my_branch.name # => "Wislon Raod"
|
730
852
|
#
|
731
853
|
# # Another client fixes the typo...
|
732
854
|
#
|
733
|
-
# my_branch.name
|
734
|
-
# # => Wislon Raod
|
855
|
+
# my_branch.name # => "Wislon Raod"
|
735
856
|
# my_branch.reload
|
736
|
-
# my_branch.name
|
737
|
-
# # => Wilson Road
|
857
|
+
# my_branch.name # => "Wilson Road"
|
738
858
|
def reload
|
739
|
-
self.load(self.class.find(
|
859
|
+
self.load(self.class.find(to_param, :params => @prefix_options).attributes)
|
740
860
|
end
|
741
861
|
|
742
862
|
# A method to manually load attributes from a hash. Recursively loads collections of
|
743
|
-
# resources. This method is called in initialize and create when a
|
863
|
+
# resources. This method is called in +initialize+ and +create+ when a hash of attributes
|
744
864
|
# is provided.
|
745
865
|
#
|
746
866
|
# ==== Examples
|
747
867
|
# my_attrs = {:name => 'J&J Textiles', :industry => 'Cloth and textiles'}
|
748
868
|
#
|
749
869
|
# the_supplier = Supplier.find(:first)
|
750
|
-
# the_supplier.name
|
751
|
-
# # => 'J&M Textiles'
|
870
|
+
# the_supplier.name # => 'J&M Textiles'
|
752
871
|
# the_supplier.load(my_attrs)
|
753
872
|
# the_supplier.name('J&J Textiles')
|
754
873
|
#
|
@@ -779,10 +898,10 @@ module ActiveResource
|
|
779
898
|
self
|
780
899
|
end
|
781
900
|
|
782
|
-
# For checking respond_to
|
901
|
+
# For checking <tt>respond_to?</tt> without searching the attributes (which is faster).
|
783
902
|
alias_method :respond_to_without_attributes?, :respond_to?
|
784
903
|
|
785
|
-
# A method to determine if an object responds to a message (e.g., a method call). In Active Resource, a
|
904
|
+
# A method to determine if an object responds to a message (e.g., a method call). In Active Resource, a Person object with a
|
786
905
|
# +name+ attribute can answer <tt>true</tt> to <tt>my_person.respond_to?("name")</tt>, <tt>my_person.respond_to?("name=")</tt>, and
|
787
906
|
# <tt>my_person.respond_to?("name?")</tt>.
|
788
907
|
def respond_to?(method, include_priv = false)
|
@@ -832,7 +951,7 @@ module ActiveResource
|
|
832
951
|
end
|
833
952
|
|
834
953
|
def element_path(options = nil)
|
835
|
-
self.class.element_path(
|
954
|
+
self.class.element_path(to_param, options || prefix_options)
|
836
955
|
end
|
837
956
|
|
838
957
|
def collection_path(options = nil)
|