activeresource 3.2.22.5 → 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +2 -2
  3. data/README.rdoc +147 -49
  4. data/lib/active_resource.rb +12 -12
  5. data/lib/active_resource/active_job_serializer.rb +26 -0
  6. data/lib/active_resource/associations.rb +175 -0
  7. data/lib/active_resource/associations/builder/association.rb +33 -0
  8. data/lib/active_resource/associations/builder/belongs_to.rb +16 -0
  9. data/lib/active_resource/associations/builder/has_many.rb +14 -0
  10. data/lib/active_resource/associations/builder/has_one.rb +14 -0
  11. data/lib/active_resource/base.rb +444 -231
  12. data/lib/active_resource/callbacks.rb +22 -0
  13. data/lib/active_resource/collection.rb +94 -0
  14. data/lib/active_resource/connection.rb +112 -105
  15. data/lib/active_resource/custom_methods.rb +24 -14
  16. data/lib/active_resource/exceptions.rb +5 -3
  17. data/lib/active_resource/formats.rb +5 -3
  18. data/lib/active_resource/formats/json_format.rb +4 -1
  19. data/lib/active_resource/formats/xml_format.rb +4 -2
  20. data/lib/active_resource/http_mock.rb +69 -31
  21. data/lib/active_resource/log_subscriber.rb +14 -3
  22. data/lib/active_resource/observing.rb +0 -29
  23. data/lib/active_resource/railtie.rb +14 -3
  24. data/lib/active_resource/reflection.rb +78 -0
  25. data/lib/active_resource/schema.rb +4 -4
  26. data/lib/active_resource/singleton.rb +113 -0
  27. data/lib/active_resource/threadsafe_attributes.rb +66 -0
  28. data/lib/active_resource/validations.rb +56 -14
  29. data/lib/active_resource/version.rb +7 -5
  30. data/lib/activeresource.rb +3 -0
  31. metadata +78 -16
  32. data/CHANGELOG.md +0 -437
  33. data/examples/performance.rb +0 -70
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 480213fdf4278e17032edc702ac6a382fed5e257
4
- data.tar.gz: d99d3c4519997be684c92b0d877f2d2126a54ede
2
+ SHA256:
3
+ metadata.gz: 9e0cde8f42f06f37aa13a7098b9a1e828aac34c90971a62f4c9a1a7fcf529ce7
4
+ data.tar.gz: 11e7f85a7c565de83cb5248d8ee35783b343b8714e8ed9adcf08462f8aaceeda
5
5
  SHA512:
6
- metadata.gz: 074be6f00e06d0bb0af86e772c95e287b65da977010f7e11eca2bc72e68e64612e62abbd24980cfa612f0db33703ee0c306bfea1ed21bc323997633085cc86ee
7
- data.tar.gz: 71bf71e63a3037ec3e145282b4ef4c1d6fcf7eaf067ef4ceec75cfde85ea389d8bdb66b764e8e0bd7a181ec0e16a6214eb5a41221d11fb946d0d7c470df6e83c
6
+ metadata.gz: 133f4a82c31d9c925bc1c04a304c63e03982d45aa51b97d7e9ba580a433579802fb811a32009d40ebb7fc84e947fe65c60fd658b2cebb2f2f4c724efc536f528
7
+ data.tar.gz: 0f0042b3b644fe8584d34403913f2e11363a684fe438d536303871baaed2a98f26319b0e1dcd036364b42597b48e76c5713a627ea4431a2b2ec9263fce98b3a6
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006-2011 David Heinemeier Hansson
1
+ Copyright (c) 2006-2016 David Heinemeier Hansson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
17
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
18
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
19
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc CHANGED
@@ -17,7 +17,7 @@ for ActiveResource::Base.
17
17
  == Overview
18
18
 
19
19
  Model classes are mapped to remote REST resources by Active Resource much the same way Active Record maps model classes to database
20
- tables. When a request is made to a remote resource, a REST XML request is generated, transmitted, and the result
20
+ tables. When a request is made to a remote resource, a REST JSON request is generated, transmitted, and the result
21
21
  received and serialized into a usable Ruby object.
22
22
 
23
23
  == Download and installation
@@ -26,9 +26,13 @@ The latest version of Active Resource can be installed with RubyGems:
26
26
 
27
27
  % [sudo] gem install activeresource
28
28
 
29
- Source code can be downloaded as part of the Rails project on GitHub
29
+ Or added to a Gemfile:
30
30
 
