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
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'active_support/core_ext/array/wrap'
|
2
|
+
|
3
|
+
module ActiveResource
|
4
|
+
module Callbacks
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
CALLBACKS = [
|
8
|
+
:before_validation, :after_validation, :before_save, :around_save, :after_save,
|
9
|
+
:before_create, :around_create, :after_create, :before_update, :around_update,
|
10
|
+
:after_update, :before_destroy, :around_destroy, :after_destroy
|
11
|
+
]
|
12
|
+
|
13
|
+
included do
|
14
|
+
extend ActiveModel::Callbacks
|
15
|
+
include ActiveModel::Validations::Callbacks
|
16
|
+
|
17
|
+
define_model_callbacks :save, :create, :update, :destroy
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
|
4
|
+
module ActiveResource # :nodoc:
|
5
|
+
class Collection # :nodoc:
|
6
|
+
include Enumerable
|
7
|
+
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :size, :last, :first, :[], :to => :to_a
|
8
|
+
|
9
|
+
# The array of actual elements returned by index actions
|
10
|
+
attr_accessor :elements, :resource_class, :original_params
|
11
|
+
|
12
|
+
# ActiveResource::Collection is a wrapper to handle parsing index responses that
|
13
|
+
# do not directly map to Rails conventions.
|
14
|
+
#
|
15
|
+
# You can define a custom class that inherets from ActiveResource::Collection
|
16
|
+
# in order to to set the elements instance.
|
17
|
+
#
|
18
|
+
# GET /posts.json delivers following response body:
|
19
|
+
# {
|
20
|
+
# posts: [
|
21
|
+
# {
|
22
|
+
# title: "ActiveResource now has associations",
|
23
|
+
# body: "Lorem Ipsum"
|
24
|
+
# }
|
25
|
+
# {...}
|
26
|
+
# ]
|
27
|
+
# next_page: "/posts.json?page=2"
|
28
|
+
# }
|
29
|
+
#
|
30
|
+
# A Post class can be setup to handle it with:
|
31
|
+
#
|
32
|
+
# class Post < ActiveResource::Base
|
33
|
+
# self.site = "http://example.com"
|
34
|
+
# self.collection_parser = PostParser
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# And the collection parser:
|
38
|
+
#
|
39
|
+
# class PostCollection < ActiveResource::Collection
|
40
|
+
# attr_accessor :next_page
|
41
|
+
# def initialize(parsed = {})
|
42
|
+
# @elements = parsed['posts']
|
43
|
+
# @next_page = parsed['next_page']
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# The result from a find method that returns multiple entries will now be a
|
48
|
+
# PostParser instance. ActiveResource::Collection includes Enumerable and
|
49
|
+
# instances can be iterated over just like an array.
|
50
|
+
# @posts = Post.find(:all) # => PostCollection:xxx
|
51
|
+
# @posts.next_page # => "/posts.json?page=2"
|
52
|
+
# @posts.map(&:id) # =>[1, 3, 5 ...]
|
53
|
+
#
|
54
|
+
# The initialize method will receive the ActiveResource::Formats parsed result
|
55
|
+
# and should set @elements.
|
56
|
+
def initialize(elements = [])
|
57
|
+
@elements = elements
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_a
|
61
|
+
elements
|
62
|
+
end
|
63
|
+
|
64
|
+
def collect!
|
65
|
+
return elements unless block_given?
|
66
|
+
set = []
|
67
|
+
each { |o| set << yield(o) }
|
68
|
+
@elements = set
|
69
|
+
self
|
70
|
+
end
|
71
|
+
alias map! collect!
|
72
|
+
|
73
|
+
def first_or_create(attributes = {})
|
74
|
+
first || resource_class.create(original_params.update(attributes))
|
75
|
+
rescue NoMethodError
|
76
|
+
raise "Cannot create resource from resource type: #{resource_class.inspect}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def first_or_initialize(attributes = {})
|
80
|
+
first || resource_class.new(original_params.update(attributes))
|
81
|
+
rescue NoMethodError
|
82
|
+
raise "Cannot build resource from resource type: #{resource_class.inspect}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -15,6 +15,7 @@ module ActiveResource
|
|
15
15
|
HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
|
16
16
|
:put => 'Content-Type',
|
17
17
|
:post => 'Content-Type',
|
18
|
+
:patch => 'Content-Type',
|
18
19
|
:delete => 'Accept',
|
19
20
|
:head => 'Accept'
|
20
21
|
}
|
@@ -39,14 +40,15 @@ module ActiveResource
|
|
39
40
|
|
40
41
|
# Set URI for remote service.
|
41
42
|
def site=(site)
|
42
|
-
@site = site.is_a?(URI) ? site : URI.
|
43
|
+
@site = site.is_a?(URI) ? site : URI.parse(site)
|
44
|
+
@ssl_options ||= {} if @site.is_a?(URI::HTTPS)
|
43
45
|
@user = URI.parser.unescape(@site.user) if @site.user
|
44
46
|
@password = URI.parser.unescape(@site.password) if @site.password
|
45
47
|
end
|
46
48
|
|
47
49
|
# Set the proxy for remote service.
|
48
50
|
def proxy=(proxy)
|
49
|
-
@proxy = proxy.is_a?(URI) ? proxy : URI.
|
51
|
+
@proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy)
|
50
52
|
end
|
51
53
|
|
52
54
|
# Sets the user for remote service.
|
@@ -70,8 +72,8 @@ module ActiveResource
|
|
70
72
|
end
|
71
73
|
|
72
74
|
# Hash of options applied to Net::HTTP instance when +site+ protocol is 'https'.
|
73
|
-
def ssl_options=(
|
74
|
-
@ssl_options =
|
75
|
+
def ssl_options=(options)
|
76
|
+
@ssl_options = options
|
75
77
|
end
|
76
78
|
|
77
79
|
# Executes a GET request.
|
@@ -86,6 +88,12 @@ module ActiveResource
|
|
86
88
|
with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) }
|
87
89
|
end
|
88
90
|
|
91
|
+
# Executes a PATCH request (see HTTP protocol documentation if unfamiliar).
|
92
|
+
# Used to update resources.
|
93
|
+
def patch(path, body = '', headers = {})
|
94
|
+
with_auth { request(:patch, path, body.to_s, build_request_headers(headers, :patch, self.site.merge(path))) }
|
95
|
+
end
|
96
|
+
|
89
97
|
# Executes a PUT request (see HTTP protocol documentation if unfamiliar).
|
90
98
|
# Used to update resources.
|
91
99
|
def put(path, body = '', headers = {})
|
@@ -166,38 +174,28 @@ module ActiveResource
|
|
166
174
|
end
|
167
175
|
|
168
176
|
def configure_http(http)
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
177
|
+
apply_ssl_options(http).tap do |https|
|
178
|
+
# Net::HTTP timeouts default to 60 seconds.
|
179
|
+
if defined? @timeout
|
180
|
+
https.open_timeout = @timeout
|
181
|
+
https.read_timeout = @timeout
|
182
|
+
end
|
175
183
|
end
|
176
|
-
|
177
|
-
http
|
178
184
|
end
|
179
185
|
|
180
186
|
def apply_ssl_options(http)
|
181
|
-
|
187
|
+
http.tap do |https|
|
188
|
+
# Skip config if site is already a https:// URI.
|
189
|
+
if defined? @ssl_options
|
190
|
+
http.use_ssl = true
|
182
191
|
|
183
|
-
|
184
|
-
|
185
|
-
return http unless defined?(@ssl_options)
|
192
|
+
# Default to no cert verification (WTF? FIXME)
|
193
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
186
194
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
http.key = @ssl_options[:key] if @ssl_options[:key]
|
192
|
-
|
193
|
-
http.cert_store = @ssl_options[:cert_store] if @ssl_options[:cert_store]
|
194
|
-
http.ssl_timeout = @ssl_options[:ssl_timeout] if @ssl_options[:ssl_timeout]
|
195
|
-
|
196
|
-
http.verify_mode = @ssl_options[:verify_mode] if @ssl_options[:verify_mode]
|
197
|
-
http.verify_callback = @ssl_options[:verify_callback] if @ssl_options[:verify_callback]
|
198
|
-
http.verify_depth = @ssl_options[:verify_depth] if @ssl_options[:verify_depth]
|
199
|
-
|
200
|
-
http
|
195
|
+
# All the SSL options have corresponding http settings.
|
196
|
+
@ssl_options.each { |key, value| http.send "#{key}=", value }
|
197
|
+
end
|
198
|
+
end
|
201
199
|
end
|
202
200
|
|
203
201
|
def default_header
|
@@ -2,25 +2,25 @@ require 'active_support/core_ext/object/blank'
|
|
2
2
|
|
3
3
|
module ActiveResource
|
4
4
|
# A module to support custom REST methods and sub-resources, allowing you to break out
|
5
|
-
# of the "default" REST methods with your own custom resource requests.
|
5
|
+
# of the "default" REST methods with your own custom resource requests. For example,
|
6
6
|
# say you use Rails to expose a REST service and configure your routes with:
|
7
7
|
#
|
8
8
|
# map.resources :people, :new => { :register => :post },
|
9
9
|
# :member => { :promote => :put, :deactivate => :delete }
|
10
10
|
# :collection => { :active => :get }
|
11
11
|
#
|
12
|
-
#
|
12
|
+
# This route set creates routes for the following HTTP requests:
|
13
13
|
#
|
14
|
-
# POST
|
15
|
-
# PUT
|
16
|
-
# DELETE
|
17
|
-
# GET
|
14
|
+
# POST /people/new/register.json # PeopleController.register
|
15
|
+
# PATCH/PUT /people/1/promote.json # PeopleController.promote with :id => 1
|
16
|
+
# DELETE /people/1/deactivate.json # PeopleController.deactivate with :id => 1
|
17
|
+
# GET /people/active.json # PeopleController.active
|
18
18
|
#
|
19
19
|
# Using this module, Active Resource can use these custom REST methods just like the
|
20
20
|
# standard methods.
|
21
21
|
#
|
22
22
|
# class Person < ActiveResource::Base
|
23
|
-
# self.site = "
|
23
|
+
# self.site = "https://37s.sunrise.com"
|
24
24
|
# end
|
25
25
|
#
|
26
26
|
# Person.new(:name => 'Ryan').post(:register) # POST /people/new/register.json
|
@@ -63,6 +63,10 @@ module ActiveResource
|
|
63
63
|
connection.post(custom_method_collection_url(custom_method_name, options), body, headers)
|
64
64
|
end
|
65
65
|
|
66
|
+
def patch(custom_method_name, options = {}, body = '')
|
67
|
+
connection.patch(custom_method_collection_url(custom_method_name, options), body, headers)
|
68
|
+
end
|
69
|
+
|
66
70
|
def put(custom_method_name, options = {}, body = '')
|
67
71
|
connection.put(custom_method_collection_url(custom_method_name, options), body, headers)
|
68
72
|
end
|
@@ -81,7 +85,7 @@ module ActiveResource
|
|
81
85
|
module ClassMethods
|
82
86
|
def custom_method_collection_url(method_name, options = {})
|
83
87
|
prefix_options, query_options = split_options(options)
|
84
|
-
"#{prefix(prefix_options)}#{collection_name}/#{method_name}
|
88
|
+
"#{prefix(prefix_options)}#{collection_name}/#{method_name}#{format_extension}#{query_string(query_options)}"
|
85
89
|
end
|
86
90
|
end
|
87
91
|
|
@@ -98,6 +102,10 @@ module ActiveResource
|
|
98
102
|
end
|
99
103
|
end
|
100
104
|
|
105
|
+
def patch(method_name, options = {}, body = '')
|
106
|
+
connection.patch(custom_method_element_url(method_name, options), body, self.class.headers)
|
107
|
+
end
|
108
|
+
|
101
109
|
def put(method_name, options = {}, body = '')
|
102
110
|
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
|
103
111
|
end
|
@@ -109,11 +117,11 @@ module ActiveResource
|
|
109
117
|
|
110
118
|
private
|
111
119
|
def custom_method_element_url(method_name, options = {})
|
112
|
-
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/#{method_name}
|
120
|
+
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/#{method_name}#{self.class.format_extension}#{self.class.__send__(:query_string, options)}"
|
113
121
|
end
|
114
122
|
|
115
123
|
def custom_method_new_element_url(method_name, options = {})
|
116
|
-
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}
|
124
|
+
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}#{self.class.format_extension}#{self.class.__send__(:query_string, options)}"
|
117
125
|
end
|
118
126
|
end
|
119
127
|
end
|
@@ -4,7 +4,7 @@ require 'active_support/core_ext/object/inclusion'
|
|
4
4
|
module ActiveResource
|
5
5
|
class InvalidRequestError < StandardError; end #:nodoc:
|
6
6
|
|
7
|
-
# One thing that has always been a pain with remote web services is testing.
|
7
|
+
# One thing that has always been a pain with remote web services is testing. The HttpMock
|
8
8
|
# class makes it easy to test your Active Resource models by creating a set of mock responses to specific
|
9
9
|
# requests.
|
10
10
|
#
|
@@ -15,17 +15,17 @@ module ActiveResource
|
|
15
15
|
#
|
16
16
|
# mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
|
17
17
|
#
|
18
|
-
# * <tt>http_method</tt> - The HTTP method to listen for.
|
18
|
+
# * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +patch+, +put+, +delete+ or
|
19
19
|
# +head+.
|
20
20
|
# * <tt>path</tt> - A string, starting with a "/", defining the URI that is expected to be
|
21
21
|
# called.
|
22
|
-
# * <tt>request_headers</tt> - Headers that are expected along with the request.
|
23
|
-
# hash format, such as <tt>{ "Content-Type" => "application/json" }</tt>.
|
22
|
+
# * <tt>request_headers</tt> - Headers that are expected along with the request. This argument uses a
|
23
|
+
# hash format, such as <tt>{ "Content-Type" => "application/json" }</tt>. This mock will only trigger
|
24
24
|
# if your tests sends a request with identical headers.
|
25
|
-
# * <tt>body</tt> - The data to be returned.
|
25
|
+
# * <tt>body</tt> - The data to be returned. This should be a string of Active Resource parseable content,
|
26
26
|
# such as Json.
|
27
27
|
# * <tt>status</tt> - The HTTP response code, as an integer, to return with the response.
|
28
|
-
# * <tt>response_headers</tt> - Headers to be returned with the response.
|
28
|
+
# * <tt>response_headers</tt> - Headers to be returned with the response. Uses the same hash format as
|
29
29
|
# <tt>request_headers</tt> listed above.
|
30
30
|
#
|
31
31
|
# In order for a mock to deliver its content, the incoming request must match by the <tt>http_method</tt>,
|
@@ -55,7 +55,7 @@ module ActiveResource
|
|
55
55
|
@responses = responses
|
56
56
|
end
|
57
57
|
|
58
|
-
[ :post, :put, :get, :delete, :head ].each do |method|
|
58
|
+
[ :post, :patch, :put, :get, :delete, :head ].each do |method|
|
59
59
|
# def post(path, request_headers = {}, body = nil, status = 200, response_headers = {})
|
60
60
|
# @responses[Request.new(:post, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
|
61
61
|
# end
|
@@ -133,7 +133,7 @@ module ActiveResource
|
|
133
133
|
#
|
134
134
|
# === Example
|
135
135
|
#
|
136
|
-
# Request.new(
|
136
|
+
# Request.new(method, path, nil, request_headers)
|
137
137
|
#
|
138
138
|
# @matz = { :person => { :id => 1, :name => "Matz" } }.to_json
|
139
139
|
#
|
@@ -217,7 +217,7 @@ module ActiveResource
|
|
217
217
|
end
|
218
218
|
|
219
219
|
# body? methods
|
220
|
-
{ true => %w(post put),
|
220
|
+
{ true => %w(post patch put),
|
221
221
|
false => %w(get delete head) }.each do |has_body, methods|
|
222
222
|
methods.each do |method|
|
223
223
|
# def post(path, body, headers)
|
@@ -292,11 +292,8 @@ module ActiveResource
|
|
292
292
|
@body = nil
|
293
293
|
end
|
294
294
|
|
295
|
-
|
296
|
-
|
297
|
-
else
|
298
|
-
self['Content-Length'] = body.size.to_s
|
299
|
-
end
|
295
|
+
self['Content-Length'] = @body.nil? ? "0" : body.size.to_s
|
296
|
+
|
300
297
|
end
|
301
298
|
|
302
299
|
# Returns true if code is 2xx,
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
require 'active_support/core_ext/module/deprecation'
|
3
|
+
|
4
|
+
module ActiveResource
|
5
|
+
# = Active Resource reflection
|
6
|
+
#
|
7
|
+
# Associations in ActiveResource would be used to resolve nested attributes
|
8
|
+
# in a response with correct classes.
|
9
|
+
# Now they could be specified over Associations with the options :class_name
|
10
|
+
module Reflection # :nodoc:
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
|
13
|
+
included do
|
14
|
+
class_attribute :reflections
|
15
|
+
self.reflections = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def create_reflection(macro, name, options)
|
20
|
+
reflection = AssociationReflection.new(macro, name, options)
|
21
|
+
self.reflections = self.reflections.merge(name => reflection)
|
22
|
+
reflection
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
class AssociationReflection
|
28
|
+
|
29
|
+
def initialize(macro, name, options)
|
30
|
+
@macro, @name, @options = macro, name, options
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the name of the macro.
|
34
|
+
#
|
35
|
+
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
|
36
|
+
attr_reader :name
|
37
|
+
|
38
|
+
# Returns the macro type.
|
39
|
+
#
|
40
|
+
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
|
41
|
+
attr_reader :macro
|
42
|
+
|
43
|
+
# Returns the hash of options used for the macro.
|
44
|
+
#
|
45
|
+
# <tt>has_many :clients</tt> returns +{}+
|
46
|
+
attr_reader :options
|
47
|
+
|
48
|
+
# Returns the class for the macro.
|
49
|
+
#
|
50
|
+
# <tt>has_many :clients</tt> returns the Client class
|
51
|
+
def klass
|
52
|
+
@klass ||= class_name.constantize
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the class name for the macro.
|
56
|
+
#
|
57
|
+
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
|
58
|
+
def class_name
|
59
|
+
@class_name ||= derive_class_name
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the foreign_key for the macro.
|
63
|
+
def foreign_key
|
64
|
+
@foreign_key ||= self.options[:foreign_key] || "#{self.name.to_s.downcase}_id"
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def derive_class_name
|
69
|
+
return (options[:class_name] ? options[:class_name].to_s : name.to_s).classify
|
70
|
+
end
|
71
|
+
|
72
|
+
def derive_foreign_key
|
73
|
+
return options[:foreign_key] ? options[:foreign_key].to_s : "#{name.to_s.downcase}_id"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|