activeresource_csi 2.3.5.p6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1157 @@
1
+ require 'active_resource/connection'
2
+ require 'cgi'
3
+ require 'set'
4
+
5
+ module ActiveResource
6
+ # ActiveResource::Base is the main class for mapping RESTful resources as models in a Rails application.
7
+ #
8
+ # For an outline of what Active Resource is capable of, see link:files/vendor/rails/activeresource/README.html.
9
+ #
10
+ # == Automated mapping
11
+ #
12
+ # Active Resource objects represent your RESTful resources as manipulatable Ruby objects. To map resources
13
+ # to Ruby objects, Active Resource only needs a class name that corresponds to the resource name (e.g., the class
14
+ # Person maps to the resources people, very similarly to Active Record) and a +site+ value, which holds the
15
+ # URI of the resources.
16
+ #
17
+ # class Person < ActiveResource::Base
18
+ # self.site = "http://api.people.com:3000/"
19
+ # end
20
+ #
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. 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
+ #
30
+ #
31
+ # == Lifecycle methods
32
+ #
33
+ # Active Resource exposes methods for creating, finding, updating, and deleting resources
34
+ # from REST web services.
35
+ #
36
+ # ryan = Person.new(:first => 'Ryan', :last => 'Daigle')
37
+ # ryan.save # => true
38
+ # ryan.id # => 2
39
+ # Person.exists?(ryan.id) # => true
40
+ # ryan.exists? # => true
41
+ #
42
+ # ryan = Person.find(1)
43
+ # # Resource holding our newly created Person object
44
+ #
45
+ # ryan.first = 'Rizzle'
46
+ # ryan.save # => true
47
+ #
48
+ # ryan.destroy # => true
49
+ #
50
+ # As you can see, these are very similar to Active Record's lifecycle methods for database records.
51
+ # You can read more about each of these methods in their respective documentation.
52
+ #
53
+ # === Custom REST methods
54
+ #
55
+ # Since simple CRUD/lifecycle methods can't accomplish every task, Active Resource also supports
56
+ # defining your own custom REST methods. To invoke them, Active Resource provides the <tt>get</tt>,
57
+ # <tt>post</tt>, <tt>put</tt> and <tt>\delete</tt> methods where you can specify a custom REST method
58
+ # name to invoke.
59
+ #
60
+ # # POST to the custom 'register' REST method, i.e. POST /people/new/register.xml.
61
+ # Person.new(:name => 'Ryan').post(:register)
62
+ # # => { :id => 1, :name => 'Ryan', :position => 'Clerk' }
63
+ #
64
+ # # PUT an update by invoking the 'promote' REST method, i.e. PUT /people/1/promote.xml?position=Manager.
65
+ # Person.find(1).put(:promote, :position => 'Manager')
66
+ # # => { :id => 1, :name => 'Ryan', :position => 'Manager' }
67
+ #
68
+ # # GET all the positions available, i.e. GET /people/positions.xml.
69
+ # Person.get(:positions)
70
+ # # => [{:name => 'Manager'}, {:name => 'Clerk'}]
71
+ #
72
+ # # DELETE to 'fire' a person, i.e. DELETE /people/1/fire.xml.
73
+ # Person.find(1).delete(:fire)
74
+ #
75
+ # For more information on using custom REST methods, see the
76
+ # ActiveResource::CustomMethods documentation.
77
+ #
78
+ # == Validations
79
+ #
80
+ # You can validate resources client side by overriding validation methods in the base class.
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
89
+ #
90
+ # See the ActiveResource::Validations documentation for more information.
91
+ #
92
+ # == Authentication
93
+ #
94
+ # Many REST APIs will require authentication, usually in the form of basic
95
+ # HTTP authentication. Authentication can be specified by:
96
+ #
97
+ # === HTTP Basic Authentication
98
+ # * putting the credentials in the URL for the +site+ variable.
99
+ #
100
+ # class Person < ActiveResource::Base
101
+ # self.site = "http://ryan:password@api.people.com:3000/"
102
+ # end
103
+ #
104
+ # * defining +user+ and/or +password+ variables
105
+ #
106
+ # class Person < ActiveResource::Base
107
+ # self.site = "http://api.people.com:3000/"
108
+ # self.user = "ryan"
109
+ # self.password = "password"
110
+ # end
111
+ #
112
+ # For obvious security reasons, it is probably best if such services are available
113
+ # over HTTPS.
114
+ #
115
+ # Note: Some values cannot be provided in the URL passed to site. e.g. email addresses
116
+ # as usernames. In those situations you should use the separate user and password option.
117
+ #
118
+ # === Certificate Authentication
119
+ #
120
+ # * End point uses an X509 certificate for authentication. <tt>See ssl_options=</tt> for all options.
121
+ #
122
+ # class Person < ActiveResource::Base
123
+ # self.site = "https://secure.api.people.com/"
124
+ # self.ssl_options = {:cert => OpenSSL::X509::Certificate.new(File.open(pem_file))
125
+ # :key => OpenSSL::PKey::RSA.new(File.open(pem_file)),
126
+ # :ca_path => "/path/to/OpenSSL/formatted/CA_Certs",
127
+ # :verify_mode => OpenSSL::SSL::VERIFY_PEER}
128
+ # end
129
+ #
130
+ # == Errors & Validation
131
+ #
132
+ # Error handling and validation is handled in much the same manner as you're used to seeing in
133
+ # Active Record. Both the response code in the HTTP response and the body of the response are used to
134
+ # indicate that an error occurred.
135
+ #
136
+ # === Resource errors
137
+ #
138
+ # When a GET is requested for a resource that does not exist, the HTTP <tt>404</tt> (Resource Not Found)
139
+ # response code will be returned from the server which will raise an ActiveResource::ResourceNotFound
140
+ # exception.
141
+ #
142
+ # # GET http://api.people.com:3000/people/999.xml
143
+ # ryan = Person.find(999) # 404, raises ActiveResource::ResourceNotFound
144
+ #
145
+ # <tt>404</tt> is just one of the HTTP error response codes that Active Resource will handle with its own exception. The
146
+ # following HTTP response codes will also result in these exceptions:
147
+ #
148
+ # * 200..399 - Valid response, no exception (other than 301, 302)
149
+ # * 301, 302 - ActiveResource::Redirection
150
+ # * 400 - ActiveResource::BadRequest
151
+ # * 401 - ActiveResource::UnauthorizedAccess
152
+ # * 403 - ActiveResource::ForbiddenAccess
153
+ # * 404 - ActiveResource::ResourceNotFound
154
+ # * 405 - ActiveResource::MethodNotAllowed
155
+ # * 409 - ActiveResource::ResourceConflict
156
+ # * 410 - ActiveResource::ResourceGone
157
+ # * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors)
158
+ # * 401..499 - ActiveResource::ClientError
159
+ # * 500..599 - ActiveResource::ServerError
160
+ # * Other - ActiveResource::ConnectionError
161
+ #
162
+ # These custom exceptions allow you to deal with resource errors more naturally and with more precision
163
+ # rather than returning a general HTTP error. For example:
164
+ #
165
+ # begin
166
+ # ryan = Person.find(my_id)
167
+ # rescue ActiveResource::ResourceNotFound
168
+ # redirect_to :action => 'not_found'
169
+ # rescue ActiveResource::ResourceConflict, ActiveResource::ResourceInvalid
170
+ # redirect_to :action => 'new'
171
+ # end
172
+ #
173
+ # === Validation errors
174
+ #
175
+ # Active Resource supports validations on resources and will return errors if any these validations fail
176
+ # (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by
177
+ # a response code of <tt>422</tt> and an XML or JSON representation of the validation errors. The save operation will
178
+ # then fail (with a <tt>false</tt> return value) and the validation errors can be accessed on the resource in question.
179
+ #
180
+ # ryan = Person.find(1)
181
+ # ryan.first # => ''
182
+ # ryan.save # => false
183
+ #
184
+ # # When
185
+ # # PUT http://api.people.com:3000/people/1.xml
186
+ # # or
187
+ # # PUT http://api.people.com:3000/people/1.json
188
+ # # is requested with invalid values, the response is:
189
+ # #
190
+ # # Response (422):
191
+ # # <errors type="array"><error>First cannot be empty</error></errors>
192
+ # # or
193
+ # # {"errors":["First cannot be empty"]}
194
+ # #
195
+ #
196
+ # ryan.errors.invalid?(:first) # => true
197
+ # ryan.errors.full_messages # => ['First cannot be empty']
198
+ #
199
+ # Learn more about Active Resource's validation features in the ActiveResource::Validations documentation.
200
+ #
201
+ # === Timeouts
202
+ #
203
+ # Active Resource relies on HTTP to access RESTful APIs and as such is inherently susceptible to slow or
204
+ # unresponsive servers. In such cases, your Active Resource method calls could \timeout. You can control the
205
+ # amount of time before Active Resource times out with the +timeout+ variable.
206
+ #
207
+ # class Person < ActiveResource::Base
208
+ # self.site = "http://api.people.com:3000/"
209
+ # self.timeout = 5
210
+ # end
211
+ #
212
+ # This sets the +timeout+ to 5 seconds. You can adjust the +timeout+ to a value suitable for the RESTful API
213
+ # you are accessing. It is recommended to set this to a reasonably low value to allow your Active Resource
214
+ # clients (especially if you are using Active Resource in a Rails application) to fail-fast (see
215
+ # http://en.wikipedia.org/wiki/Fail-fast) rather than cause cascading failures that could incapacitate your
216
+ # server.
217
+ #
218
+ # When a \timeout occurs, an ActiveResource::TimeoutError is raised. You should rescue from
219
+ # ActiveResource::TimeoutError in your Active Resource method calls.
220
+ #
221
+ # Internally, Active Resource relies on Ruby's Net::HTTP library to make HTTP requests. Setting +timeout+
222
+ # sets the <tt>read_timeout</tt> of the internal Net::HTTP instance to the same value. The default
223
+ # <tt>read_timeout</tt> is 60 seconds on most Ruby implementations.
224
+ class Base
225
+ ##
226
+ # :singleton-method:
227
+ # The logger for diagnosing and tracing Active Resource calls.
228
+ cattr_accessor :logger
229
+
230
+ class << self
231
+ # Gets the URI of the REST resources to map for this class. The site variable is required for
232
+ # Active Resource's mapping to work.
233
+ def site
234
+ # Not using superclass_delegating_reader because don't want subclasses to modify superclass instance
235
+ #
236
+ # With superclass_delegating_reader
237
+ #
238
+ # Parent.site = 'http://anonymous@test.com'
239
+ # Subclass.site # => 'http://anonymous@test.com'
240
+ # Subclass.site.user = 'david'
241
+ # Parent.site # => 'http://david@test.com'
242
+ #
243
+ # Without superclass_delegating_reader (expected behaviour)
244
+ #
245
+ # Parent.site = 'http://anonymous@test.com'
246
+ # Subclass.site # => 'http://anonymous@test.com'
247
+ # Subclass.site.user = 'david' # => TypeError: can't modify frozen object
248
+ #
249
+ if defined?(@site)
250
+ @site
251
+ elsif superclass != Object && superclass.site
252
+ superclass.site.dup.freeze
253
+ end
254
+ end
255
+
256
+ # Sets the URI of the REST resources to map for this class to the value in the +site+ argument.
257
+ # The site variable is required for Active Resource's mapping to work.
258
+ def site=(site)
259
+ @connection = nil
260
+ if site.nil?
261
+ @site = nil
262
+ else
263
+ @site = create_site_uri_from(site)
264
+ @user = URI.decode(@site.user) if @site.user
265
+ @password = URI.decode(@site.password) if @site.password
266
+ end
267
+ end
268
+
269
+ # Gets the \proxy variable if a proxy is required
270
+ def proxy
271
+ # Not using superclass_delegating_reader. See +site+ for explanation
272
+ if defined?(@proxy)
273
+ @proxy
274
+ elsif superclass != Object && superclass.proxy
275
+ superclass.proxy.dup.freeze
276
+ end
277
+ end
278
+
279
+ # Sets the URI of the http proxy to the value in the +proxy+ argument.
280
+ def proxy=(proxy)
281
+ @connection = nil
282
+ @proxy = proxy.nil? ? nil : create_proxy_uri_from(proxy)
283
+ end
284
+
285
+ # Gets the \user for REST HTTP authentication.
286
+ def user
287
+ # Not using superclass_delegating_reader. See +site+ for explanation
288
+ if defined?(@user)
289
+ @user
290
+ elsif superclass != Object && superclass.user
291
+ superclass.user.dup.freeze
292
+ end
293
+ end
294
+
295
+ # Sets the \user for REST HTTP authentication.
296
+ def user=(user)
297
+ @connection = nil
298
+ @user = user
299
+ end
300
+
301
+ # Gets the \password for REST HTTP authentication.
302
+ def password
303
+ # Not using superclass_delegating_reader. See +site+ for explanation
304
+ if defined?(@password)
305
+ @password
306
+ elsif superclass != Object && superclass.password
307
+ superclass.password.dup.freeze
308
+ end
309
+ end
310
+
311
+ # Sets the \password for REST HTTP authentication.
312
+ def password=(password)
313
+ @connection = nil
314
+ @password = password
315
+ end
316
+
317
+ # Sets the format that attributes are sent and received in from a mime type reference:
318
+ #
319
+ # Person.format = :json
320
+ # Person.find(1) # => GET /people/1.json
321
+ #
322
+ # Person.format = ActiveResource::Formats::XmlFormat
323
+ # Person.find(1) # => GET /people/1.xml
324
+ #
325
+ # Default format is <tt>:xml</tt>.
326
+ def format=(mime_type_reference_or_format)
327
+ format = mime_type_reference_or_format.is_a?(Symbol) ?
328
+ ActiveResource::Formats[mime_type_reference_or_format] : mime_type_reference_or_format
329
+
330
+ write_inheritable_attribute(:format, format)
331
+ connection.format = format if site
332
+ end
333
+
334
+ # Returns the current format, default is ActiveResource::Formats::XmlFormat.
335
+ def format
336
+ read_inheritable_attribute(:format) || ActiveResource::Formats[:xml]
337
+ end
338
+
339
+ # Sets the number of seconds after which requests to the REST API should time out.
340
+ def timeout=(timeout)
341
+ @connection = nil
342
+ @timeout = timeout
343
+ end
344
+
345
+ # Gets the number of seconds after which requests to the REST API should time out.
346
+ def timeout
347
+ if defined?(@timeout)
348
+ @timeout
349
+ elsif superclass != Object && superclass.timeout
350
+ superclass.timeout
351
+ end
352
+ end
353
+
354
+ # Options that will get applied to an SSL connection.
355
+ #
356
+ # * <tt>:key</tt> - An OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
357
+ # * <tt>:cert</tt> - An OpenSSL::X509::Certificate object as client certificate
358
+ # * <tt>:ca_file</tt> - Path to a CA certification file in PEM format. The file can contrain several CA certificates.
359
+ # * <tt>:ca_path</tt> - Path of a CA certification directory containing certifications in PEM format.
360
+ # * <tt>:verify_mode</tt> - Flags for server the certification verification at begining of SSL/TLS session. (OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable)
361
+ # * <tt>:verify_callback</tt> - The verify callback for the server certification verification.
362
+ # * <tt>:verify_depth</tt> - The maximum depth for the certificate chain verification.
363
+ # * <tt>:cert_store</tt> - OpenSSL::X509::Store to verify peer certificate.
364
+ # * <tt>:ssl_timeout</tt> -The SSL timeout in seconds.
365
+ def ssl_options=(opts={})
366
+ @connection = nil
367
+ @ssl_options = opts
368
+ end
369
+
370
+ # Returns the SSL options hash.
371
+ def ssl_options
372
+ if defined?(@ssl_options)
373
+ @ssl_options
374
+ elsif superclass != Object && superclass.ssl_options
375
+ superclass.ssl_options
376
+ end
377
+ end
378
+
379
+ # An instance of ActiveResource::Connection that is the base \connection to the remote service.
380
+ # The +refresh+ parameter toggles whether or not the \connection is refreshed at every request
381
+ # or not (defaults to <tt>false</tt>).
382
+ def connection(refresh = false)
383
+ if defined?(@connection) || superclass == Object
384
+ @connection = Connection.new(site, format) if refresh || @connection.nil?
385
+ @connection.proxy = proxy if proxy
386
+ @connection.user = user if user
387
+ @connection.password = password if password
388
+ @connection.timeout = timeout if timeout
389
+ @connection.ssl_options = ssl_options if ssl_options
390
+ @connection
391
+ else
392
+ superclass.connection
393
+ end
394
+ end
395
+
396
+ def headers
397
+ @headers ||= {}
398
+ end
399
+
400
+ # Do not include any modules in the default element name. This makes it easier to seclude ARes objects
401
+ # in a separate namespace without having to set element_name repeatedly.
402
+ attr_accessor_with_default(:element_name) { to_s.split("::").last.underscore } #:nodoc:
403
+
404
+ attr_accessor_with_default(:collection_name) { element_name.pluralize } #:nodoc:
405
+ attr_accessor_with_default(:primary_key, 'id') #:nodoc:
406
+
407
+ # Gets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>)
408
+ # This method is regenerated at runtime based on what the \prefix is set to.
409
+ def prefix(options={})
410
+ default = site.path
411
+ default << '/' unless default[-1..-1] == '/'
412
+ # generate the actual method based on the current site path
413
+ self.prefix = default
414
+ prefix(options)
415
+ end
416
+
417
+ # An attribute reader for the source string for the resource path \prefix. This
418
+ # method is regenerated at runtime based on what the \prefix is set to.
419
+ def prefix_source
420
+ prefix # generate #prefix and #prefix_source methods first
421
+ prefix_source
422
+ end
423
+
424
+ # Sets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>).
425
+ # Default value is <tt>site.path</tt>.
426
+ def prefix=(value = '/')
427
+ # Replace :placeholders with '#{embedded options[:lookups]}'
428
+ prefix_call = value.gsub(/:\w+/) { |key| "\#{options[#{key}]}" }
429
+
430
+ # Clear prefix parameters in case they have been cached
431
+ @prefix_parameters = nil
432
+
433
+ # Redefine the new methods.
434
+ code = <<-end_code
435
+ def prefix_source() "#{value}" end
436
+ def prefix(options={}) "#{prefix_call}" end
437
+ end_code
438
+ silence_warnings { instance_eval code, __FILE__, __LINE__ }
439
+ rescue
440
+ logger.error "Couldn't set prefix: #{$!}\n #{code}"
441
+ raise
442
+ end
443
+
444
+ alias_method :set_prefix, :prefix= #:nodoc:
445
+
446
+ alias_method :set_element_name, :element_name= #:nodoc:
447
+ alias_method :set_collection_name, :collection_name= #:nodoc:
448
+
449
+ # Gets the element path for the given ID in +id+. If the +query_options+ parameter is omitted, Rails
450
+ # will split from the \prefix options.
451
+ #
452
+ # ==== Options
453
+ # +prefix_options+ - A \hash to add a \prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
454
+ # would yield a URL like <tt>/accounts/19/purchases.xml</tt>).
455
+ # +query_options+ - A \hash to add items to the query string for the request.
456
+ #
457
+ # ==== Examples
458
+ # Post.element_path(1)
459
+ # # => /posts/1.xml
460
+ #
461
+ # Comment.element_path(1, :post_id => 5)
462
+ # # => /posts/5/comments/1.xml
463
+ #
464
+ # Comment.element_path(1, :post_id => 5, :active => 1)
465
+ # # => /posts/5/comments/1.xml?active=1
466
+ #
467
+ # Comment.element_path(1, {:post_id => 5}, {:active => 1})
468
+ # # => /posts/5/comments/1.xml?active=1
469
+ #
470
+ def element_path(id, prefix_options = {}, query_options = nil)
471
+ prefix_options, query_options = split_options(prefix_options) if query_options.nil?
472
+ "#{prefix(prefix_options)}#{collection_name}/#{id}.#{format.extension}#{query_string(query_options)}"
473
+ end
474
+
475
+ # Gets the collection path for the REST resources. If the +query_options+ parameter is omitted, Rails
476
+ # will split from the +prefix_options+.
477
+ #
478
+ # ==== Options
479
+ # * +prefix_options+ - A hash to add a prefix to the request for nested URL's (e.g., <tt>:account_id => 19</tt>
480
+ # would yield a URL like <tt>/accounts/19/purchases.xml</tt>).
481
+ # * +query_options+ - A hash to add items to the query string for the request.
482
+ #
483
+ # ==== Examples
484
+ # Post.collection_path
485
+ # # => /posts.xml
486
+ #
487
+ # Comment.collection_path(:post_id => 5)
488
+ # # => /posts/5/comments.xml
489
+ #
490
+ # Comment.collection_path(:post_id => 5, :active => 1)
491
+ # # => /posts/5/comments.xml?active=1
492
+ #
493
+ # Comment.collection_path({:post_id => 5}, {:active => 1})
494
+ # # => /posts/5/comments.xml?active=1
495
+ #
496
+ def collection_path(prefix_options = {}, query_options = nil)
497
+ prefix_options, query_options = split_options(prefix_options) if query_options.nil?
498
+ "#{prefix(prefix_options)}#{collection_name}.#{format.extension}#{query_string(query_options)}"
499
+ end
500
+
501
+ alias_method :set_primary_key, :primary_key= #:nodoc:
502
+
503
+ # Creates a new resource instance and makes a request to the remote service
504
+ # that it be saved, making it equivalent to the following simultaneous calls:
505
+ #
506
+ # ryan = Person.new(:first => 'ryan')
507
+ # ryan.save
508
+ #
509
+ # Returns the newly created resource. If a failure has occurred an
510
+ # exception will be raised (see <tt>save</tt>). If the resource is invalid and
511
+ # has not been saved then <tt>valid?</tt> will return <tt>false</tt>,
512
+ # while <tt>new?</tt> will still return <tt>true</tt>.
513
+ #
514
+ # ==== Examples
515
+ # Person.create(:name => 'Jeremy', :email => 'myname@nospam.com', :enabled => true)
516
+ # my_person = Person.find(:first)
517
+ # my_person.email # => myname@nospam.com
518
+ #
519
+ # dhh = Person.create(:name => 'David', :email => 'dhh@nospam.com', :enabled => true)
520
+ # dhh.valid? # => true
521
+ # dhh.new? # => false
522
+ #
523
+ # # We'll assume that there's a validation that requires the name attribute
524
+ # that_guy = Person.create(:name => '', :email => 'thatguy@nospam.com', :enabled => true)
525
+ # that_guy.valid? # => false
526
+ # that_guy.new? # => true
527
+ def create(attributes = {})
528
+ self.new(attributes).tap { |resource| resource.save }
529
+ end
530
+
531
+ # Core method for finding resources. Used similarly to Active Record's +find+ method.
532
+ #
533
+ # ==== Arguments
534
+ # The first argument is considered to be the scope of the query. That is, how many
535
+ # resources are returned from the request. It can be one of the following.
536
+ #
537
+ # * <tt>:one</tt> - Returns a single resource.
538
+ # * <tt>:first</tt> - Returns the first resource found.
539
+ # * <tt>:last</tt> - Returns the last resource found.
540
+ # * <tt>:all</tt> - Returns every resource that matches the request.
541
+ #
542
+ # ==== Options
543
+ #
544
+ # * <tt>:from</tt> - Sets the path or custom method that resources will be fetched from.
545
+ # * <tt>:params</tt> - Sets query and \prefix (nested URL) parameters.
546
+ #
547
+ # ==== Examples
548
+ # Person.find(1)
549
+ # # => GET /people/1.xml
550
+ #
551
+ # Person.find(:all)
552
+ # # => GET /people.xml
553
+ #
554
+ # Person.find(:all, :params => { :title => "CEO" })
555
+ # # => GET /people.xml?title=CEO
556
+ #
557
+ # Person.find(:first, :from => :managers)
558
+ # # => GET /people/managers.xml
559
+ #
560
+ # Person.find(:last, :from => :managers)
561
+ # # => GET /people/managers.xml
562
+ #
563
+ # Person.find(:all, :from => "/companies/1/people.xml")
564
+ # # => GET /companies/1/people.xml
565
+ #
566
+ # Person.find(:one, :from => :leader)
567
+ # # => GET /people/leader.xml
568
+ #
569
+ # Person.find(:all, :from => :developers, :params => { :language => 'ruby' })
570
+ # # => GET /people/developers.xml?language=ruby
571
+ #
572
+ # Person.find(:one, :from => "/companies/1/manager.xml")
573
+ # # => GET /companies/1/manager.xml
574
+ #
575
+ # StreetAddress.find(1, :params => { :person_id => 1 })
576
+ # # => GET /people/1/street_addresses/1.xml
577
+ def find(*arguments)
578
+ scope = arguments.slice!(0)
579
+ options = arguments.slice!(0) || {}
580
+
581
+ case scope
582
+ when :all then find_every(options)
583
+ when :first then find_every(options).first
584
+ when :last then find_every(options).last
585
+ when :one then find_one(options)
586
+ else find_single(scope, options)
587
+ end
588
+ end
589
+
590
+ # Deletes the resources with the ID in the +id+ parameter.
591
+ #
592
+ # ==== Options
593
+ # All options specify \prefix and query parameters.
594
+ #
595
+ # ==== Examples
596
+ # Event.delete(2) # sends DELETE /events/2
597
+ #
598
+ # Event.create(:name => 'Free Concert', :location => 'Community Center')
599
+ # my_event = Event.find(:first) # let's assume this is event with ID 7
600
+ # Event.delete(my_event.id) # sends DELETE /events/7
601
+ #
602
+ # # Let's assume a request to events/5/cancel.xml
603
+ # Event.delete(params[:id]) # sends DELETE /events/5
604
+ def delete(id, options = {})
605
+ connection.delete(element_path(id, options))
606
+ end
607
+
608
+ # Asserts the existence of a resource, returning <tt>true</tt> if the resource is found.
609
+ #
610
+ # ==== Examples
611
+ # Note.create(:title => 'Hello, world.', :body => 'Nothing more for now...')
612
+ # Note.exists?(1) # => true
613
+ #
614
+ # Note.exists(1349) # => false
615
+ def exists?(id, options = {})
616
+ if id
617
+ prefix_options, query_options = split_options(options[:params])
618
+ path = element_path(id, prefix_options, query_options)
619
+ response = connection.head(path, headers)
620
+ response.code.to_i == 200
621
+ end
622
+ # id && !find_single(id, options).nil?
623
+ rescue ActiveResource::ResourceNotFound, ActiveResource::ResourceGone
624
+ false
625
+ end
626
+
627
+ private
628
+ # Find every resource
629
+ def find_every(options)
630
+ case from = options[:from]
631
+ when Symbol
632
+ instantiate_collection(get(from, options[:params]))
633
+ when String
634
+ path = "#{from}#{query_string(options[:params])}"
635
+ instantiate_collection(connection.get(path, headers) || [])
636
+ else
637
+ prefix_options, query_options = split_options(options[:params])
638
+ path = collection_path(prefix_options, query_options)
639
+ instantiate_collection( (connection.get(path, headers) || []), prefix_options )
640
+ end
641
+ end
642
+
643
+ # Find a single resource from a one-off URL
644
+ def find_one(options)
645
+ case from = options[:from]
646
+ when Symbol
647
+ instantiate_record(get(from, options[:params]))
648
+ when String
649
+ path = "#{from}#{query_string(options[:params])}"
650
+ instantiate_record(connection.get(path, headers))
651
+ end
652
+ end
653
+
654
+ # Find a single resource from the default URL
655
+ def find_single(scope, options)
656
+ prefix_options, query_options = split_options(options[:params])
657
+ path = element_path(scope, prefix_options, query_options)
658
+ instantiate_record(connection.get(path, headers), prefix_options)
659
+ end
660
+
661
+ def instantiate_collection(collection, prefix_options = {})
662
+ collection.collect! { |record| instantiate_record(record, prefix_options) }
663
+ end
664
+
665
+ def instantiate_record(record, prefix_options = {})
666
+ new(record).tap do |resource|
667
+ resource.prefix_options = prefix_options
668
+ end
669
+ end
670
+
671
+
672
+ # Accepts a URI and creates the site URI from that.
673
+ def create_site_uri_from(site)
674
+ site.is_a?(URI) ? site.dup : URI.parse(site)
675
+ end
676
+
677
+ # Accepts a URI and creates the proxy URI from that.
678
+ def create_proxy_uri_from(proxy)
679
+ proxy.is_a?(URI) ? proxy.dup : URI.parse(proxy)
680
+ end
681
+
682
+ # contains a set of the current prefix parameters.
683
+ def prefix_parameters
684
+ @prefix_parameters ||= prefix_source.scan(/:\w+/).map { |key| key[1..-1].to_sym }.to_set
685
+ end
686
+
687
+ # Builds the query string for the request.
688
+ def query_string(options)
689
+ "?#{options.to_query}" unless options.nil? || options.empty?
690
+ end
691
+
692
+ # split an option hash into two hashes, one containing the prefix options,
693
+ # and the other containing the leftovers.
694
+ def split_options(options = {})
695
+ prefix_options, query_options = {}, {}
696
+
697
+ (options || {}).each do |key, value|
698
+ next if key.blank?
699
+ (prefix_parameters.include?(key.to_sym) ? prefix_options : query_options)[key.to_sym] = value
700
+ end
701
+
702
+ [ prefix_options, query_options ]
703
+ end
704
+ end
705
+
706
+ attr_accessor :attributes #:nodoc:
707
+ attr_accessor :prefix_options #:nodoc:
708
+
709
+ # Constructor method for \new resources; the optional +attributes+ parameter takes a \hash
710
+ # of attributes for the \new resource.
711
+ #
712
+ # ==== Examples
713
+ # my_course = Course.new
714
+ # my_course.name = "Western Civilization"
715
+ # my_course.lecturer = "Don Trotter"
716
+ # my_course.save
717
+ #
718
+ # my_other_course = Course.new(:name => "Philosophy: Reason and Being", :lecturer => "Ralph Cling")
719
+ # my_other_course.save
720
+ def initialize(attributes = {})
721
+ @attributes = {}
722
+ @prefix_options = {}
723
+ load(attributes)
724
+ end
725
+
726
+ # Returns a \clone of the resource that hasn't been assigned an +id+ yet and
727
+ # is treated as a \new resource.
728
+ #
729
+ # ryan = Person.find(1)
730
+ # not_ryan = ryan.clone
731
+ # not_ryan.new? # => true
732
+ #
733
+ # Any active resource member attributes will NOT be cloned, though all other
734
+ # attributes are. This is to prevent the conflict between any +prefix_options+
735
+ # that refer to the original parent resource and the newly cloned parent
736
+ # resource that does not exist.
737
+ #
738
+ # ryan = Person.find(1)
739
+ # ryan.address = StreetAddress.find(1, :person_id => ryan.id)
740
+ # ryan.hash = {:not => "an ARes instance"}
741
+ #
742
+ # not_ryan = ryan.clone
743
+ # not_ryan.new? # => true
744
+ # not_ryan.address # => NoMethodError
745
+ # not_ryan.hash # => {:not => "an ARes instance"}
746
+ def clone
747
+ # Clone all attributes except the pk and any nested ARes
748
+ cloned = attributes.reject {|k,v| k == self.class.primary_key || v.is_a?(ActiveResource::Base)}.inject({}) do |attrs, (k, v)|
749
+ attrs[k] = v.clone
750
+ attrs
751
+ end
752
+ # Form the new resource - bypass initialize of resource with 'new' as that will call 'load' which
753
+ # attempts to convert hashes into member objects and arrays into collections of objects. We want
754
+ # the raw objects to be cloned so we bypass load by directly setting the attributes hash.
755
+ resource = self.class.new({})
756
+ resource.prefix_options = self.prefix_options
757
+ resource.send :instance_variable_set, '@attributes', cloned
758
+ resource
759
+ end
760
+
761
+
762
+ # A method to determine if the resource a \new object (i.e., it has not been POSTed to the remote service yet).
763
+ #
764
+ # ==== Examples
765
+ # not_new = Computer.create(:brand => 'Apple', :make => 'MacBook', :vendor => 'MacMall')
766
+ # not_new.new? # => false
767
+ #
768
+ # is_new = Computer.new(:brand => 'IBM', :make => 'Thinkpad', :vendor => 'IBM')
769
+ # is_new.new? # => true
770
+ #
771
+ # is_new.save
772
+ # is_new.new? # => false
773
+ #
774
+ def new?
775
+ id.nil?
776
+ end
777
+ alias :new_record? :new?
778
+
779
+ # Gets the <tt>\id</tt> attribute of the resource.
780
+ def id
781
+ attributes[self.class.primary_key]
782
+ end
783
+
784
+ # Sets the <tt>\id</tt> attribute of the resource.
785
+ def id=(id)
786
+ attributes[self.class.primary_key] = id
787
+ end
788
+
789
+ # Allows Active Resource objects to be used as parameters in Action Pack URL generation.
790
+ def to_param
791
+ id && id.to_s
792
+ end
793
+
794
+ # Test for equality. Resource are equal if and only if +other+ is the same object or
795
+ # is an instance of the same class, is not <tt>new?</tt>, and has the same +id+.
796
+ #
797
+ # ==== Examples
798
+ # ryan = Person.create(:name => 'Ryan')
799
+ # jamie = Person.create(:name => 'Jamie')
800
+ #
801
+ # ryan == jamie
802
+ # # => false (Different name attribute and id)
803
+ #
804
+ # ryan_again = Person.new(:name => 'Ryan')
805
+ # ryan == ryan_again
806
+ # # => false (ryan_again is new?)
807
+ #
808
+ # ryans_clone = Person.create(:name => 'Ryan')
809
+ # ryan == ryans_clone
810
+ # # => false (Different id attributes)
811
+ #
812
+ # ryans_twin = Person.find(ryan.id)
813
+ # ryan == ryans_twin
814
+ # # => true
815
+ #
816
+ def ==(other)
817
+ other.equal?(self) || (other.instance_of?(self.class) && other.id == id && other.prefix_options == prefix_options)
818
+ end
819
+
820
+ # Tests for equality (delegates to ==).
821
+ def eql?(other)
822
+ self == other
823
+ end
824
+
825
+ # Delegates to id in order to allow two resources of the same type and \id to work with something like:
826
+ # [Person.find(1), Person.find(2)] & [Person.find(1), Person.find(4)] # => [Person.find(1)]
827
+ def hash
828
+ id.hash
829
+ end
830
+
831
+ # Duplicate the current resource without saving it.
832
+ #
833
+ # ==== Examples
834
+ # my_invoice = Invoice.create(:customer => 'That Company')
835
+ # next_invoice = my_invoice.dup
836
+ # next_invoice.new? # => true
837
+ #
838
+ # next_invoice.save
839
+ # next_invoice == my_invoice # => false (different id attributes)
840
+ #
841
+ # my_invoice.customer # => That Company
842
+ # next_invoice.customer # => That Company
843
+ def dup
844
+ self.class.new.tap do |resource|
845
+ resource.attributes = @attributes
846
+ resource.prefix_options = @prefix_options
847
+ end
848
+ end
849
+
850
+ # A method to \save (+POST+) or \update (+PUT+) a resource. It delegates to +create+ if a \new object,
851
+ # +update+ if it is existing. If the response to the \save includes a body, it will be assumed that this body
852
+ # is XML for the final object as it looked after the \save (which would include attributes like +created_at+
853
+ # that weren't part of the original submit).
854
+ #
855
+ # ==== Examples
856
+ # my_company = Company.new(:name => 'RoleModel Software', :owner => 'Ken Auer', :size => 2)
857
+ # my_company.new? # => true
858
+ # my_company.save # sends POST /companies/ (create)
859
+ #
860
+ # my_company.new? # => false
861
+ # my_company.size = 10
862
+ # my_company.save # sends PUT /companies/1 (update)
863
+ def save
864
+ new? ? create : update
865
+ end
866
+
867
+ # Deletes the resource from the remote service.
868
+ #
869
+ # ==== Examples
870
+ # my_id = 3
871
+ # my_person = Person.find(my_id)
872
+ # my_person.destroy
873
+ # Person.find(my_id) # 404 (Resource Not Found)
874
+ #
875
+ # new_person = Person.create(:name => 'James')
876
+ # new_id = new_person.id # => 7
877
+ # new_person.destroy
878
+ # Person.find(new_id) # 404 (Resource Not Found)
879
+ def destroy
880
+ connection.delete(element_path, self.class.headers)
881
+ end
882
+
883
+ # Evaluates to <tt>true</tt> if this resource is not <tt>new?</tt> and is
884
+ # found on the remote service. Using this method, you can check for
885
+ # resources that may have been deleted between the object's instantiation
886
+ # and actions on it.
887
+ #
888
+ # ==== Examples
889
+ # Person.create(:name => 'Theodore Roosevelt')
890
+ # that_guy = Person.find(:first)
891
+ # that_guy.exists? # => true
892
+ #
893
+ # that_lady = Person.new(:name => 'Paul Bean')
894
+ # that_lady.exists? # => false
895
+ #
896
+ # guys_id = that_guy.id
897
+ # Person.delete(guys_id)
898
+ # that_guy.exists? # => false
899
+ def exists?
900
+ !new? && self.class.exists?(to_param, :params => prefix_options)
901
+ end
902
+
903
+ # Converts the resource to an XML string representation.
904
+ #
905
+ # ==== Options
906
+ # The +options+ parameter is handed off to the +to_xml+ method on each
907
+ # attribute, so it has the same options as the +to_xml+ methods in
908
+ # Active Support.
909
+ #
910
+ # * <tt>:indent</tt> - Set the indent level for the XML output (default is +2+).
911
+ # * <tt>:dasherize</tt> - Boolean option to determine whether or not element names should
912
+ # replace underscores with dashes. Default is <tt>true</tt>. The default can be set to <tt>false</tt>
913
+ # by setting the module attribute <tt>ActiveSupport.dasherize_xml = false</tt> in an initializer. Because save
914
+ # uses this method, and there are no options on save, then you will have to set the default if you don't
915
+ # want underscores in element names to become dashes when the resource is saved. This is important when
916
+ # integrating with non-Rails applications.
917
+ # * <tt>:camelize</tt> - Boolean option to determine whether or not element names should be converted
918
+ # to camel case, e.g some_name to SomeName. Default is <tt>false</tt>. Like <tt>:dasherize</tt> you can
919
+ # change the default by setting the module attribute <tt>ActiveSupport.camelise_xml = true</tt> in an initializer.
920
+ # * <tt>:skip_instruct</tt> - Toggle skipping the +instruct!+ call on the XML builder
921
+ # that generates the XML declaration (default is <tt>false</tt>).
922
+ #
923
+ # ==== Examples
924
+ # my_group = SubsidiaryGroup.find(:first)
925
+ # my_group.to_xml
926
+ # # => <?xml version="1.0" encoding="UTF-8"?>
927
+ # # <subsidiary_group> [...] </subsidiary_group>
928
+ #
929
+ # my_group.to_xml(:dasherize => true)
930
+ # # => <?xml version="1.0" encoding="UTF-8"?>
931
+ # # <subsidiary-group> [...] </subsidiary-group>
932
+ #
933
+ # my_group.to_xml(:skip_instruct => true)
934
+ # # => <subsidiary_group> [...] </subsidiary_group>
935
+ def to_xml(options={})
936
+ attributes.to_xml({:root => self.class.element_name}.merge(options))
937
+ end
938
+
939
+ # Coerces to a hash for JSON encoding.
940
+ #
941
+ # ==== Options
942
+ # The +options+ are passed to the +to_json+ method on each
943
+ # attribute, so the same options as the +to_json+ methods in
944
+ # Active Support.
945
+ #
946
+ # * <tt>:only</tt> - Only include the specified attribute or list of
947
+ # attributes in the serialized output. Attribute names must be specified
948
+ # as strings.
949
+ # * <tt>:except</tt> - Do not include the specified attribute or list of
950
+ # attributes in the serialized output. Attribute names must be specified
951
+ # as strings.
952
+ #
953
+ # ==== Examples
954
+ # person = Person.new(:first_name => "Jim", :last_name => "Smith")
955
+ # person.to_json
956
+ # # => {"first_name": "Jim", "last_name": "Smith"}
957
+ #
958
+ # person.to_json(:only => ["first_name"])
959
+ # # => {"first_name": "Jim"}
960
+ #
961
+ # person.to_json(:except => ["first_name"])
962
+ # # => {"last_name": "Smith"}
963
+ def as_json(options = nil)
964
+ attributes.as_json(options)
965
+ end
966
+
967
+ # Returns the serialized string representation of the resource in the configured
968
+ # serialization format specified in ActiveResource::Base.format. The options
969
+ # applicable depend on the configured encoding format.
970
+ def encode(options={})
971
+ case self.class.format
972
+ when ActiveResource::Formats[:xml]
973
+ self.class.format.encode(attributes, {:root => self.class.element_name}.merge(options))
974
+ else
975
+ self.class.format.encode(attributes, options)
976
+ end
977
+ end
978
+
979
+ # A method to \reload the attributes of this object from the remote web service.
980
+ #
981
+ # ==== Examples
982
+ # my_branch = Branch.find(:first)
983
+ # my_branch.name # => "Wislon Raod"
984
+ #
985
+ # # Another client fixes the typo...
986
+ #
987
+ # my_branch.name # => "Wislon Raod"
988
+ # my_branch.reload
989
+ # my_branch.name # => "Wilson Road"
990
+ def reload
991
+ self.load(self.class.find(to_param, :params => @prefix_options).attributes)
992
+ end
993
+
994
+ # A method to manually load attributes from a \hash. Recursively loads collections of
995
+ # resources. This method is called in +initialize+ and +create+ when a \hash of attributes
996
+ # is provided.
997
+ #
998
+ # ==== Examples
999
+ # my_attrs = {:name => 'J&J Textiles', :industry => 'Cloth and textiles'}
1000
+ # my_attrs = {:name => 'Marty', :colors => ["red", "green", "blue"]}
1001
+ #
1002
+ # the_supplier = Supplier.find(:first)
1003
+ # the_supplier.name # => 'J&M Textiles'
1004
+ # the_supplier.load(my_attrs)
1005
+ # the_supplier.name('J&J Textiles')
1006
+ #
1007
+ # # These two calls are the same as Supplier.new(my_attrs)
1008
+ # my_supplier = Supplier.new
1009
+ # my_supplier.load(my_attrs)
1010
+ #
1011
+ # # These three calls are the same as Supplier.create(my_attrs)
1012
+ # your_supplier = Supplier.new
1013
+ # your_supplier.load(my_attrs)
1014
+ # your_supplier.save
1015
+ def load(attributes)
1016
+ raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
1017
+ @prefix_options, attributes = split_options(attributes)
1018
+ attributes.each do |key, value|
1019
+ @attributes[key.to_s] =
1020
+ case value
1021
+ when Array
1022
+ resource = find_or_create_resource_for_collection(key)
1023
+ value.map do |attrs|
1024
+ if attrs.is_a?(String) || attrs.is_a?(Numeric)
1025
+ attrs.duplicable? ? attrs.dup : attrs
1026
+ else
1027
+ resource.new(attrs)
1028
+ end
1029
+ end
1030
+ when Hash
1031
+ resource = find_or_create_resource_for(key)
1032
+ resource.new(value)
1033
+ else
1034
+ value.dup rescue value
1035
+ end
1036
+ end
1037
+ self
1038
+ end
1039
+
1040
+ # For checking <tt>respond_to?</tt> without searching the attributes (which is faster).
1041
+ alias_method :respond_to_without_attributes?, :respond_to?
1042
+
1043
+ # A method to determine if an object responds to a message (e.g., a method call). In Active Resource, a Person object with a
1044
+ # +name+ attribute can answer <tt>true</tt> to <tt>my_person.respond_to?(:name)</tt>, <tt>my_person.respond_to?(:name=)</tt>, and
1045
+ # <tt>my_person.respond_to?(:name?)</tt>.
1046
+ def respond_to?(method, include_priv = false)
1047
+ method_name = method.to_s
1048
+ if attributes.nil?
1049
+ return super
1050
+ elsif attributes.has_key?(method_name)
1051
+ return true
1052
+ elsif ['?','='].include?(method_name.last) && attributes.has_key?(method_name.first(-1))
1053
+ return true
1054
+ end
1055
+ # super must be called at the end of the method, because the inherited respond_to?
1056
+ # would return true for generated readers, even if the attribute wasn't present
1057
+ super
1058
+ end
1059
+
1060
+
1061
+ protected
1062
+ def connection(refresh = false)
1063
+ self.class.connection(refresh)
1064
+ end
1065
+
1066
+ # Update the resource on the remote service.
1067
+ def update
1068
+ connection.put(element_path(prefix_options), encode, self.class.headers).tap do |response|
1069
+ load_attributes_from_response(response)
1070
+ end
1071
+ end
1072
+
1073
+ # Create (i.e., \save to the remote service) the \new resource.
1074
+ def create
1075
+ connection.post(collection_path, encode, self.class.headers).tap do |response|
1076
+ self.id = id_from_response(response)
1077
+ load_attributes_from_response(response)
1078
+ end
1079
+ end
1080
+
1081
+ def load_attributes_from_response(response)
1082
+ if response['Content-Length'] != "0" && response.body.strip.size > 0
1083
+ load(self.class.format.decode(response.body))
1084
+ end
1085
+ end
1086
+
1087
+ # Takes a response from a typical create post and pulls the ID out
1088
+ def id_from_response(response)
1089
+ response['Location'][/\/([^\/]*?)(\.\w+)?$/, 1] if response['Location']
1090
+ end
1091
+
1092
+ def element_path(options = nil)
1093
+ self.class.element_path(to_param, options || prefix_options)
1094
+ end
1095
+
1096
+ def collection_path(options = nil)
1097
+ self.class.collection_path(options || prefix_options)
1098
+ end
1099
+
1100
+ private
1101
+ # Tries to find a resource for a given collection name; if it fails, then the resource is created
1102
+ def find_or_create_resource_for_collection(name)
1103
+ find_or_create_resource_for(name.to_s.singularize)
1104
+ end
1105
+
1106
+ # Tries to find a resource in a non empty list of nested modules
1107
+ # Raises a NameError if it was not found in any of the given nested modules
1108
+ def find_resource_in_modules(resource_name, module_names)
1109
+ receiver = Object
1110
+ namespaces = module_names[0, module_names.size-1].map do |module_name|
1111
+ receiver = receiver.const_get(module_name)
1112
+ end
1113
+ if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(resource_name) }
1114
+ return namespace.const_get(resource_name)
1115
+ else
1116
+ raise NameError
1117
+ end
1118
+ end
1119
+
1120
+ # Tries to find a resource for a given name; if it fails, then the resource is created
1121
+ def find_or_create_resource_for(name)
1122
+ resource_name = name.to_s.camelize
1123
+ ancestors = self.class.name.split("::")
1124
+ if ancestors.size > 1
1125
+ find_resource_in_modules(resource_name, ancestors)
1126
+ else
1127
+ self.class.const_get(resource_name)
1128
+ end
1129
+ rescue NameError
1130
+ if self.class.const_defined?(resource_name)
1131
+ resource = self.class.const_get(resource_name)
1132
+ else
1133
+ resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
1134
+ end
1135
+ resource.prefix = self.class.prefix
1136
+ resource.site = self.class.site
1137
+ resource
1138
+ end
1139
+
1140
+ def split_options(options = {})
1141
+ self.class.__send__(:split_options, options)
1142
+ end
1143
+
1144
+ def method_missing(method_symbol, *arguments) #:nodoc:
1145
+ method_name = method_symbol.to_s
1146
+
1147
+ case method_name.last
1148
+ when "="
1149
+ attributes[method_name.first(-1)] = arguments.first
1150
+ when "?"
1151
+ attributes[method_name.first(-1)]
1152
+ else
1153
+ attributes.has_key?(method_name) ? attributes[method_name] : super
1154
+ end
1155
+ end
1156
+ end
1157
+ end