31
- * https://github.com/rails/rails/tree/3-2-stable/activeresource
31
+ gem 'activeresource'
32
+
33
+ Source code can be downloaded on GitHub
34
+
35
+ * https://github.com/rails/activeresource/tree/master/activeresource
32
36
 
33
37
  === Configuration and Usage
34
38
 
@@ -43,15 +47,54 @@ Now the Person class is REST enabled and can invoke REST services very similarly
43
47
  life cycle methods that operate against a persistent store.
44
48
 
45
49
  # Find a person with id = 1
46
- ryan = Person.find(1)
50
+ tyler = Person.find(1)
47
51
  Person.exists?(1) # => true
48
52
 
49
53
  As you can see, the methods are quite similar to Active Record's methods for dealing with database
50
54
  records. But rather than dealing directly with a database record, you're dealing with HTTP resources (which may or may not be database records).
51
55
 
56
+ Connection settings (`site`, `headers`, `user`, `password`, `proxy`) and the connections themselves are store in
57
+ thread-local variables to make them thread-safe, so you can also set these dynamically, even in a multi-threaded environment, for instance:
58
+
59
+ ActiveResource::Base.site = api_site_for(request)
60
+
61
+ ==== Authentication
62
+
63
+ Active Resource supports the token based authentication provided by Rails through the <tt>ActionController::HttpAuthentication::Token</tt> class using custom headers.
64
+
65
+ class Person < ActiveResource::Base
66
+ self.headers['Authorization'] = 'Token token="abcd"'
67
+ end
68
+
69
+ You can also set any specific HTTP header using the same way. As mentioned above, headers are thread-safe, so you can set headers dynamically, even in a multi-threaded environment:
70
+
71
+ ActiveResource::Base.headers['Authorization'] = current_session_api_token
72
+
73
+ Global Authentication to be used across all subclasses of ActiveResource::Base should be handled using the ActiveResource::Connection class.
74
+
75
+ ActiveResource::Base.connection.auth_type = :bearer
76
+ ActiveResource::Base.connection.bearer_token = @bearer_token
77
+
78
+ class Person < ActiveResource::Base
79
+ self.connection.auth_type = :bearer
80
+ self.connection.bearer_token = @bearer_token
81
+ end
82
+
83
+ ActiveResource supports 2 options for HTTP authentication today.
84
+
85
+ 1. Basic
86
+
87
+ ActiveResource::Connection.new("http://my%40email.com:%31%32%33@localhost")
88
+ # username: my@email.com password: 123
89
+
90
+ 2. Bearer Token
91
+
92
+ ActiveResource::Base.connection.auth_type = :bearer
93
+ ActiveResource::Base.connection.bearer_token = @bearer_token
94
+
52
95
  ==== Protocol
53
96
 
54
- Active Resource is built on a standard XML format for requesting and submitting resources over HTTP. It mirrors the RESTful routing
97
+ Active Resource is built on a standard JSON or XML format for requesting and submitting resources over HTTP. It mirrors the RESTful routing
55
98
  built into Action Controller but will also work with any other REST service that properly implements the protocol.
56
99
  REST uses HTTP, but unlike "typical" web applications, it makes use of all the verbs available in the HTTP specification:
57
100
 
