activeresource-five 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,32 @@
1
+ module ActiveResource::Associations::Builder
2
+ class Association #:nodoc:
3
+
4
+ # providing a Class-Variable, which will have a different store of subclasses
5
+ class_attribute :valid_options
6
+ self.valid_options = [:class_name]
7
+
8
+ # would identify subclasses of association
9
+ class_attribute :macro
10
+
11
+ attr_reader :model, :name, :options, :klass
12
+
13
+ def self.build(model, name, options)
14
+ new(model, name, options).build
15
+ end
16
+
17
+ def initialize(model, name, options)
18
+ @model, @name, @options = model, name, options
19
+ end
20
+
21
+ def build
22
+ validate_options
23
+ model.create_reflection(self.class.macro, name, options)
24
+ end
25
+
26
+ private
27
+
28
+ def validate_options
29
+ options.assert_valid_keys(self.class.valid_options)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,14 @@
1
+ module ActiveResource::Associations::Builder
2
+ class BelongsTo < Association
3
+ self.valid_options += [:foreign_key]
4
+
5
+ self.macro = :belongs_to
6
+
7
+ def build
8
+ validate_options
9
+ reflection = model.create_reflection(self.class.macro, name, options)
10
+ model.defines_belongs_to_finder_method(reflection)
11
+ return reflection
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ module ActiveResource::Associations::Builder
2
+ class HasMany < Association
3
+ self.macro = :has_many
4
+
5
+ def build
6
+ validate_options
7
+ model.create_reflection(self.class.macro, name, options).tap do |reflection|
8
+ model.defines_has_many_finder_method(reflection)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module ActiveResource::Associations::Builder
2
+ class HasOne < Association
3
+ self.macro = :has_one
4
+
5
+ def build
6
+ validate_options
7
+ model.create_reflection(self.class.macro, name, options).tap do |reflection|
8
+ model.defines_has_one_finder_method(reflection)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,1626 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/class/attribute_accessors'
3
+ require 'active_support/core_ext/class/attribute'
4
+ require 'active_support/core_ext/hash/indifferent_access'
5
+ require 'active_support/core_ext/kernel/reporting'
6
+ require 'active_support/core_ext/module/delegation'
7
+ require 'active_support/core_ext/module/aliasing'
8
+ require 'active_support/core_ext/object/blank'
9
+ require 'active_support/core_ext/object/to_query'
10
+ require 'active_support/core_ext/object/duplicable'
11
+ require 'set'
12
+ require 'uri'
13
+
14
+ require 'active_support/core_ext/uri'
15
+ require 'active_resource/connection'
16
+ require 'active_resource/formats'
17
+ require 'active_resource/schema'
18
+ require 'active_resource/log_subscriber'
19
+ require 'active_resource/associations'
20
+ require 'active_resource/reflection'
21
+ require 'active_resource/threadsafe_attributes'
22
+
23
+ require 'active_model/serializers/xml'
24
+
25
+ module ActiveResource
26
+ # ActiveResource::Base is the main class for mapping RESTful resources as models in a Rails application.
27
+ #
28
+ # For an outline of what Active Resource is capable of, see its {README}[link:files/activeresource/README_rdoc.html].
29
+ #
30
+ # == Automated mapping
31
+ #
32
+ # Active Resource objects represent your RESTful resources as manipulatable Ruby objects. To map resources
33
+ # to Ruby objects, Active Resource only needs a class name that corresponds to the resource name (e.g., the class
34
+ # Person maps to the resources people, very similarly to Active Record) and a +site+ value, which holds the
35
+ # URI of the resources.
36
+ #
37
+ # class Person < ActiveResource::Base
38
+ # self.site = "https://api.people.com"
39
+ # end
40
+ #
41
+ # Now the Person class is mapped to RESTful resources located at <tt>https://api.people.com/people/</tt>, and
42
+ # you can now use Active Resource's life cycle methods to manipulate resources. In the case where you already have
43
+ # an existing model with the same name as the desired RESTful resource you can set the +element_name+ value.
44
+ #
45
+ # class PersonResource < ActiveResource::Base
46
+ # self.site = "https://api.people.com"
47
+ # self.element_name = "person"
48
+ # end
49
+ #
50
+ # If your Active Resource object is required to use an HTTP proxy you can set the +proxy+ value which holds a URI.
51
+ #
52
+ # class PersonResource < ActiveResource::Base
53
+ # self.site = "https://api.people.com"
54
+ # self.proxy = "https://user:password@proxy.people.com:8080"
55
+ # end
56
+ #
57
+ #
58
+ # == Life cycle methods
59
+ #
60
+ # Active Resource exposes methods for creating, finding, updating, and deleting resources
61
+ # from REST web services.
62
+ #
63
+ # ryan = Person.new(:first => 'Ryan', :last => 'Daigle')
64
+ # ryan.save # => true
65
+ # ryan.id # => 2
66
+ # Person.exists?(ryan.id) # => true
67
+ # ryan.exists? # => true
68
+ #
69
+ # ryan = Person.find(1)
70
+ # # Resource holding our newly created Person object
71
+ #
72
+ # ryan.first = 'Rizzle'
73
+ # ryan.save # => true
74
+ #
75
+ # ryan.destroy # => true
76
+ #
77
+ # As you can see, these are very similar to Active Record's life cycle methods for database records.
78
+ # You can read more about each of these methods in their respective documentation.
79
+ #
80
+ # === Custom REST methods
81
+ #
82
+ # Since simple CRUD/life cycle methods can't accomplish every task, Active Resource also supports
83
+ # defining your own custom REST methods. To invoke them, Active Resource provides the <tt>get</tt>,
84
+ # <tt>post</tt>, <tt>put</tt> and <tt>delete</tt> methods where you can specify a custom REST method
85
+ # name to invoke.
86
+ #
87
+ # # POST to the custom 'register' REST method, i.e. POST /people/new/register.json.
88
+ # Person.new(:name => 'Ryan').post(:register)
89
+ # # => { :id => 1, :name => 'Ryan', :position => 'Clerk' }
90
+ #
91
+ # # PUT an update by invoking the 'promote' REST method, i.e. PUT /people/1/promote.json?position=Manager.
92
+ # Person.find(1).put(:promote, :position => 'Manager')
93
+ # # => { :id => 1, :name => 'Ryan', :position => 'Manager' }
94
+ #
95
+ # # GET all the positions available, i.e. GET /people/positions.json.
96
+ # Person.get(:positions)
97
+ # # => [{:name => 'Manager'}, {:name => 'Clerk'}]
98
+ #
99
+ # # DELETE to 'fire' a person, i.e. DELETE /people/1/fire.json.
100
+ # Person.find(1).delete(:fire)
101
+ #
102
+ # For more information on using custom REST methods, see the
103
+ # ActiveResource::CustomMethods documentation.
104
+ #
105
+ # == Validations
106
+ #
107
+ # You can validate resources client side by overriding validation methods in the base class.
108
+ #
109
+ # class Person < ActiveResource::Base
110
+ # self.site = "https://api.people.com"
111
+ # protected
112
+ # def validate
113
+ # errors.add("last", "has invalid characters") unless last =~ /[a-zA-Z]*/
114
+ # end
115
+ # end
116
+ #
117
+ # See the ActiveResource::Validations documentation for more information.
118
+ #
119
+ # == Authentication
120
+ #
121
+ # Many REST APIs require authentication. The HTTP spec describes two ways to
122
+ # make requests with a username and password (see RFC 2617).
123
+ #
124
+ # Basic authentication simply sends a username and password along with HTTP
125
+ # requests. These sensitive credentials are sent unencrypted, visible to
126
+ # any onlooker, so this scheme should only be used with SSL.
127
+ #
128
+ # Digest authentication sends a crytographic hash of the username, password,
129
+ # HTTP method, URI, and a single-use secret key provided by the server.
130
+ # Sensitive credentials aren't visible to onlookers, so digest authentication
131
+ # doesn't require SSL. However, this doesn't mean the connection is secure!
132
+ # Just the username and password.
133
+ #
134
+ # (You really, really want to use SSL. There's little reason not to.)
135
+ #
136
+ # === Picking an authentication scheme
137
+ #
138
+ # Basic authentication is the default. To switch to digest authentication,
139
+ # set +auth_type+ to +:digest+:
140
+ #
141
+ # class Person < ActiveResource::Base
142
+ # self.auth_type = :digest
143
+ # end
144
+ #
145
+ # === Setting the username and password
146
+ #
147
+ # Set +user+ and +password+ on the class, or include them in the +site+ URL.
148
+ #
149
+ # class Person < ActiveResource::Base
150
+ # # Set user and password directly:
151
+ # self.user = "ryan"
152
+ # self.password = "password"
153
+ #
154
+ # # Or include them in the site:
155
+ # self.site = "https://ryan:password@api.people.com"
156
+ # end
157
+ #
158
+ # === Certificate Authentication
159
+ #
160
+ # You can also authenticate using an X509 certificate. <tt>See ssl_options=</tt> for all options.
161
+ #
162
+ # class Person < ActiveResource::Base
163
+ # self.site = "https://secure.api.people.com/"
164
+ #
165
+ # File.open(pem_file_path, 'rb') do |pem_file|
166
+ # self.ssl_options = {
167
+ # cert: OpenSSL::X509::Certificate.new(pem_file),
168
+ # key: OpenSSL::PKey::RSA.new(pem_file),
169
+ # ca_path: "/path/to/OpenSSL/formatted/CA_Certs",
170
+ # verify_mode: OpenSSL::SSL::VERIFY_PEER }
171
+ # end
172
+ # end
173
+ #
174
+ #
175
+ # == Errors & Validation
176
+ #
177
+ # Error handling and validation is handled in much the same manner as you're used to seeing in
178
+ # Active Record. Both the response code in the HTTP response and the body of the response are used to
179
+ # indicate that an error occurred.
180
+ #
181
+ # === Resource errors
182
+ #
183
+ # When a GET is requested for a resource that does not exist, the HTTP <tt>404</tt> (Resource Not Found)
184
+ # response code will be returned from the server which will raise an ActiveResource::ResourceNotFound
185
+ # exception.
186
+ #
187
+ # # GET https://api.people.com/people/999.json
188
+ # ryan = Person.find(999) # 404, raises ActiveResource::ResourceNotFound
189
+ #
190
+ #
191
+ # <tt>404</tt> is just one of the HTTP error response codes that Active Resource will handle with its own exception. The
192
+ # following HTTP response codes will also result in these exceptions:
193
+ #
194
+ # * 200..399 - Valid response. No exceptions, other than these redirects:
195
+ # * 301, 302, 303, 307 - ActiveResource::Redirection
196
+ # * 400 - ActiveResource::BadRequest
197
+ # * 401 - ActiveResource::UnauthorizedAccess
198
+ # * 403 - ActiveResource::ForbiddenAccess
199
+ # * 404 - ActiveResource::ResourceNotFound
200
+ # * 405 - ActiveResource::MethodNotAllowed
201
+ # * 409 - ActiveResource::ResourceConflict
202
+ # * 410 - ActiveResource::ResourceGone
203
+ # * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors)
204
+ # * 401..499 - ActiveResource::ClientError
205
+ # * 500..599 - ActiveResource::ServerError
206
+ # * Other - ActiveResource::ConnectionError
207
+ #
208
+ # These custom exceptions allow you to deal with resource errors more naturally and with more precision
209
+ # rather than returning a general HTTP error. For example:
210
+ #
211
+ # begin
212
+ # ryan = Person.find(my_id)
213
+ # rescue ActiveResource::ResourceNotFound
214
+ # redirect_to :action => 'not_found'
215
+ # rescue ActiveResource::ResourceConflict, ActiveResource::ResourceInvalid
216
+ # redirect_to :action => 'new'
217
+ # end
218
+ #
219
+ # When a GET is requested for a nested resource and you don't provide the prefix_param
220
+ # an ActiveResource::MissingPrefixParam will be raised.
221
+ #
222
+ # class Comment < ActiveResource::Base
223
+ # self.site = "https://someip.com/posts/:post_id"
224
+ # end
225
+ #
226
+ # Comment.find(1)
227
+ # # => ActiveResource::MissingPrefixParam: post_id prefix_option is missing
228
+ #
229
+ # === Validation errors
230
+ #
231
+ # Active Resource supports validations on resources and will return errors if any of these validations fail
232
+ # (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by
233
+ # a response code of <tt>422</tt> and an JSON or XML representation of the validation errors. The save operation will
234
+ # then fail (with a <tt>false</tt> return value) and the validation errors can be accessed on the resource in question.
235
+ #
236
+ # ryan = Person.find(1)
237
+ # ryan.first # => ''
238
+ # ryan.save # => false
239
+ #
240
+ # # When
241
+ # # PUT https://api.people.com/people/1.xml
242
+ # # or
243
+ # # PUT https://api.people.com/people/1.json
244
+ # # is requested with invalid values, the response is:
245
+ # #
246
+ # # Response (422):
247
+ # # <errors><error>First cannot be empty</error></errors>
248
+ # # or
249
+ # # {"errors":{"first":["cannot be empty"]}}
250
+ # #
251
+ #
252
+ # ryan.errors.invalid?(:first) # => true
253
+ # ryan.errors.full_messages # => ['First cannot be empty']
254
+ #
255
+ # For backwards-compatibility with older endpoints, the following formats are also supported in JSON responses:
256
+ #
257
+ # # {"errors":['First cannot be empty']}
258
+ # # This was the required format for previous versions of ActiveResource
259
+ # # {"first":["cannot be empty"]}
260
+ # # This was the default format produced by respond_with in ActionController <3.2.1
261
+ #
262
+ # Parsing either of these formats will result in a deprecation warning.
263
+ #
264
+ # Learn more about Active Resource's validation features in the ActiveResource::Validations documentation.
265
+ #
266
+ # === Timeouts
267
+ #
268
+ # Active Resource relies on HTTP to access RESTful APIs and as such is inherently susceptible to slow or
269
+ # unresponsive servers. In such cases, your Active Resource method calls could \timeout. You can control the
270
+ # amount of time before Active Resource times out with the +timeout+ variable.
271
+ #
272
+ # class Person < ActiveResource::Base
273
+ # self.site = "https://api.people.com"
274
+ # self.timeout = 5
275
+ # end
276
+ #
277
+ # This sets the +timeout+ to 5 seconds. You can adjust the +timeout+ to a value suitable for the RESTful API
278
+ # you are accessing. It is recommended to set this to a reasonably low value to allow your Active Resource
279
+ # clients (especially if you are using Active Resource in a Rails application) to fail-fast (see
280
+ # http://en.wikipedia.org/wiki/Fail-fast) rather than cause cascading failures that could incapacitate your
281
+ # server.
282
+ #
283
+ # When a \timeout occurs, an ActiveResource::TimeoutError is raised. You should rescue from
284
+ # ActiveResource::TimeoutError in your Active Resource method calls.
285
+ #
286
+ # Internally, Active Resource relies on Ruby's Net::HTTP library to make HTTP requests. Setting +timeout+
287
+ # sets the <tt>read_timeout</tt> of the internal Net::HTTP instance to the same value. The default
288
+ # <tt>read_timeout</tt> is 60 seconds on most Ruby implementations.
289
+ #
290
+ # Active Resource also supports distinct +open_timeout+ (time to connect) and +read_timeout+ (how long to
291
+ # wait for an upstream response). This is inline with supported +Net::HTTP+ timeout configuration and allows
292
+ # for finer control of client timeouts depending on context.
293
+ #
294
+ # class Person < ActiveResource::Base
295
+ # self.site = "https://api.people.com"
296
+ # self.open_timeout = 2
297
+ # self.read_timeout = 10
298
+ # end
299
+ class Base
300
+ ##
301
+ # :singleton-method:
302
+ # The logger for diagnosing and tracing Active Resource calls.
303
+ cattr_accessor :logger
304
+
305
+ class_attribute :_format
306
+ class_attribute :_collection_parser
307
+ class_attribute :include_format_in_path
308
+ self.include_format_in_path = true
309
+
310
+ class_attribute :connection_class
311
+ self.connection_class = Connection
312
+
313
+ class << self
314
+ include ThreadsafeAttributes
315
+ threadsafe_attribute :_headers, :_connection, :_user, :_password, :_site, :_proxy
316
+
317
+ # Creates a schema for this resource - setting the attributes that are
318
+ # known prior to fetching an instance from the remote system.
319
+ #
320
+ # The schema helps define the set of <tt>known_attributes</tt> of the
321
+ # current resource.
322
+ #
323
+ # There is no need to specify a schema for your Active Resource. If
324
+ # you do not, the <tt>known_attributes</tt> will be guessed from the
325
+ # instance attributes returned when an instance is fetched from the
326
+ # remote system.
327
+ #
328
+ # example:
329
+ # class Person < ActiveResource::Base
330
+ # schema do
331
+ # # define each attribute separately
332
+ # attribute 'name', :string
333
+ #
334
+ # # or use the convenience methods and pass >=1 attribute names
335
+ # string 'eye_color', 'hair_color'
336
+ # integer 'age'
337
+ # float 'height', 'weight'
338
+ #
339
+ # # unsupported types should be left as strings
340
+ # # overload the accessor methods if you need to convert them
341
+ # attribute 'created_at', 'string'
342
+ # end
343
+ # end
344
+ #
345
+ # p = Person.new
346
+ # p.respond_to? :name # => true
347
+ # p.respond_to? :age # => true
348
+ # p.name # => nil
349
+ # p.age # => nil
350
+ #
351
+ # j = Person.find_by_name('John')
352
+ # <person><name>John</name><age>34</age><num_children>3</num_children></person>
353
+ # j.respond_to? :name # => true
354
+ # j.respond_to? :age # => true
355
+ # j.name # => 'John'
356
+ # j.age # => '34' # note this is a string!
357
+ # j.num_children # => '3' # note this is a string!
358
+ #
359
+ # p.num_children # => NoMethodError
360
+ #
361
+ # Attribute-types must be one of: <tt>string, text, integer, float, decimal, datetime, timestamp, time, date, binary, boolean</tt>
362
+ #
363
+ # Note: at present the attribute-type doesn't do anything, but stay
364
+ # tuned...
365
+ # Shortly it will also *cast* the value of the returned attribute.
366
+ # ie:
367
+ # j.age # => 34 # cast to an integer
368
+ # j.weight # => '65' # still a string!
369
+ #
370
+ def schema(&block)
371
+ if block_given?
372
+ schema_definition = Schema.new
373
+ schema_definition.instance_eval(&block)
374
+
375
+ # skip out if we didn't define anything
376
+ return unless schema_definition.attrs.present?
377
+
378
+ @schema ||= {}.with_indifferent_access
379
+ @known_attributes ||= []
380
+
381
+ schema_definition.attrs.each do |k,v|
382
+ @schema[k] = v
383
+ @known_attributes << k
384
+ end
385
+
386
+ @schema
387
+ else
388
+ @schema ||= nil
389
+ end
390
+ end
391
+
392
+ # Alternative, direct way to specify a <tt>schema</tt> for this
393
+ # Resource. <tt>schema</tt> is more flexible, but this is quick
394
+ # for a very simple schema.
395
+ #
396
+ # Pass the schema as a hash with the keys being the attribute-names
397
+ # and the value being one of the accepted attribute types (as defined
398
+ # in <tt>schema</tt>)
399
+ #
400
+ # example:
401
+ #
402
+ # class Person < ActiveResource::Base
403
+ # schema = {'name' => :string, 'age' => :integer }
404
+ # end
405
+ #
406
+ # The keys/values can be strings or symbols. They will be converted to
407
+ # strings.
408
+ #
409
+ def schema=(the_schema)
410
+ unless the_schema.present?
411
+ # purposefully nulling out the schema
412
+ @schema = nil
413
+ @known_attributes = []
414
+ return
415
+ end
416
+
417
+ raise ArgumentError, "Expected a hash" unless the_schema.kind_of? Hash
418
+
419
+ schema do
420
+ the_schema.each {|k,v| attribute(k,v) }
421
+ end
422
+ end
423
+
424
+ # Returns the list of known attributes for this resource, gathered
425
+ # from the provided <tt>schema</tt>
426
+ # Attributes that are known will cause your resource to return 'true'
427
+ # when <tt>respond_to?</tt> is called on them. A known attribute will
428
+ # return nil if not set (rather than <tt>MethodNotFound</tt>); thus
429
+ # known attributes can be used with <tt>validates_presence_of</tt>
430
+ # without a getter-method.
431
+ def known_attributes
432
+ @known_attributes ||= []
433
+ end
434
+
435
+ # Gets the URI of the REST resources to map for this class. The site variable is required for
436
+ # Active Resource's mapping to work.
437
+ def site
438
+ # Not using superclass_delegating_reader because don't want subclasses to modify superclass instance
439
+ #
440
+ # With superclass_delegating_reader
441
+ #
442
+ # Parent.site = 'https://anonymous@test.com'
443
+ # Subclass.site # => 'https://anonymous@test.com'
444
+ # Subclass.site.user = 'david'
445
+ # Parent.site # => 'https://david@test.com'
446
+ #
447
+ # Without superclass_delegating_reader (expected behavior)
448
+ #
449
+ # Parent.site = 'https://anonymous@test.com'
450
+ # Subclass.site # => 'https://anonymous@test.com'
451
+ # Subclass.site.user = 'david' # => TypeError: can't modify frozen object
452
+ #
453
+ if _site_defined?
454
+ _site
455
+ elsif superclass != Object && superclass.site
456
+ superclass.site.dup.freeze
457
+ end
458
+ end
459
+
460
+ # Sets the URI of the REST resources to map for this class to the value in the +site+ argument.
461
+ # The site variable is required for Active Resource's mapping to work.
462
+ def site=(site)
463
+ self._connection = nil
464
+ if site.nil?
465
+ self._site = nil
466
+ else
467
+ self._site = create_site_uri_from(site)
468
+ self._user = URI.parser.unescape(_site.user) if _site.user
469
+ self._password = URI.parser.unescape(_site.password) if _site.password
470
+ end
471
+ end
472
+
473
+ # Gets the \proxy variable if a proxy is required
474
+ def proxy
475
+ # Not using superclass_delegating_reader. See +site+ for explanation
476
+ if _proxy_defined?
477
+ _proxy
478
+ elsif superclass != Object && superclass.proxy
479
+ superclass.proxy.dup.freeze
480
+ end
481
+ end
482
+
483
+ # Sets the URI of the http proxy to the value in the +proxy+ argument.
484
+ def proxy=(proxy)
485
+ self._connection = nil
486
+ self._proxy = proxy.nil? ? nil : create_proxy_uri_from(proxy)
487
+ end
488
+
489
+ # Gets the \user for REST HTTP authentication.
490
+ def user
491
+ # Not using superclass_delegating_reader. See +site+ for explanation
492
+ if _user_defined?
493
+ _user
494
+ elsif superclass != Object && superclass.user
495
+ superclass.user.dup.freeze
496
+ end
497
+ end
498
+
499
+ # Sets the \user for REST HTTP authentication.
500
+ def user=(user)
501
+ self._connection = nil
502
+ self._user = user
503
+ end
504
+
505
+ # Gets the \password for REST HTTP authentication.
506
+ def password
507
+ # Not using superclass_delegating_reader. See +site+ for explanation
508
+ if _password_defined?
509
+ _password
510
+ elsif superclass != Object && superclass.password
511
+ superclass.password.dup.freeze
512
+ end
513
+ end
514
+
515
+ # Sets the \password for REST HTTP authentication.
516
+ def password=(password)
517
+ self._connection = nil
518
+ self._password = password
519
+ end
520
+
521
+ def auth_type
522
+ if defined?(@auth_type)
523
+ @auth_type
524
+ end
525
+ end
526
+
527
+ def auth_type=(auth_type)
528
+ self._connection = nil
529
+ @auth_type = auth_type
530
+ end
531
+
532
+ # Sets the format that attributes are sent and received in from a mime type reference:
533
+ #
534
+ # Person.format = :json
535
+ # Person.find(1) # => GET /people/1.json
536
+ #
537
+ # Person.format = ActiveResource::Formats::XmlFormat
538
+ # Person.find(1) # => GET /people/1.xml
539
+ #
540
+ # Default format is <tt>:json</tt>.
541
+ def format=(mime_type_reference_or_format)
542
+ format = mime_type_reference_or_format.is_a?(Symbol) ?
543
+ ActiveResource::Formats[mime_type_reference_or_format] : mime_type_reference_or_format
544
+
545
+ self._format = format
546
+ connection.format = format if site
547
+ end
548
+
549
+ # Returns the current format, default is ActiveResource::Formats::JsonFormat.
550
+ def format
551
+ self._format || ActiveResource::Formats::JsonFormat
552
+ end
553
+
554
+ # Sets the parser to use when a collection is returned. The parser must be Enumerable.
555
+ def collection_parser=(parser_instance)
556
+ parser_instance = parser_instance.constantize if parser_instance.is_a?(String)
557
+ self._collection_parser = parser_instance
558
+ end
559
+
560
+ def collection_parser
561
+ self._collection_parser || ActiveResource::Collection
562
+ end
563
+
564
+ # Sets the number of seconds after which requests to the REST API should time out.
565
+ def timeout=(timeout)
566
+ self._connection = nil
567
+ @timeout = timeout
568
+ end
569
+
570
+ # Sets the number of seconds after which connection attempts to the REST API should time out.
571
+ def open_timeout=(timeout)
572
+ self._connection = nil
573
+ @open_timeout = timeout
574
+ end
575
+
576
+ # Sets the number of seconds after which reads to the REST API should time out.
577
+ def read_timeout=(timeout)
578
+ self._connection = nil
579
+ @read_timeout = timeout
580
+ end
581
+
582
+ # Gets the number of seconds after which requests to the REST API should time out.
583
+ def timeout
584
+ if defined?(@timeout)
585
+ @timeout
586
+ elsif superclass != Object && superclass.timeout
587
+ superclass.timeout
588
+ end
589
+ end
590
+
591
+ # Gets the number of seconds after which connection attempts to the REST API should time out.
592
+ def open_timeout
593
+ if defined?(@open_timeout)
594
+ @open_timeout
595
+ elsif superclass != Object && superclass.open_timeout
596
+ superclass.open_timeout
597
+ end
598
+ end
599
+
600
+ # Gets the number of seconds after which reads to the REST API should time out.
601
+ def read_timeout
602
+ if defined?(@read_timeout)
603
+ @read_timeout
604
+ elsif superclass != Object && superclass.read_timeout
605
+ superclass.read_timeout
606
+ end
607
+ end
608
+
609
+ # Options that will get applied to an SSL connection.
610
+ #
611
+ # * <tt>:key</tt> - An OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
612
+ # * <tt>:cert</tt> - An OpenSSL::X509::Certificate object as client certificate
613
+ # * <tt>:ca_file</tt> - Path to a CA certification file in PEM format. The file can contain several CA certificates.
614
+ # * <tt>:ca_path</tt> - Path of a CA certification directory containing certifications in PEM format.
615
+ # * <tt>:verify_mode</tt> - Flags for server the certification verification at beginning of SSL/TLS session. (OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable)
616
+ # * <tt>:verify_callback</tt> - The verify callback for the server certification verification.
617
+ # * <tt>:verify_depth</tt> - The maximum depth for the certificate chain verification.
618
+ # * <tt>:cert_store</tt> - OpenSSL::X509::Store to verify peer certificate.
619
+ # * <tt>:ssl_timeout</tt> -The SSL timeout in seconds.
620
+ def ssl_options=(options)
621
+ self._connection = nil
622
+ @ssl_options = options
623
+ end
624
+
625
+ # Returns the SSL options hash.
626
+ def ssl_options
627
+ if defined?(@ssl_options)
628
+ @ssl_options
629
+ elsif superclass != Object && superclass.ssl_options
630
+ superclass.ssl_options
631
+ end
632
+ end
633
+
634
+ # An instance of ActiveResource::Connection that is the base \connection to the remote service.
635
+ # The +refresh+ parameter toggles whether or not the \connection is refreshed at every request
636
+ # or not (defaults to <tt>false</tt>).
637
+ def connection(refresh = false)
638
+ if _connection_defined? || superclass == Object
639
+ self._connection = connection_class.new(site, format) if refresh || _connection.nil?
640
+ _connection.proxy = proxy if proxy
641
+ _connection.user = user if user
642
+ _connection.password = password if password
643
+ _connection.auth_type = auth_type if auth_type
644
+ _connection.timeout = timeout if timeout
645
+ _connection.open_timeout = open_timeout if open_timeout
646
+ _connection.read_timeout = read_timeout if read_timeout
647
+ _connection.ssl_options = ssl_options if ssl_options
648
+ _connection
649
+ else
650
+ superclass.connection
651
+ end
652
+ end
653
+
654
+ def headers
655
+ headers_state = self._headers || {}
656
+ if superclass != Object
657
+ self._headers = superclass.headers.merge(headers_state)
658
+ else
659
+ headers_state
660
+ end
661
+ end
662
+
663
+ attr_writer :element_name
664
+
665
+ def element_name
666
+ @element_name ||= model_name.element
667
+ end
668
+
669
+ attr_writer :collection_name
670
+
671
+ def collection_name
672
+ @collection_name ||= ActiveSupport::Inflector.pluralize(element_name)
673
+ end
674
+
675
+ attr_writer :primary_key
676
+
677
+ def primary_key
678
+ if defined?(@primary_key)
679
+ @primary_key
680
+ elsif superclass != Object && superclass.primary_key
681
+ primary_key = superclass.primary_key
682
+ return primary_key if primary_key.is_a?(Symbol)
683
+ primary_key.dup.freeze
684
+ else
685
+ 'id'
686
+ end
687
+ end
688
+
689
+ # Gets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.json</tt>)
690
+ # This method is regenerated at runtime based on what the \prefix is set to.
691
+ def prefix(options={})
692
+ default = site.path
693
+ default << '/' unless default[-1..-1] == '/'
694
+ # generate the actual method based on the current site path
695
+ self.prefix = default
696
+ prefix(options)
697
+ end
698
+
699
+ # An attribute reader for the source string for the resource path \prefix. This
700
+ # method is regenerated at runtime based on what the \prefix is set to.
701
+ def prefix_source
702
+ prefix # generate #prefix and #prefix_source methods first
703
+ prefix_source
704
+ end
705
+
706
+ # Sets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.json</tt>).
707
+ # Default value is <tt>site.path</tt>.
708
+ def prefix=(value = '/')
709
+ # Replace :placeholders with '#{embedded options[:lookups]}'
710
+ prefix_call = value.gsub(/:\w+/) { |key| "\#{URI.parser.escape options[#{key}].to_s}" }
711
+
712
+ # Clear prefix parameters in case they have been cached
713
+ @prefix_parameters = nil
714
+
715
+ silence_warnings do
716
+ # Redefine the new methods.
717
+ instance_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
718
+ def prefix_source() "#{value}" end
719
+ def prefix(options={}) "#{prefix_call}" end
720
+ RUBY_EVAL
721
+ end
722
+ rescue Exception => e
723
+ logger.error "Couldn't set prefix: #{e}\n #{code}" if logger
724
+ raise
725
+ end
726
+
727
+ alias_method :set_prefix, :prefix= #:nodoc:
728
+
729
+ alias_method :set_element_name, :element_name= #:nodoc:
730
+ alias_method :set_collection_name, :collection_name= #:nodoc:
731
+
732
+ def format_extension
733
+ include_format_in_path ? ".#{format.extension}" : ""
734
+ end
735
+
736
+ # Gets the element path for the given ID in +id+. If the +query_options+ parameter is omitted, Rails
737
+ # will split from the \prefix options.
738
+ #
739
+ # ==== Options
740
+ # +prefix_options+ - A \hash to add a \prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
741
+ # would yield a URL like <tt>/accounts/19/purchases.json</tt>).
742
+ #
743
+ # +query_options+ - A \hash to add items to the query string for the request.
744
+ #
745
+ # ==== Examples
746
+ # Post.element_path(1)
747
+ # # => /posts/1.json
748
+ #
749
+ # class Comment < ActiveResource::Base
750
+ # self.site = "https://37s.sunrise.com/posts/:post_id"
751
+ # end
752
+ #
753
+ # Comment.element_path(1, :post_id => 5)
754
+ # # => /posts/5/comments/1.json
755
+ #
756
+ # Comment.element_path(1, :post_id => 5, :active => 1)
757
+ # # => /posts/5/comments/1.json?active=1
758
+ #
759
+ # Comment.element_path(1, {:post_id => 5}, {:active => 1})
760
+ # # => /posts/5/comments/1.json?active=1
761
+ #
762
+ def element_path(id, prefix_options = {}, query_options = nil)
763
+ check_prefix_options(prefix_options)
764
+
765
+ prefix_options, query_options = split_options(prefix_options) if query_options.nil?
766
+ "#{prefix(prefix_options)}#{collection_name}/#{URI.parser.escape id.to_s}#{format_extension}#{query_string(query_options)}"
767
+ end
768
+
769
+ # Gets the new element path for REST resources.
770
+ #
771
+ # ==== Options
772
+ # * +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
773
+ # would yield a URL like <tt>/accounts/19/purchases/new.json</tt>).
774
+ #
775
+ # ==== Examples
776
+ # Post.new_element_path
777
+ # # => /posts/new.json
778
+ #
779
+ # class Comment < ActiveResource::Base
780
+ # self.site = "https://37s.sunrise.com/posts/:post_id"
781
+ # end
782
+ #
783
+ # Comment.collection_path(:post_id => 5)
784
+ # # => /posts/5/comments/new.json
785
+ def new_element_path(prefix_options = {})
786
+ "#{prefix(prefix_options)}#{collection_name}/new#{format_extension}"
787
+ end
788
+
789
+ # Gets the collection path for the REST resources. If the +query_options+ parameter is omitted, Rails
790
+ # will split from the +prefix_options+.
791
+ #
792
+ # ==== Options
793
+ # * +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
794
+ # would yield a URL like <tt>/accounts/19/purchases.json</tt>).
795
+ # * +query_options+ - A hash to add items to the query string for the request.
796
+ #
797
+ # ==== Examples
798
+ # Post.collection_path
799
+ # # => /posts.json
800
+ #
801
+ # Comment.collection_path(:post_id => 5)
802
+ # # => /posts/5/comments.json
803
+ #
804
+ # Comment.collection_path(:post_id => 5, :active => 1)
805
+ # # => /posts/5/comments.json?active=1
806
+ #
807
+ # Comment.collection_path({:post_id => 5}, {:active => 1})
808
+ # # => /posts/5/comments.json?active=1
809
+ #
810
+ def collection_path(prefix_options = {}, query_options = nil)
811
+ check_prefix_options(prefix_options)
812
+ prefix_options, query_options = split_options(prefix_options) if query_options.nil?
813
+ "#{prefix(prefix_options)}#{collection_name}#{format_extension}#{query_string(query_options)}"
814
+ end
815
+
816
+ alias_method :set_primary_key, :primary_key= #:nodoc:
817
+
818
+ # Builds a new, unsaved record using the default values from the remote server so
819
+ # that it can be used with RESTful forms.
820
+ #
821
+ # ==== Options
822
+ # * +attributes+ - A hash that overrides the default values from the server.
823
+ #
824
+ # Returns the new resource instance.
825
+ #
826
+ def build(attributes = {})
827
+ attrs = self.format.decode(connection.get("#{new_element_path(attributes)}", headers).body).merge(attributes)
828
+ self.new(attrs)
829
+ end
830
+
831
+ # Creates a new resource instance and makes a request to the remote service
832
+ # that it be saved, making it equivalent to the following simultaneous calls:
833
+ #
834
+ # ryan = Person.new(:first => 'ryan')
835
+ # ryan.save
836
+ #
837
+ # Returns the newly created resource. If a failure has occurred an
838
+ # exception will be raised (see <tt>save</tt>). If the resource is invalid and
839
+ # has not been saved then <tt>valid?</tt> will return <tt>false</tt>,
840
+ # while <tt>new?</tt> will still return <tt>true</tt>.
841
+ #
842
+ # ==== Examples
843
+ # Person.create(:name => 'Jeremy', :email => 'myname@nospam.com', :enabled => true)
844
+ # my_person = Person.find(:first)
845
+ # my_person.email # => myname@nospam.com
846
+ #
847
+ # dhh = Person.create(:name => 'David', :email => 'dhh@nospam.com', :enabled => true)
848
+ # dhh.valid? # => true
849
+ # dhh.new? # => false
850
+ #
851
+ # # We'll assume that there's a validation that requires the name attribute
852
+ # that_guy = Person.create(:name => '', :email => 'thatguy@nospam.com', :enabled => true)
853
+ # that_guy.valid? # => false
854
+ # that_guy.new? # => true
855
+ def create(attributes = {})
856
+ self.new(attributes).tap { |resource| resource.save }
857
+ end
858
+
859
+ # Creates a new resource (just like <tt>create</tt>) and makes a request to the
860
+ # remote service that it be saved, but runs validations and raises
861
+ # <tt>ActiveResource::ResourceInvalid</tt>, making it equivalent to the following
862
+ # simultaneous calls:
863
+ #
864
+ # ryan = Person.new(:first => 'ryan')
865
+ # ryan.save!
866
+ def create!(attributes = {})
867
+ self.new(attributes).tap { |resource| resource.save! }
868
+ end
869
+
870
+ # Core method for finding resources. Used similarly to Active Record's +find+ method.
871
+ #
872
+ # ==== Arguments
873
+ # The first argument is considered to be the scope of the query. That is, how many
874
+ # resources are returned from the request. It can be one of the following.
875
+ #
876
+ # * <tt>:one</tt> - Returns a single resource.
877
+ # * <tt>:first</tt> - Returns the first resource found.
878
+ # * <tt>:last</tt> - Returns the last resource found.
879
+ # * <tt>:all</tt> - Returns every resource that matches the request.
880
+ #
881
+ # ==== Options
882
+ #
883
+ # * <tt>:from</tt> - Sets the path or custom method that resources will be fetched from.
884
+ # * <tt>:params</tt> - Sets query and \prefix (nested URL) parameters.
885
+ #
886
+ # ==== Examples
887
+ # Person.find(1)
888
+ # # => GET /people/1.json
889
+ #
890
+ # Person.find(:all)
891
+ # # => GET /people.json
892
+ #
893
+ # Person.find(:all, :params => { :title => "CEO" })
894
+ # # => GET /people.json?title=CEO
895
+ #
896
+ # Person.find(:first, :from => :managers)
897
+ # # => GET /people/managers.json
898
+ #
899
+ # Person.find(:last, :from => :managers)
900
+ # # => GET /people/managers.json
901
+ #
902
+ # Person.find(:all, :from => "/companies/1/people.json")
903
+ # # => GET /companies/1/people.json
904
+ #
905
+ # Person.find(:one, :from => :leader)
906
+ # # => GET /people/leader.json
907
+ #
908
+ # Person.find(:all, :from => :developers, :params => { :language => 'ruby' })
909
+ # # => GET /people/developers.json?language=ruby
910
+ #
911
+ # Person.find(:one, :from => "/companies/1/manager.json")
912
+ # # => GET /companies/1/manager.json
913
+ #
914
+ # StreetAddress.find(1, :params => { :person_id => 1 })
915
+ # # => GET /people/1/street_addresses/1.json
916
+ #
917
+ # == Failure or missing data
918
+ # A failure to find the requested object raises a ResourceNotFound
919
+ # exception if the find was called with an id.
920
+ # With any other scope, find returns nil when no data is returned.
921
+ #
922
+ # Person.find(1)
923
+ # # => raises ResourceNotFound
924
+ #
925
+ # Person.find(:all)
926
+ # Person.find(:first)
927
+ # Person.find(:last)
928
+ # # => nil
929
+ def find(*arguments)
930
+ scope = arguments.slice!(0)
931
+ options = arguments.slice!(0) || {}
932
+
933
+ case scope
934
+ when :all then find_every(options)
935
+ when :first then find_every(options).to_a.first
936
+ when :last then find_every(options).to_a.last
937
+ when :one then find_one(options)
938
+ else find_single(scope, options)
939
+ end
940
+ end
941
+
942
+
943
+ # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass
944
+ # in all the same arguments to this method as you can to
945
+ # <tt>find(:first)</tt>.
946
+ def first(*args)
947
+ find(:first, *args)
948
+ end
949
+
950
+ # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass
951
+ # in all the same arguments to this method as you can to
952
+ # <tt>find(:last)</tt>.
953
+ def last(*args)
954
+ find(:last, *args)
955
+ end
956
+
957
+ # This is an alias for find(:all). You can pass in all the same
958
+ # arguments to this method as you can to <tt>find(:all)</tt>
959
+ def all(*args)
960
+ find(:all, *args)
961
+ end
962
+
963
+ def where(clauses = {})
964
+ raise ArgumentError, "expected a clauses Hash, got #{clauses.inspect}" unless clauses.is_a? Hash
965
+ find(:all, :params => clauses)
966
+ end
967
+
968
+
969
+ # Deletes the resources with the ID in the +id+ parameter.
970
+ #
971
+ # ==== Options
972
+ # All options specify \prefix and query parameters.
973
+ #
974
+ # ==== Examples
975
+ # Event.delete(2) # sends DELETE /events/2
976
+ #
977
+ # Event.create(:name => 'Free Concert', :location => 'Community Center')
978
+ # my_event = Event.find(:first) # let's assume this is event with ID 7
979
+ # Event.delete(my_event.id) # sends DELETE /events/7
980
+ #
981
+ # # Let's assume a request to events/5/cancel.json
982
+ # Event.delete(params[:id]) # sends DELETE /events/5
983
+ def delete(id, options = {})
984
+ connection.delete(element_path(id, options), headers)
985
+ end
986
+
987
+ # Asserts the existence of a resource, returning <tt>true</tt> if the resource is found.
988
+ #
989
+ # ==== Examples
990
+ # Note.create(:title => 'Hello, world.', :body => 'Nothing more for now...')
991
+ # Note.exists?(1) # => true
992
+ #
993
+ # Note.exists(1349) # => false
994
+ def exists?(id, options = {})
995
+ if id
996
+ prefix_options, query_options = split_options(options[:params])
997
+ path = element_path(id, prefix_options, query_options)
998
+ response = connection.head(path, headers)
999
+ (200..206).include? response.code
1000
+ end
1001
+ # id && !find_single(id, options).nil?
1002
+ rescue ActiveResource::ResourceNotFound, ActiveResource::ResourceGone
1003
+ false
1004
+ end
1005
+
1006
+ private
1007
+
1008
+ def check_prefix_options(prefix_options)
1009
+ p_options = HashWithIndifferentAccess.new(prefix_options)
1010
+ prefix_parameters.each do |p|
1011
+ raise(MissingPrefixParam, "#{p} prefix_option is missing") if p_options[p].blank?
1012
+ end
1013
+ end
1014
+
1015
+ # Find every resource
1016
+ def find_every(options)
1017
+ begin
1018
+ case from = options[:from]
1019
+ when Symbol
1020
+ instantiate_collection(get(from, options[:params]), options[:params])
1021
+ when String
1022
+ path = "#{from}#{query_string(options[:params])}"
1023
+ instantiate_collection(format.decode(connection.get(path, headers).body) || [], options[:params])
1024
+ else
1025
+ prefix_options, query_options = split_options(options[:params])
1026
+ path = collection_path(prefix_options, query_options)
1027
+ instantiate_collection( (format.decode(connection.get(path, headers).body) || []), query_options, prefix_options )
1028
+ end
1029
+ rescue ActiveResource::ResourceNotFound
1030
+ # Swallowing ResourceNotFound exceptions and return nil - as per
1031
+ # ActiveRecord.
1032
+ nil
1033
+ end
1034
+ end
1035
+
1036
+ # Find a single resource from a one-off URL
1037
+ def find_one(options)
1038
+ case from = options[:from]
1039
+ when Symbol
1040
+ instantiate_record(get(from, options[:params]))
1041
+ when String
1042
+ path = "#{from}#{query_string(options[:params])}"
1043
+ instantiate_record(format.decode(connection.get(path, headers).body))
1044
+ end
1045
+ end
1046
+
1047
+ # Find a single resource from the default URL
1048
+ def find_single(scope, options)
1049
+ prefix_options, query_options = split_options(options[:params])
1050
+ path = element_path(scope, prefix_options, query_options)
1051
+ instantiate_record(format.decode(connection.get(path, headers).body), prefix_options)
1052
+ end
1053
+
1054
+ def instantiate_collection(collection, original_params = {}, prefix_options = {})
1055
+ collection_parser.new(collection).tap do |parser|
1056
+ parser.resource_class = self
1057
+ parser.original_params = original_params
1058
+ end.collect! { |record| instantiate_record(record, prefix_options) }
1059
+ end
1060
+
1061
+ def instantiate_record(record, prefix_options = {})
1062
+ new(record, true).tap do |resource|
1063
+ resource.prefix_options = prefix_options
1064
+ end
1065
+ end
1066
+
1067
+
1068
+ # Accepts a URI and creates the site URI from that.
1069
+ def create_site_uri_from(site)
1070
+ site.is_a?(URI) ? site.dup : URI.parse(site)
1071
+ end
1072
+
1073
+ # Accepts a URI and creates the proxy URI from that.
1074
+ def create_proxy_uri_from(proxy)
1075
+ proxy.is_a?(URI) ? proxy.dup : URI.parse(proxy)
1076
+ end
1077
+
1078
+ # contains a set of the current prefix parameters.
1079
+ def prefix_parameters
1080
+ @prefix_parameters ||= prefix_source.scan(/:\w+/).map { |key| key[1..-1].to_sym }.to_set
1081
+ end
1082
+
1083
+ # Builds the query string for the request.
1084
+ def query_string(options)
1085
+ "?#{options.to_query}" unless options.nil? || options.empty?
1086
+ end
1087
+
1088
+ # split an option hash into two hashes, one containing the prefix options,
1089
+ # and the other containing the leftovers.
1090
+ def split_options(options = {})
1091
+ prefix_options, query_options = {}, {}
1092
+
1093
+ (options || {}).each do |key, value|
1094
+ next if key.blank? || !key.respond_to?(:to_sym)
1095
+ (prefix_parameters.include?(key.to_sym) ? prefix_options : query_options)[key.to_sym] = value
1096
+ end
1097
+
1098
+ [ prefix_options, query_options ]
1099
+ end
1100
+ end
1101
+
1102
+ attr_accessor :attributes #:nodoc:
1103
+ attr_accessor :prefix_options #:nodoc:
1104
+
1105
+ # If no schema has been defined for the class (see
1106
+ # <tt>ActiveResource::schema=</tt>), the default automatic schema is
1107
+ # generated from the current instance's attributes
1108
+ def schema
1109
+ self.class.schema || self.attributes
1110
+ end
1111
+
1112
+ # This is a list of known attributes for this resource. Either
1113
+ # gathered from the provided <tt>schema</tt>, or from the attributes
1114
+ # set on this instance after it has been fetched from the remote system.
1115
+ def known_attributes
1116
+ (self.class.known_attributes + self.attributes.keys.map(&:to_s)).uniq
1117
+ end
1118
+
1119
+
1120
+ # Constructor method for \new resources; the optional +attributes+ parameter takes a \hash
1121
+ # of attributes for the \new resource.
1122
+ #
1123
+ # ==== Examples
1124
+ # my_course = Course.new
1125
+ # my_course.name = "Western Civilization"
1126
+ # my_course.lecturer = "Don Trotter"
1127
+ # my_course.save
1128
+ #
1129
+ # my_other_course = Course.new(:name => "Philosophy: Reason and Being", :lecturer => "Ralph Cling")
1130
+ # my_other_course.save
1131
+ def initialize(attributes = {}, persisted = false)
1132
+ @attributes = {}.with_indifferent_access
1133
+ @prefix_options = {}
1134
+ @persisted = persisted
1135
+ load(attributes, false, persisted)
1136
+ end
1137
+
1138
+ # Returns a \clone of the resource that hasn't been assigned an +id+ yet and
1139
+ # is treated as a \new resource.
1140
+ #
1141
+ # ryan = Person.find(1)
1142
+ # not_ryan = ryan.clone
1143
+ # not_ryan.new? # => true
1144
+ #
1145
+ # Any active resource member attributes will NOT be cloned, though all other
1146
+ # attributes are. This is to prevent the conflict between any +prefix_options+
1147
+ # that refer to the original parent resource and the newly cloned parent
1148
+ # resource that does not exist.
1149
+ #
1150
+ # ryan = Person.find(1)
1151
+ # ryan.address = StreetAddress.find(1, :person_id => ryan.id)
1152
+ # ryan.hash = {:not => "an ARes instance"}
1153
+ #
1154
+ # not_ryan = ryan.clone
1155
+ # not_ryan.new? # => true
1156
+ # not_ryan.address # => NoMethodError
1157
+ # not_ryan.hash # => {:not => "an ARes instance"}
1158
+ def clone
1159
+ # Clone all attributes except the pk and any nested ARes
1160
+ cloned = Hash[attributes.reject {|k,v| k == self.class.primary_key || v.is_a?(ActiveResource::Base)}.map { |k, v| [k, v.clone] }]
1161
+ # Form the new resource - bypass initialize of resource with 'new' as that will call 'load' which
1162
+ # attempts to convert hashes into member objects and arrays into collections of objects. We want
1163
+ # the raw objects to be cloned so we bypass load by directly setting the attributes hash.
1164
+ resource = self.class.new({})
1165
+ resource.prefix_options = self.prefix_options
1166
+ resource.send :instance_variable_set, '@attributes', cloned
1167
+ resource
1168
+ end
1169
+
1170
+
1171
+ # Returns +true+ if this object hasn't yet been saved, otherwise, returns +false+.
1172
+ #
1173
+ # ==== Examples
1174
+ # not_new = Computer.create(:brand => 'Apple', :make => 'MacBook', :vendor => 'MacMall')
1175
+ # not_new.new? # => false
1176
+ #
1177
+ # is_new = Computer.new(:brand => 'IBM', :make => 'Thinkpad', :vendor => 'IBM')
1178
+ # is_new.new? # => true
1179
+ #
1180
+ # is_new.save
1181
+ # is_new.new? # => false
1182
+ #
1183
+ def new?
1184
+ !persisted?
1185
+ end
1186
+ alias :new_record? :new?
1187
+
1188
+ # Returns +true+ if this object has been saved, otherwise returns +false+.
1189
+ #
1190
+ # ==== Examples
1191
+ # persisted = Computer.create(:brand => 'Apple', :make => 'MacBook', :vendor => 'MacMall')
1192
+ # persisted.persisted? # => true
1193
+ #
1194
+ # not_persisted = Computer.new(:brand => 'IBM', :make => 'Thinkpad', :vendor => 'IBM')
1195
+ # not_persisted.persisted? # => false
1196
+ #
1197
+ # not_persisted.save
1198
+ # not_persisted.persisted? # => true
1199
+ #
1200
+ def persisted?
1201
+ @persisted
1202
+ end
1203
+
1204
+ # Gets the <tt>\id</tt> attribute of the resource.
1205
+ def id
1206
+ attributes[self.class.primary_key]
1207
+ end
1208
+
1209
+ # Sets the <tt>\id</tt> attribute of the resource.
1210
+ def id=(id)
1211
+ attributes[self.class.primary_key] = id
1212
+ end
1213
+
1214
+ # Test for equality. Resource are equal if and only if +other+ is the same object or
1215
+ # is an instance of the same class, is not <tt>new?</tt>, and has the same +id+.
1216
+ #
1217
+ # ==== Examples
1218
+ # ryan = Person.create(:name => 'Ryan')
1219
+ # jamie = Person.create(:name => 'Jamie')
1220
+ #
1221
+ # ryan == jamie
1222
+ # # => false (Different name attribute and id)
1223
+ #
1224
+ # ryan_again = Person.new(:name => 'Ryan')
1225
+ # ryan == ryan_again
1226
+ # # => false (ryan_again is new?)
1227
+ #
1228
+ # ryans_clone = Person.create(:name => 'Ryan')
1229
+ # ryan == ryans_clone
1230
+ # # => false (Different id attributes)
1231
+ #
1232
+ # ryans_twin = Person.find(ryan.id)
1233
+ # ryan == ryans_twin
1234
+ # # => true
1235
+ #
1236
+ def ==(other)
1237
+ other.equal?(self) || (other.instance_of?(self.class) && other.id == id && other.prefix_options == prefix_options)
1238
+ end
1239
+
1240
+ # Tests for equality (delegates to ==).
1241
+ def eql?(other)
1242
+ self == other
1243
+ end
1244
+
1245
+ # Delegates to id in order to allow two resources of the same type and \id to work with something like:
1246
+ # [(a = Person.find 1), (b = Person.find 2)] & [(c = Person.find 1), (d = Person.find 4)] # => [a]
1247
+ def hash
1248
+ id.hash
1249
+ end
1250
+
1251
+ # Duplicates the current resource without saving it.
1252
+ #
1253
+ # ==== Examples
1254
+ # my_invoice = Invoice.create(:customer => 'That Company')
1255
+ # next_invoice = my_invoice.dup
1256
+ # next_invoice.new? # => true
1257
+ #
1258
+ # next_invoice.save
1259
+ # next_invoice == my_invoice # => false (different id attributes)
1260
+ #
1261
+ # my_invoice.customer # => That Company
1262
+ # next_invoice.customer # => That Company
1263
+ def dup
1264
+ self.class.new.tap do |resource|
1265
+ resource.attributes = @attributes
1266
+ resource.prefix_options = @prefix_options
1267
+ end
1268
+ end
1269
+
1270
+ # Saves (+POST+) or \updates (+PUT+) a resource. Delegates to +create+ if the object is \new,
1271
+ # +update+ if it exists. If the response to the \save includes a body, it will be assumed that this body
1272
+ # is Json for the final object as it looked after the \save (which would include attributes like +created_at+
1273
+ # that weren't part of the original submit).
1274
+ #
1275
+ # ==== Examples
1276
+ # my_company = Company.new(:name => 'RoleModel Software', :owner => 'Ken Auer', :size => 2)
1277
+ # my_company.new? # => true
1278
+ # my_company.save # sends POST /companies/ (create)
1279
+ #
1280
+ # my_company.new? # => false
1281
+ # my_company.size = 10
1282
+ # my_company.save # sends PUT /companies/1 (update)
1283
+ def save
1284
+ run_callbacks :save do
1285
+ new? ? create : update
1286
+ end
1287
+ end
1288
+
1289
+ # Saves the resource.
1290
+ #
1291
+ # If the resource is new, it is created via +POST+, otherwise the
1292
+ # existing resource is updated via +PUT+.
1293
+ #
1294
+ # With <tt>save!</tt> validations always run. If any of them fail
1295
+ # ActiveResource::ResourceInvalid gets raised, and nothing is POSTed to
1296
+ # the remote system.
1297
+ # See ActiveResource::Validations for more information.
1298
+ #
1299
+ # There's a series of callbacks associated with <tt>save!</tt>. If any
1300
+ # of the <tt>before_*</tt> callbacks return +false+ the action is
1301
+ # cancelled and <tt>save!</tt> raises ActiveResource::ResourceInvalid.
1302
+ def save!
1303
+ save || raise(ResourceInvalid.new(self))
1304
+ end
1305
+
1306
+ # Deletes the resource from the remote service.
1307
+ #
1308
+ # ==== Examples
1309
+ # my_id = 3
1310
+ # my_person = Person.find(my_id)
1311
+ # my_person.destroy
1312
+ # Person.find(my_id) # 404 (Resource Not Found)
1313
+ #
1314
+ # new_person = Person.create(:name => 'James')
1315
+ # new_id = new_person.id # => 7
1316
+ # new_person.destroy
1317
+ # Person.find(new_id) # 404 (Resource Not Found)
1318
+ def destroy
1319
+ run_callbacks :destroy do
1320
+ connection.delete(element_path, self.class.headers)
1321
+ end
1322
+ end
1323
+
1324
+ # Evaluates to <tt>true</tt> if this resource is not <tt>new?</tt> and is
1325
+ # found on the remote service. Using this method, you can check for
1326
+ # resources that may have been deleted between the object's instantiation
1327
+ # and actions on it.
1328
+ #
1329
+ # ==== Examples
1330
+ # Person.create(:name => 'Theodore Roosevelt')
1331
+ # that_guy = Person.find(:first)
1332
+ # that_guy.exists? # => true
1333
+ #
1334
+ # that_lady = Person.new(:name => 'Paul Bean')
1335
+ # that_lady.exists? # => false
1336
+ #
1337
+ # guys_id = that_guy.id
1338
+ # Person.delete(guys_id)
1339
+ # that_guy.exists? # => false
1340
+ def exists?
1341
+ !new? && self.class.exists?(to_param, :params => prefix_options)
1342
+ end
1343
+
1344
+ # Returns the serialized string representation of the resource in the configured
1345
+ # serialization format specified in ActiveResource::Base.format. The options
1346
+ # applicable depend on the configured encoding format.
1347
+ def encode(options={})
1348
+ send("to_#{self.class.format.extension}", options)
1349
+ end
1350
+
1351
+ # A method to \reload the attributes of this object from the remote web service.
1352
+ #
1353
+ # ==== Examples
1354
+ # my_branch = Branch.find(:first)
1355
+ # my_branch.name # => "Wislon Raod"
1356
+ #
1357
+ # # Another client fixes the typo...
1358
+ #
1359
+ # my_branch.name # => "Wislon Raod"
1360
+ # my_branch.reload
1361
+ # my_branch.name # => "Wilson Road"
1362
+ def reload
1363
+ self.load(self.class.find(to_param, :params => @prefix_options).attributes, false, true)
1364
+ end
1365
+
1366
+ # A method to manually load attributes from a \hash. Recursively loads collections of
1367
+ # resources. This method is called in +initialize+ and +create+ when a \hash of attributes
1368
+ # is provided.
1369
+ #
1370
+ # ==== Examples
1371
+ # my_attrs = {:name => 'J&J Textiles', :industry => 'Cloth and textiles'}
1372
+ # my_attrs = {:name => 'Marty', :colors => ["red", "green", "blue"]}
1373
+ #
1374
+ # the_supplier = Supplier.find(:first)
1375
+ # the_supplier.name # => 'J&M Textiles'
1376
+ # the_supplier.load(my_attrs)
1377
+ # the_supplier.name('J&J Textiles')
1378
+ #
1379
+ # # These two calls are the same as Supplier.new(my_attrs)
1380
+ # my_supplier = Supplier.new
1381
+ # my_supplier.load(my_attrs)
1382
+ #
1383
+ # # These three calls are the same as Supplier.create(my_attrs)
1384
+ # your_supplier = Supplier.new
1385
+ # your_supplier.load(my_attrs)
1386
+ # your_supplier.save
1387
+ def load(attributes, remove_root = false, persisted = false)
1388
+ raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
1389
+ @prefix_options, attributes = split_options(attributes)
1390
+
1391
+ if attributes.keys.size == 1
1392
+ remove_root = self.class.element_name == attributes.keys.first.to_s
1393
+ end
1394
+
1395
+ attributes = Formats.remove_root(attributes) if remove_root
1396
+
1397
+ attributes.each do |key, value|
1398
+ @attributes[key.to_s] =
1399
+ case value
1400
+ when Array
1401
+ resource = nil
1402
+ value.map do |attrs|
1403
+ if attrs.is_a?(Hash)
1404
+ resource ||= find_or_create_resource_for_collection(key)
1405
+ resource.new(attrs, persisted)
1406
+ else
1407
+ attrs.duplicable? ? attrs.dup : attrs
1408
+ end
1409
+ end
1410
+ when Hash
1411
+ resource = find_or_create_resource_for(key)
1412
+ resource.new(value, persisted)
1413
+ else
1414
+ value.duplicable? ? value.dup : value
1415
+ end
1416
+ end
1417
+ self
1418
+ end
1419
+
1420
+ # Updates a single attribute and then saves the object.
1421
+ #
1422
+ # Note: <tt>Unlike ActiveRecord::Base.update_attribute</tt>, this method <b>is</b>
1423
+ # subject to normal validation routines as an update sends the whole body
1424
+ # of the resource in the request. (See Validations).
1425
+ #
1426
+ # As such, this method is equivalent to calling update_attributes with a single attribute/value pair.
1427
+ #
1428
+ # If the saving fails because of a connection or remote service error, an
1429
+ # exception will be raised. If saving fails because the resource is
1430
+ # invalid then <tt>false</tt> will be returned.
1431
+ def update_attribute(name, value)
1432
+ self.send("#{name}=".to_sym, value)
1433
+ self.save
1434
+ end
1435
+
1436
+ # Updates this resource with all the attributes from the passed-in Hash
1437
+ # and requests that the record be saved.
1438
+ #
1439
+ # If the saving fails because of a connection or remote service error, an
1440
+ # exception will be raised. If saving fails because the resource is
1441
+ # invalid then <tt>false</tt> will be returned.
1442
+ #
1443
+ # Note: Though this request can be made with a partial set of the
1444
+ # resource's attributes, the full body of the request will still be sent
1445
+ # in the save request to the remote service.
1446
+ def update_attributes(attributes)
1447
+ load(attributes, false) && save
1448
+ end
1449
+
1450
+ # For checking <tt>respond_to?</tt> without searching the attributes (which is faster).
1451
+ alias_method :respond_to_without_attributes?, :respond_to?
1452
+
1453
+ # A method to determine if an object responds to a message (e.g., a method call). In Active Resource, a Person object with a
1454
+ # +name+ attribute can answer <tt>true</tt> to <tt>my_person.respond_to?(:name)</tt>, <tt>my_person.respond_to?(:name=)</tt>, and
1455
+ # <tt>my_person.respond_to?(:name?)</tt>.
1456
+ def respond_to_missing?(method, include_priv = false)
1457
+ method_name = method.to_s
1458
+ if attributes.nil?
1459
+ super
1460
+ elsif known_attributes.include?(method_name)
1461
+ true
1462
+ elsif method_name =~ /(?:=|\?)$/ && attributes.include?($`)
1463
+ true
1464
+ else
1465
+ # super must be called at the end of the method, because the inherited respond_to?
1466
+ # would return true for generated readers, even if the attribute wasn't present
1467
+ super
1468
+ end
1469
+ end
1470
+
1471
+ def to_json(options={})
1472
+ super(include_root_in_json ? { :root => self.class.element_name }.merge(options) : options)
1473
+ end
1474
+
1475
+ def to_xml(options={})
1476
+ super({ :root => self.class.element_name }.merge(options))
1477
+ end
1478
+
1479
+ protected
1480
+ def connection(refresh = false)
1481
+ self.class.connection(refresh)
1482
+ end
1483
+
1484
+ # Update the resource on the remote service.
1485
+ def update
1486
+ run_callbacks :update do
1487
+ connection.put(element_path(prefix_options), encode, self.class.headers).tap do |response|
1488
+ load_attributes_from_response(response)
1489
+ end
1490
+ end
1491
+ end
1492
+
1493
+ # Create (i.e., \save to the remote service) the \new resource.
1494
+ def create
1495
+ run_callbacks :create do
1496
+ connection.post(collection_path, encode, self.class.headers).tap do |response|
1497
+ self.id = id_from_response(response)
1498
+ load_attributes_from_response(response)
1499
+ end
1500
+ end
1501
+ end
1502
+
1503
+ def load_attributes_from_response(response)
1504
+ if (response_code_allows_body?(response.code) &&
1505
+ (response['Content-Length'].nil? || response['Content-Length'] != "0") &&
1506
+ !response.body.nil? && response.body.strip.size > 0)
1507
+ load(self.class.format.decode(response.body), true, true)
1508
+ @persisted = true
1509
+ end
1510
+ end
1511
+
1512
+ # Takes a response from a typical create post and pulls the ID out
1513
+ def id_from_response(response)
1514
+ response['Location'][/\/([^\/]*?)(\.\w+)?$/, 1] if response['Location']
1515
+ end
1516
+
1517
+ def element_path(options = nil)
1518
+ self.class.element_path(to_param, options || prefix_options)
1519
+ end
1520
+
1521
+ def new_element_path
1522
+ self.class.new_element_path(prefix_options)
1523
+ end
1524
+
1525
+ def collection_path(options = nil)
1526
+ self.class.collection_path(options || prefix_options)
1527
+ end
1528
+
1529
+ private
1530
+
1531
+ def read_attribute_for_serialization(n)
1532
+ attributes[n]
1533
+ end
1534
+
1535
+ # Determine whether the response is allowed to have a body per HTTP 1.1 spec section 4.4.1
1536
+ def response_code_allows_body?(c)
1537
+ !((100..199).include?(c) || [204,304].include?(c))
1538
+ end
1539
+
1540
+ # Tries to find a resource for a given collection name; if it fails, then the resource is created
1541
+ def find_or_create_resource_for_collection(name)
1542
+ return reflections[name.to_sym].klass if reflections.key?(name.to_sym)
1543
+ find_or_create_resource_for(ActiveSupport::Inflector.singularize(name.to_s))
1544
+ end
1545
+
1546
+ # Tries to find a resource in a non empty list of nested modules
1547
+ # if it fails, then the resource is created
1548
+ def find_or_create_resource_in_modules(resource_name, module_names)
1549
+ receiver = Object
1550
+ namespaces = module_names[0, module_names.size-1].map do |module_name|
1551
+ receiver = receiver.const_get(module_name)
1552
+ end
1553
+ const_args = [resource_name, false]
1554
+ if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(*const_args) }
1555
+ namespace.const_get(*const_args)
1556
+ else
1557
+ create_resource_for(resource_name)
1558
+ end
1559
+ end
1560
+
1561
+ # Tries to find a resource for a given name; if it fails, then the resource is created
1562
+ def find_or_create_resource_for(name)
1563
+ return reflections[name.to_sym].klass if reflections.key?(name.to_sym)
1564
+ resource_name = name.to_s.camelize
1565
+
1566
+ const_args = [resource_name, false]
1567
+ if self.class.const_defined?(*const_args)
1568
+ self.class.const_get(*const_args)
1569
+ else
1570
+ ancestors = self.class.name.to_s.split("::")
1571
+ if ancestors.size > 1
1572
+ find_or_create_resource_in_modules(resource_name, ancestors)
1573
+ else
1574
+ if Object.const_defined?(*const_args)
1575
+ Object.const_get(*const_args)
1576
+ else
1577
+ create_resource_for(resource_name)
1578
+ end
1579
+ end
1580
+ end
1581
+ end
1582
+
1583
+ # Create and return a class definition for a resource inside the current resource
1584
+ def create_resource_for(resource_name)
1585
+ resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
1586
+ resource.prefix = self.class.prefix
1587
+ resource.site = self.class.site
1588
+ resource
1589
+ end
1590
+
1591
+ def split_options(options = {})
1592
+ self.class.__send__(:split_options, options)
1593
+ end
1594
+
1595
+ def method_missing(method_symbol, *arguments) #:nodoc:
1596
+ method_name = method_symbol.to_s
1597
+
1598
+ if method_name =~ /(=|\?)$/
1599
+ case $1
1600
+ when "="
1601
+ attributes[$`] = arguments.first
1602
+ when "?"
1603
+ attributes[$`]
1604
+ end
1605
+ else
1606
+ return attributes[method_name] if attributes.include?(method_name)
1607
+ # not set right now but we know about it
1608
+ return nil if known_attributes.include?(method_name)
1609
+ super
1610
+ end
1611
+ end
1612
+ end
1613
+
1614
+ class Base
1615
+ extend ActiveModel::Naming
1616
+ extend ActiveResource::Associations
1617
+
1618
+ include Callbacks, CustomMethods, Validations
1619
+ include ActiveModel::Conversion
1620
+ include ActiveModel::Serializers::JSON
1621
+ include ActiveModel::Serializers::Xml
1622
+ include ActiveResource::Reflection
1623
+ end
1624
+
1625
+ ActiveSupport.run_load_hooks(:active_resource, Base)
1626
+ end