activeresource 3.2.22.5 → 4.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activeresource might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.rdoc +53 -48
- data/lib/active_resource.rb +4 -7
- data/lib/active_resource/associations.rb +168 -0
- data/lib/active_resource/associations/builder/association.rb +32 -0
- data/lib/active_resource/associations/builder/belongs_to.rb +14 -0
- data/lib/active_resource/associations/builder/has_many.rb +12 -0
- data/lib/active_resource/associations/builder/has_one.rb +12 -0
- data/lib/active_resource/base.rb +213 -132
- data/lib/active_resource/callbacks.rb +20 -0
- data/lib/active_resource/collection.rb +85 -0
- data/lib/active_resource/connection.rb +28 -30
- data/lib/active_resource/custom_methods.rb +18 -10
- data/lib/active_resource/http_mock.rb +11 -14
- data/lib/active_resource/observing.rb +2 -0
- data/lib/active_resource/reflection.rb +77 -0
- data/lib/active_resource/schema.rb +0 -2
- data/lib/active_resource/singleton.rb +114 -0
- data/lib/active_resource/validations.rb +46 -7
- data/lib/active_resource/version.rb +4 -4
- metadata +69 -22
- data/CHANGELOG.md +0 -437
- data/MIT-LICENSE +0 -20
- data/examples/performance.rb +0 -70
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 931c2a3ac5d3c4b76c2165a6d58c44f4aedab23e
|
4
|
+
data.tar.gz: 1a3056f9e297d082f97bdb7350023c571c3dc79d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 459d6c6a88802add4531118ac6f9191c27196e805be61f35e60787a8fc4a4b380cf456c459a63f6f4c6d9c83f8397dec1fdb9d70f8265c2a8358c978dc919d70
|
7
|
+
data.tar.gz: 05bea224b9115b0ab07cdf8c18846b7327ccabf4265d889ee6caff0437a4349de9f68b9e249c5a8679551001da05322a08b28d5cfcf9dee90c12ae4a6a3fa4d4
|
data/README.rdoc
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= Active Resource
|
1
|
+
= Active Resource {<img src="https://secure.travis-ci.org/rails/activeresource.png" />}[http://travis-ci.org/rails/activeresource]
|
2
2
|
|
3
3
|
Active Resource (ARes) connects business objects and Representational State Transfer (REST)
|
4
4
|
web services. It implements object-relational mapping for REST web services to provide transparent
|
@@ -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
|
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
|
-
|
29
|
+
Or added to a Gemfile:
|
30
30
|
|
31
|
-
|
31
|
+
gem 'activeresource', :require => 'active_resource'
|
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,7 +47,7 @@ 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
|
-
|
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
|
@@ -51,7 +55,7 @@ records. But rather than dealing directly with a database record, you're dealin
|
|
51
55
|
|
52
56
|
==== Protocol
|
53
57
|
|
54
|
-
Active Resource is built on a standard XML format for requesting and submitting resources over HTTP. It mirrors the RESTful routing
|
58
|
+
Active Resource is built on a standard JSON or XML format for requesting and submitting resources over HTTP. It mirrors the RESTful routing
|
55
59
|
built into Action Controller but will also work with any other REST service that properly implements the protocol.
|
56
60
|
REST uses HTTP, but unlike "typical" web applications, it makes use of all the verbs available in the HTTP specification:
|
57
61
|
|
@@ -65,73 +69,72 @@ for more general information on REST web services, see the article here[http://e
|
|
65
69
|
|
66
70
|
==== Find
|
67
71
|
|
68
|
-
Find requests use the GET method and expect the
|
69
|
-
for a request for a single element, the
|
72
|
+
Find requests use the GET method and expect the JSON form of whatever resource/resources is/are being requested. So,
|
73
|
+
for a request for a single element, the JSON of that item is expected in response:
|
70
74
|
|
71
75
|
# Expects a response of
|
72
76
|
#
|
73
|
-
#
|
77
|
+
# {"id":1,"first":"Tyler","last":"Durden"}
|
74
78
|
#
|
75
|
-
# for GET http://api.people.com:3000/people/1.
|
79
|
+
# for GET http://api.people.com:3000/people/1.json
|
76
80
|
#
|
77
|
-
|
81
|
+
tyler = Person.find(1)
|
78
82
|
|
79
|
-
The
|
80
|
-
|
83
|
+
The JSON document that is received is used to build a new object of type Person, with each
|
84
|
+
JSON element becoming an attribute on the object.
|
81
85
|
|
82
|
-
|
83
|
-
|
86
|
+
tyler.is_a? Person # => true
|
87
|
+
tyler.last # => 'Durden'
|
84
88
|
|
85
89
|
Any complex element (one that contains other elements) becomes its own object:
|
86
90
|
|
87
91
|
# With this response:
|
92
|
+
# {"id":1,"first":"Tyler","address":{"street":"Paper St.","state":"CA"}}
|
88
93
|
#
|
89
|
-
#
|
94
|
+
# for GET http://api.people.com:3000/people/1.json
|
90
95
|
#
|
91
|
-
|
92
|
-
#
|
93
|
-
|
94
|
-
ryan.complex # => <Person::Complex::xxxxx>
|
95
|
-
ryan.complex.attribute2 # => 'value2'
|
96
|
+
tyler = Person.find(1)
|
97
|
+
tyler.address # => <Person::Address::xxxxx>
|
98
|
+
tyler.address.street # => 'Paper St.'
|
96
99
|
|
97
100
|
Collections can also be requested in a similar fashion
|
98
101
|
|
99
102
|
# Expects a response of
|
100
103
|
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
104
|
+
# [
|
105
|
+
# {"id":1,"first":"Tyler","last":"Durden"},
|
106
|
+
# {"id":2,"first":"Tony","last":"Stark",}
|
107
|
+
# ]
|
105
108
|
#
|
106
|
-
# for GET http://api.people.com:3000/people.
|
109
|
+
# for GET http://api.people.com:3000/people.json
|
107
110
|
#
|
108
111
|
people = Person.all
|
109
|
-
people.first # => <Person::xxx 'first' => '
|
110
|
-
people.last # => <Person::xxx 'first' => '
|
112
|
+
people.first # => <Person::xxx 'first' => 'Tyler' ...>
|
113
|
+
people.last # => <Person::xxx 'first' => 'Tony' ...>
|
111
114
|
|
112
115
|
==== Create
|
113
116
|
|
114
|
-
Creating a new resource submits the
|
117
|
+
Creating a new resource submits the JSON form of the resource as the body of the request and expects
|
115
118
|
a 'Location' header in the response with the RESTful URL location of the newly created resource. The
|
116
119
|
id of the newly created resource is parsed out of the Location response header and automatically set
|
117
120
|
as the id of the ARes object.
|
118
121
|
|
119
|
-
#
|
122
|
+
# {"person":{"first":"Tyler","last":"Durden"}}
|
120
123
|
#
|
121
124
|
# is submitted as the body on
|
122
125
|
#
|
123
|
-
# POST http://api.people.com:3000/people.
|
126
|
+
# POST http://api.people.com:3000/people.json
|
124
127
|
#
|
125
128
|
# when save is called on a new Person object. An empty response is
|
126
129
|
# is expected with a 'Location' header value:
|
127
130
|
#
|
128
131
|
# Response (201): Location: http://api.people.com:3000/people/2
|
129
132
|
#
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
133
|
+
tyler = Person.new(:first => 'Tyler')
|
134
|
+
tyler.new? # => true
|
135
|
+
tyler.save # => true
|
136
|
+
tyler.new? # => false
|
137
|
+
tyler.id # => 2
|
135
138
|
|
136
139
|
==== Update
|
137
140
|
|
@@ -139,19 +142,19 @@ as the id of the ARes object.
|
|
139
142
|
with the exception that no response headers are needed -- just an empty response when the update on the
|
140
143
|
server side was successful.
|
141
144
|
|
142
|
-
#
|
145
|
+
# {"person":{"first":"Tyler"}}
|
143
146
|
#
|
144
147
|
# is submitted as the body on
|
145
148
|
#
|
146
|
-
# PUT http://api.people.com:3000/people/1.
|
149
|
+
# PUT http://api.people.com:3000/people/1.json
|
147
150
|
#
|
148
151
|
# when save is called on an existing Person object. An empty response is
|
149
152
|
# is expected with code (204)
|
150
153
|
#
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
154
|
+
tyler = Person.find(1)
|
155
|
+
tyler.first # => 'Tyler'
|
156
|
+
tyler.first = 'Tyson'
|
157
|
+
tyler.save # => true
|
155
158
|
|
156
159
|
==== Delete
|
157
160
|
|
@@ -159,20 +162,22 @@ Destruction of a resource can be invoked as a class and instance method of the r
|
|
159
162
|
|
160
163
|
# A request is made to
|
161
164
|
#
|
162
|
-
# DELETE http://api.people.com:3000/people/1.
|
165
|
+
# DELETE http://api.people.com:3000/people/1.json
|
163
166
|
#
|
164
167
|
# for both of these forms. An empty response with
|
165
168
|
# is expected with response code (200)
|
166
169
|
#
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
+
tyler = Person.find(1)
|
171
|
+
tyler.destroy # => true
|
172
|
+
tyler.exists? # => false
|
170
173
|
Person.delete(2) # => true
|
171
174
|
Person.exists?(2) # => false
|
172
175
|
|
173
176
|
== License
|
174
177
|
|
175
|
-
Active Resource is released under the MIT license
|
178
|
+
Active Resource is released under the MIT license:
|
179
|
+
|
180
|
+
* http://www.opensource.org/licenses/MIT
|
176
181
|
|
177
182
|
== Support
|
178
183
|
|
@@ -182,6 +187,6 @@ API documentation is at
|
|
182
187
|
|
183
188
|
Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
|
184
189
|
|
185
|
-
* https://github.com/rails/
|
190
|
+
* https://github.com/rails/activeresource/issues
|
186
191
|
|
187
192
|
You can find more usage information in the ActiveResource::Base documentation.
|
data/lib/active_resource.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2006-
|
2
|
+
# Copyright (c) 2006-2012 David Heinemeier Hansson
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining
|
5
5
|
# a copy of this software and associated documentation files (the
|
@@ -21,12 +21,6 @@
|
|
21
21
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
22
|
#++
|
23
23
|
|
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
24
|
require 'active_support'
|
31
25
|
require 'active_model'
|
32
26
|
require 'active_resource/exceptions'
|
@@ -36,11 +30,14 @@ module ActiveResource
|
|
36
30
|
extend ActiveSupport::Autoload
|
37
31
|
|
38
32
|
autoload :Base
|
33
|
+
autoload :Callbacks
|
39
34
|
autoload :Connection
|
40
35
|
autoload :CustomMethods
|
41
36
|
autoload :Formats
|
42
37
|
autoload :HttpMock
|
43
38
|
autoload :Observing
|
44
39
|
autoload :Schema
|
40
|
+
autoload :Singleton
|
45
41
|
autoload :Validations
|
42
|
+
autoload :Collection
|
46
43
|
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
module ActiveResource::Associations
|
2
|
+
|
3
|
+
module Builder
|
4
|
+
autoload :Association, 'active_resource/associations/builder/association'
|
5
|
+
autoload :HasMany, 'active_resource/associations/builder/has_many'
|
6
|
+
autoload :HasOne, 'active_resource/associations/builder/has_one'
|
7
|
+
autoload :BelongsTo, 'active_resource/associations/builder/belongs_to'
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
# Specifies a one-to-many association.
|
13
|
+
#
|
14
|
+
# === Options
|
15
|
+
# [:class_name]
|
16
|
+
# Specify the class name of the association. This class name would
|
17
|
+
# be used for resolving the association class.
|
18
|
+
#
|
19
|
+
# ==== Example for [:class_name] - option
|
20
|
+
# GET /posts/123.json delivers following response body:
|
21
|
+
# {
|
22
|
+
# title: "ActiveResource now has associations",
|
23
|
+
# body: "Lorem Ipsum"
|
24
|
+
# comments: [
|
25
|
+
# {
|
26
|
+
# content: "..."
|
27
|
+
# },
|
28
|
+
# {
|
29
|
+
# content: "..."
|
30
|
+
# }
|
31
|
+
# ]
|
32
|
+
# }
|
33
|
+
# ====
|
34
|
+
#
|
35
|
+
# <tt>has_many :comments, :class_name => 'myblog/comment'</tt>
|
36
|
+
# Would resolve those comments into the <tt>Myblog::Comment</tt> class.
|
37
|
+
#
|
38
|
+
# If the response body does not contain an attribute matching the association name
|
39
|
+
# a request sent to the index action under the current resource.
|
40
|
+
# For the example above, if the comments are not present the requested path would be:
|
41
|
+
# GET /posts/123/comments.xml
|
42
|
+
def has_many(name, options = {})
|
43
|
+
Builder::HasMany.build(self, name, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Specifies a one-to-one association.
|
47
|
+
#
|
48
|
+
# === Options
|
49
|
+
# [:class_name]
|
50
|
+
# Specify the class name of the association. This class name would
|
51
|
+
# be used for resolving the association class.
|
52
|
+
#
|
53
|
+
# ==== Example for [:class_name] - option
|
54
|
+
# GET /posts/1.json delivers following response body:
|
55
|
+
# {
|
56
|
+
# title: "ActiveResource now has associations",
|
57
|
+
# body: "Lorem Ipsum",
|
58
|
+
# author: {
|
59
|
+
# name: "Gabby Blogger",
|
60
|
+
# }
|
61
|
+
# }
|
62
|
+
# ====
|
63
|
+
#
|
64
|
+
# <tt>has_one :author, :class_name => 'myblog/author'</tt>
|
65
|
+
# Would resolve this author into the <tt>Myblog::Author</tt> class.
|
66
|
+
#
|
67
|
+
# If the response body does not contain an attribute matching the association name
|
68
|
+
# a request is sent to a singelton path under the current resource.
|
69
|
+
# For example, if a Product class <tt>has_one :inventory</tt> calling <tt>Product#inventory</tt>
|
70
|
+
# will generate a request on /product/:product_id/inventory.json.
|
71
|
+
#
|
72
|
+
def has_one(name, options = {})
|
73
|
+
Builder::HasOne.build(self, name, options)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Specifies a one-to-one association with another class. This class should only be used
|
77
|
+
# if this class contains the foreign key.
|
78
|
+
#
|
79
|
+
# Methods will be added for retrieval and query for a single associated object, for which
|
80
|
+
# this object holds an id:
|
81
|
+
#
|
82
|
+
# [association(force_reload = false)]
|
83
|
+
# Returns the associated object. +nil+ is returned if the foreign key is +nil+.
|
84
|
+
# Throws a ActiveResource::ResourceNotFound exception if the foreign key is not +nil+
|
85
|
+
# and the resource is not found.
|
86
|
+
#
|
87
|
+
# (+association+ is replaced with the symbol passed as the first argument, so
|
88
|
+
# <tt>belongs_to :post</tt> would add among others <tt>post.nil?</tt>.
|
89
|
+
#
|
90
|
+
# === Example
|
91
|
+
#
|
92
|
+
# A Comment class declaress <tt>belongs_to :post</tt>, which will add:
|
93
|
+
# * <tt>Comment#post</tt> (similar to <tt>Post.find(post_id)</tt>)
|
94
|
+
# The declaration can also include an options hash to specialize the behavior of the association.
|
95
|
+
#
|
96
|
+
# === Options
|
97
|
+
# [:class_name]
|
98
|
+
# Specify the class name for the association. Use it only if that name canÄt be inferred from association name.
|
99
|
+
# So <tt>belongs_to :post</tt> will by default be linked to the Post class, but if the real class name is Article,
|
100
|
+
# you'll have to specify it with whis option.
|
101
|
+
# [:foreign_key]
|
102
|
+
# Specify the foreign key used for the association. By default this is guessed to be the name
|
103
|
+
# of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :post</tt>
|
104
|
+
# association will use "post_id" as the default <tt>:foreign_key</tt>. Similarly,
|
105
|
+
# <tt>belongs_to :article, :class_name => "Post"</tt> will use a foreign key
|
106
|
+
# of "article_id".
|
107
|
+
#
|
108
|
+
# Option examples:
|
109
|
+
# <tt>belongs_to :customer, :class_name => 'User'</tt>
|
110
|
+
# Creates a belongs_to association called customer which is represented through the <tt>User</tt> class.
|
111
|
+
#
|
112
|
+
# <tt>belongs_to :customer, :foreign_key => 'user_id'</tt>
|
113
|
+
# 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>
|
114
|
+
#
|
115
|
+
def belongs_to(name, options={})
|
116
|
+
Builder::BelongsTo.build(self, name, options)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Defines the belongs_to association finder method
|
120
|
+
def defines_belongs_to_finder_method(method_name, association_model, finder_key)
|
121
|
+
ivar_name = :"@#{method_name}"
|
122
|
+
|
123
|
+
if method_defined?(method_name)
|
124
|
+
instance_variable_set(ivar_name, nil)
|
125
|
+
remove_method(method_name)
|
126
|
+
end
|
127
|
+
|
128
|
+
define_method(method_name) do
|
129
|
+
if instance_variable_defined?(ivar_name)
|
130
|
+
instance_variable_get(ivar_name)
|
131
|
+
elsif attributes.include?(method_name)
|
132
|
+
attributes[method_name]
|
133
|
+
else
|
134
|
+
instance_variable_set(ivar_name, association_model.find(send(finder_key)))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def defines_has_many_finder_method(method_name, association_model)
|
140
|
+
ivar_name = :"@#{method_name}"
|
141
|
+
|
142
|
+
define_method(method_name) do
|
143
|
+
if instance_variable_defined?(ivar_name)
|
144
|
+
instance_variable_get(ivar_name)
|
145
|
+
elsif attributes.include?(method_name)
|
146
|
+
attributes[method_name]
|
147
|
+
else
|
148
|
+
instance_variable_set(ivar_name, association_model.find(:all, :params => {:"#{self.class.element_name}_id" => self.id}))
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Defines the has_one association
|
154
|
+
def defines_has_one_finder_method(method_name, association_model)
|
155
|
+
ivar_name = :"@#{method_name}"
|
156
|
+
|
157
|
+
define_method(method_name) do
|
158
|
+
if instance_variable_defined?(ivar_name)
|
159
|
+
instance_variable_get(ivar_name)
|
160
|
+
elsif attributes.include?(method_name)
|
161
|
+
attributes[method_name]
|
162
|
+
else
|
163
|
+
instance_variable_set(ivar_name, association_model.find(:params => {:"#{self.class.element_name}_id" => self.id}))
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
@@ -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
|