@@ -65,73 +108,75 @@ for more general information on REST web services, see the article here[http://e
65
108
 
66
109
  ==== Find
67
110
 
68
- Find requests use the GET method and expect the XML form of whatever resource/resources is/are being requested. So,
69
- for a request for a single element, the XML of that item is expected in response:
111
+ Find requests use the GET method and expect the JSON form of whatever resource/resources is/are being requested. So,
112
+ for a request for a single element, the JSON of that item is expected in response:
70
113
 
71
114
  # Expects a response of
72
115
  #
73
- # <person><id type="integer">1</id><attribute1>value1</attribute1><attribute2>..</attribute2></person>
116
+ # {"id":1,"first":"Tyler","last":"Durden"}
74
117
  #
75
- # for GET http://api.people.com:3000/people/1.xml
118
+ # for GET http://api.people.com:3000/people/1.json
76
119
  #
77
- ryan = Person.find(1)
120
+ tyler = Person.find(1)
78
121
 
79
- The XML document that is received is used to build a new object of type Person, with each
80
- XML element becoming an attribute on the object.
122
+ The JSON document that is received is used to build a new object of type Person, with each
123
+ JSON element becoming an attribute on the object.
81
124
 
82
- ryan.is_a? Person # => true
83
- ryan.attribute1 # => 'value1'
125
+ tyler.is_a? Person # => true
126
+ tyler.last # => 'Durden'
84
127
 
85
128
  Any complex element (one that contains other elements) becomes its own object:
86
129
 
87
130
  # With this response:
131
+ # {"id":1,"first":"Tyler","address":{"street":"Paper St.","state":"CA"}}
88
132
  #
89
- # <person><id>1</id><attribute1>value1</attribute1><complex><attribute2>value2</attribute2></complex></person>
133
+ # for GET http://api.people.com:3000/people/1.json
90
134
  #
91
- # for GET http://api.people.com:3000/people/1.xml
92
- #
93
- ryan = Person.find(1)
94
- ryan.complex # => <Person::Complex::xxxxx>
95
- ryan.complex.attribute2 # => 'value2'
135
+ tyler = Person.find(1)
136
+ tyler.address # => <Person::Address::xxxxx>
137
+ tyler.address.street # => 'Paper St.'
96
138
 
97
139
  Collections can also be requested in a similar fashion
98
140
 
99
141
  # Expects a response of
100
142
  #
101
- # <people type="array">
102
- # <person><id type="integer">1</id><first>Ryan</first></person>
103
- # <person><id type="integer">2</id><first>Jim</first></person>
104
- # </people>
143
+ # [
144
+ # {"id":1,"first":"Tyler","last":"Durden"},
145
+ # {"id":2,"first":"Tony","last":"Stark",}
146
+ # ]
105
147
  #
106
- # for GET http://api.people.com:3000/people.xml
148
+ # for GET http://api.people.com:3000/people.json
107
149
  #
108
150
  people = Person.all
109
- people.first # => <Person::xxx 'first' => 'Ryan' ...>
110
- people.last # => <Person::xxx 'first' => 'Jim' ...>
151
+ people.first # => <Person::xxx 'first' => 'Tyler' ...>
152
+ people.last # => <Person::xxx 'first' => 'Tony' ...>
111
153
 
112
154
  ==== Create
113
155
 
114
- Creating a new resource submits the XML form of the resource as the body of the request and expects
156
+ Creating a new resource submits the JSON form of the resource as the body of the request and expects
115
157
  a 'Location' header in the response with the RESTful URL location of the newly created resource. The
116
158
  id of the newly created resource is parsed out of the Location response header and automatically set
117
159
  as the id of the ARes object.
118
160
 
119
- # <person><first>Ryan</first></person>
161
+ # {"first":"Tyler","last":"Durden"}
120
162
  #
121
163
  # is submitted as the body on
122
164
  #
123
- # POST http://api.people.com:3000/people.xml
165
+ # if include_root_in_json is not set or set to false => {"first":"Tyler"}
166
+ # if include_root_in_json is set to true => {"person":{"first":"Tyler"}}
167
+ #
168
+ # POST http://api.people.com:3000/people.json
124
169
  #
125
170
  # when save is called on a new Person object. An empty response is
126
171
  # is expected with a 'Location' header value:
127
172
  #
128
173
  # Response (201): Location: http://api.people.com:3000/people/2
129
174
  #
130
- ryan = Person.new(:first => 'Ryan')
131
- ryan.new? # => true
132
- ryan.save # => true
133
- ryan.new? # => false
134
- ryan.id # => 2
175
+ tyler = Person.new(:first => 'Tyler')
176
+ tyler.new? # => true
177
+ tyler.save # => true
178
+ tyler.new? # => false
179
+ tyler.id # => 2
135
180
 
136
181
  ==== Update
137
182
 
@@ -139,19 +184,22 @@ as the id of the ARes object.
139
184
  with the exception that no response headers are needed -- just an empty response when the update on the
140
185
  server side was successful.
141
186
 
142
- # <person><first>Ryan</first></person>
187
+ # {"first":"Tyler"}
143
188
  #
144
189
  # is submitted as the body on
145
190
  #
146
- # PUT http://api.people.com:3000/people/1.xml
191
+ # if include_root_in_json is not set or set to false => {"first":"Tyler"}
192
+ # if include_root_in_json is set to true => {"person":{"first":"Tyler"}}
193
+ #
194
+ # PUT http://api.people.com:3000/people/1.json
147
195
  #
148
196
  # when save is called on an existing Person object. An empty response is
149
197
  # is expected with code (204)
150
198
  #
151
- ryan = Person.find(1)
152
- ryan.first # => 'Ryan'
153
- ryan.first = 'Rizzle'
154
- ryan.save # => true
199
+ tyler = Person.find(1)
200
+ tyler.first # => 'Tyler'
201
+ tyler.first = 'Tyson'
202
+ tyler.save # => true
155
203
 
156
204
  ==== Delete
157
205
 
@@ -159,29 +207,79 @@ Destruction of a resource can be invoked as a class and instance method of the r
159
207
 
160
208
  # A request is made to
161
209
  #
162
- # DELETE http://api.people.com:3000/people/1.xml
210
+ # DELETE http://api.people.com:3000/people/1.json
163
211
  #
164
212
  # for both of these forms. An empty response with
165
213
  # is expected with response code (200)
166
214
  #
167
- ryan = Person.find(1)
168
- ryan.destroy # => true
169
- ryan.exists? # => false
215
+ tyler = Person.find(1)
216
+ tyler.destroy # => true
217
+ tyler.exists? # => false
170
218
  Person.delete(2) # => true
171
219
  Person.exists?(2) # => false
172
220
 
221
+ ==== Associations
222
+
223
+ Relationships between resources can be declared using the standard association syntax
224
+ that should be familiar to anyone who uses activerecord. For example, using the
225
+ class definition below:
226
+
227
+ class Post < ActiveResource::Base
228
+ self.site = "http://blog.io"
229
+ has_many :comments
230
+ end
231
+
232
+ post = Post.find(1) # issues GET http://blog.io/posts/1.json
233
+ comments = post.comments # issues GET http://blog.io/comments.json?post_id=1
234
+
235
+
236
+ If you control the server, you may wish to include nested resources thus avoiding a
237
+ second network request. Given the resource above, if the response includes comments
238
+ in the response, they will be automatically loaded into the activeresource object.
239
+ The server-side model can be adjusted as follows to include comments in the response.
240
+
241
+ class Post < ActiveRecord::Base
242
+ has_many :comments
243
+
244
+ def as_json(options)
245
+ super.merge(:include=>[:comments])
246
+ end
247
+ end
248
+
249
+ ==== Logging
250
+
251
+ Active Resource instruments the event `request.active_resource` when doing a request
252
+ to the remote service. You can subscribe to it by doing:
253
+
254
+ ActiveSupport::Notifications.subscribe('request.active_resource') do |name, start, finish, id, payload|
255
+
256
+ The `payload` is a `Hash` with the following keys:
257
+
258
+ * `method` as a `Symbol`
259
+ * `request_uri` as a `String`
260
+ * `result` as an `Net::HTTPResponse`
261
+
173
262
  == License
174
263
 
175
- Active Resource is released under the MIT license.
264
+ Active Resource is released under the MIT license:
265
+
266
+ * http://www.opensource.org/licenses/MIT
267
+
268
+ == Contributing to Active Resource
269
+
270
+ Active Resource is work of many contributors. You're encouraged to submit pull requests, propose
271
+ features and discuss issues.
272
+
273
+ See {CONTRIBUTING}[https://github.com/rails/activeresource/blob/master/CONTRIBUTING.md].
176
274
 
177
275
  == Support
178
276
 
179
- API documentation is at
277
+ Full API documentation is available at
180
278
 
181
- * http://api.rubyonrails.org
279
+ * http://rubydoc.info/gems/activeresource
182
280
 
183
281
  Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
184
282
 
185
- * https://github.com/rails/rails/issues
283
+ * https://github.com/rails/activeresource/issues
186
284
 
187
285
  You can find more usage information in the ActiveResource::Base documentation.
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #--
2
- # Copyright (c) 2006-2011 David Heinemeier Hansson
4
+ # Copyright (c) 2006-2012 David Heinemeier Hansson
3
5
  #
4
6
  # Permission is hereby granted, free of charge, to any person obtaining
5
7
  # a copy of this software and associated documentation files (the
@@ -21,26 +23,24 @@
21
23
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
24
  #++
23
25
 
24
- activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
25
- $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
26
-
27
- activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
28
- $:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
29
-
30
- require 'active_support'
31
- require 'active_model'
32
- require 'active_resource/exceptions'
33
- require 'active_resource/version'
26
+ require "active_support"
27
+ require "active_model"
28
+ require "active_resource/exceptions"
29
+ require "active_resource/version"
34
30
 
35
31
  module ActiveResource
36
32
  extend ActiveSupport::Autoload
37
33
 
38
34
  autoload :Base
35
+ autoload :Callbacks
39
36
  autoload :Connection
40
37
  autoload :CustomMethods
41
38
  autoload :Formats
42
39
  autoload :HttpMock
43
- autoload :Observing
44
40
  autoload :Schema
41
+ autoload :Singleton
45
42
  autoload :Validations
43
+ autoload :Collection
46
44
  end
45
+
46
+ require "active_resource/railtie" if defined?(Rails.application)
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveResource
4
+ class ActiveJobSerializer < ActiveJob::Serializers::ObjectSerializer
5
+ def serialize(resource)
6
+ super(
7
+ "class" => resource.class.name,
8
+ "persisted" => resource.persisted?,
9
+ "prefix_options" => resource.prefix_options.as_json,
10
+ "attributes" => resource.attributes.as_json
11
+ )
12
+ end
13
+
14
+ def deserialize(hash)
15
+ hash["class"].constantize.new(hash["attributes"]).tap do |resource|
16
+ resource.persisted = hash["persisted"]
17
+ resource.prefix_options = hash["prefix_options"]
18
+ end
19
+ end
20
+
21
+ private
22
+ def klass
23
+ ActiveResource::Base
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveResource::Associations
4
+ module Builder
5
+ autoload :Association, "active_resource/associations/builder/association"
6
+ autoload :HasMany, "active_resource/associations/builder/has_many"
7
+ autoload :HasOne, "active_resource/associations/builder/has_one"
8
+ autoload :BelongsTo, "active_resource/associations/builder/belongs_to"
9
+ end
10
+
11
+
12
+
13
+ # Specifies a one-to-many association.
14
+ #
15
+ # === Options
16
+ # [:class_name]
17
+ # Specify the class name of the association. This class name would
18
+ # be used for resolving the association class.
19
+ #
20
+ # ==== Example for [:class_name] - option
21
+ # GET /posts/123.json delivers following response body:
22
+ # {
23
+ # title: "ActiveResource now has associations",
24
+ # body: "Lorem Ipsum"
25
+ # comments: [
26
+ # {
27
+ # content: "..."
28
+ # },
29
+ # {
30
+ # content: "..."
31
+ # }
32
+ # ]
33
+ # }
34
+ # ====
35
+ #
36
+ # <tt>has_many :comments, :class_name => 'myblog/comment'</tt>
37
+ # Would resolve those comments into the <tt>Myblog::Comment</tt> class.
38
+ #
39
+ # If the response body does not contain an attribute matching the association name
40
+ # a request sent to the index action under the current resource.
41
+ # For the example above, if the comments are not present the requested path would be:
42
+ # GET /posts/123/comments.xml
43
+ def has_many(name, options = {})
44
+ Builder::HasMany.build(self, name, options)
45
+ end
46
+
47
+ # Specifies a one-to-one association.
48
+ #
49
+ # === Options
50
+ # [:class_name]
51
+ # Specify the class name of the association. This class name would
52
+ # be used for resolving the association class.
53
+ #
54
+ # ==== Example for [:class_name] - option
55
+ # GET /posts/1.json delivers following response body:
56
+ # {
57
+ # title: "ActiveResource now has associations",
58
+ # body: "Lorem Ipsum",
59
+ # author: {
60
+ # name: "Gabby Blogger",
61
+ # }
62
+ # }
63
+ # ====
64
+ #
65
+ # <tt>has_one :author, :class_name => 'myblog/author'</tt>
66
+ # Would resolve this author into the <tt>Myblog::Author</tt> class.
67
+ #
68
+ # If the response body does not contain an attribute matching the association name
69
+ # a request is sent to a singleton path under the current resource.
70
+ # For example, if a Product class <tt>has_one :inventory</tt> calling <tt>Product#inventory</tt>
71
+ # will generate a request on /products/:product_id/inventory.json.
72
+ #
73
+ def has_one(name, options = {})
74
+ Builder::HasOne.build(self, name, options)
75
+ end
76
+
77
+ # Specifies a one-to-one association with another class. This class should only be used
78
+ # if this class contains the foreign key.
79
+ #
80
+ # Methods will be added for retrieval and query for a single associated object, for which
81
+ # this object holds an id:
82
+ #
83
+ # [association(force_reload = false)]
84
+ # Returns the associated object. +nil+ is returned if the foreign key is +nil+.
85
+ # Throws a ActiveResource::ResourceNotFound exception if the foreign key is not +nil+
86
+ # and the resource is not found.
87
+ #
88
+ # (+association+ is replaced with the symbol passed as the first argument, so
89
+ # <tt>belongs_to :post</tt> would add among others <tt>post.nil?</tt>.
90
+ #
91
+ # === Example
92
+ #
93
+ # A Comment class declares <tt>belongs_to :post</tt>, which will add:
94
+ # * <tt>Comment#post</tt> (similar to <tt>Post.find(post_id)</tt>)
95
+ # The declaration can also include an options hash to specialize the behavior of the association.
96
+ #
97
+ # === Options
98
+ # [:class_name]
99
+ # Specify the class name for the association. Use it only if that name can't be inferred from association name.
100
+ # So <tt>belongs_to :post</tt> will by default be linked to the Post class, but if the real class name is Article,
101
+ # you'll have to specify it with this option.
102
+ # [:foreign_key]
103
+ # Specify the foreign key used for the association. By default this is guessed to be the name
104
+ # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :post</tt>
105
+ # association will use "post_id" as the default <tt>:foreign_key</tt>. Similarly,
106
+ # <tt>belongs_to :article, :class_name => "Post"</tt> will use a foreign key
107
+ # of "article_id".
108
+ #
109
+ # Option examples:
110
+ # <tt>belongs_to :customer, :class_name => 'User'</tt>
111
+ # Creates a belongs_to association called customer which is represented through the <tt>User</tt> class.
112
+ #
113
+ # <tt>belongs_to :customer, :foreign_key => 'user_id'</tt>
114
+ # Creates a belongs_to association called customer which would be resolved by the foreign_key <tt>user_id</tt> instead of <tt>customer_id</tt>
115
+ #
116
+ def belongs_to(name, options = {})
117
+ Builder::BelongsTo.build(self, name, options)
118
+ end
119
+
120
+ # Defines the belongs_to association finder method
121
+ def defines_belongs_to_finder_method(reflection)
122
+ method_name = reflection.name
123
+ ivar_name = :"@#{method_name}"
124
+
125
+ if method_defined?(method_name)
126
+ instance_variable_set(ivar_name, nil)
127
+ remove_method(method_name)
128
+ end
129
+
130
+ define_method(method_name) do
131
+ if instance_variable_defined?(ivar_name)
132
+ instance_variable_get(ivar_name)
133
+ elsif attributes.include?(method_name)
134
+ attributes[method_name]
135
+ elsif association_id = send(reflection.foreign_key)
136
+ instance_variable_set(ivar_name, reflection.klass.find(association_id))
137
+ end
138
+ end
139
+ end
140
+
141
+ def defines_has_many_finder_method(reflection)
142
+ method_name = reflection.name
143
+ ivar_name = :"@#{method_name}"
144
+
145
+ define_method(method_name) do
146
+ if instance_variable_defined?(ivar_name)
147
+ instance_variable_get(ivar_name)
148
+ elsif attributes.include?(method_name)
149
+ attributes[method_name]
150
+ elsif !new_record?
151
+ instance_variable_set(ivar_name, reflection.klass.find(:all, params: { :"#{self.class.element_name}_id" => self.id }))
152
+ else
153
+ instance_variable_set(ivar_name, self.class.collection_parser.new)
154
+ end
155
+ end
156
+ end
157
+
158
+ # Defines the has_one association
159
+ def defines_has_one_finder_method(reflection)
160
+ method_name = reflection.name
161
+ ivar_name = :"@#{method_name}"
162
+
163
+ define_method(method_name) do
164
+ if instance_variable_defined?(ivar_name)
165
+ instance_variable_get(ivar_name)
166
+ elsif attributes.include?(method_name)
167
+ attributes[method_name]
168
+ elsif reflection.klass.respond_to?(:singleton_name)
169
+ instance_variable_set(ivar_name, reflection.klass.find(params: { :"#{self.class.element_name}_id" => self.id }))
170
+ else
171
+ instance_variable_set(ivar_name, reflection.klass.find(:one, from: "/#{self.class.collection_name}/#{self.id}/#{method_name}#{self.class.format_extension}"))
172
+ end
173
+ end
174
+ end
175
+ end