activeresource 2.3.18 → 3.0.0.beta
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.
- data/CHANGELOG +1 -29
- data/README +7 -7
- data/examples/simple.rb +15 -0
- data/lib/active_resource.rb +17 -17
- data/lib/active_resource/base.rb +320 -66
- data/lib/active_resource/connection.rb +100 -100
- data/lib/active_resource/custom_methods.rb +33 -36
- data/lib/active_resource/exceptions.rb +4 -1
- data/lib/active_resource/formats.rb +4 -4
- data/lib/active_resource/formats/json_format.rb +2 -0
- data/lib/active_resource/formats/xml_format.rb +2 -0
- data/lib/active_resource/http_mock.rb +12 -103
- data/lib/active_resource/observing.rb +21 -0
- data/lib/active_resource/railtie.rb +17 -0
- data/lib/active_resource/railties/subscriber.rb +15 -0
- data/lib/active_resource/schema.rb +55 -0
- data/lib/active_resource/validations.rb +66 -215
- data/lib/active_resource/version.rb +3 -3
- metadata +29 -43
- data/Rakefile +0 -137
- data/lib/activeresource.rb +0 -2
- data/test/abstract_unit.rb +0 -21
- data/test/authorization_test.rb +0 -122
- data/test/base/custom_methods_test.rb +0 -100
- data/test/base/equality_test.rb +0 -52
- data/test/base/load_test.rb +0 -161
- data/test/base_errors_test.rb +0 -98
- data/test/base_test.rb +0 -1087
- data/test/connection_test.rb +0 -238
- data/test/fixtures/beast.rb +0 -14
- data/test/fixtures/customer.rb +0 -3
- data/test/fixtures/person.rb +0 -3
- data/test/fixtures/proxy.rb +0 -4
- data/test/fixtures/street_address.rb +0 -4
- data/test/format_test.rb +0 -112
- data/test/http_mock_test.rb +0 -155
- data/test/setter_trap.rb +0 -26
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActiveResource
|
2
|
+
module Observing
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include ActiveModel::Observing
|
5
|
+
|
6
|
+
included do
|
7
|
+
%w( create save update destroy ).each do |method|
|
8
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
9
|
+
def #{method}_with_notifications(*args, &block)
|
10
|
+
notify_observers(:before_#{method})
|
11
|
+
if result = #{method}_without_notifications(*args, &block)
|
12
|
+
notify_observers(:after_#{method})
|
13
|
+
end
|
14
|
+
result
|
15
|
+
end
|
16
|
+
EOS
|
17
|
+
alias_method_chain(method, :notifications)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "active_resource"
|
2
|
+
require "rails"
|
3
|
+
|
4
|
+
module ActiveResource
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
railtie_name :active_resource
|
7
|
+
|
8
|
+
require "active_resource/railties/subscriber"
|
9
|
+
subscriber ActiveResource::Railties::Subscriber.new
|
10
|
+
|
11
|
+
initializer "active_resource.set_configs" do |app|
|
12
|
+
app.config.active_resource.each do |k,v|
|
13
|
+
ActiveResource::Base.send "#{k}=", v
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ActiveResource
|
2
|
+
module Railties
|
3
|
+
class Subscriber < Rails::Subscriber
|
4
|
+
def request(event)
|
5
|
+
result = event.payload[:result]
|
6
|
+
info "#{event.payload[:method].to_s.upcase} #{event.payload[:request_uri]}"
|
7
|
+
info "--> %d %s %d (%.1fms)" % [result.code, result.message, result.body.to_s.length, event.duration]
|
8
|
+
end
|
9
|
+
|
10
|
+
def logger
|
11
|
+
ActiveResource::Base.logger
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'active_resource/exceptions'
|
2
|
+
|
3
|
+
module ActiveResource # :nodoc:
|
4
|
+
class Schema # :nodoc:
|
5
|
+
# attributes can be known to be one of these types. They are easy to
|
6
|
+
# cast to/from.
|
7
|
+
KNOWN_ATTRIBUTE_TYPES = %w( string integer float )
|
8
|
+
|
9
|
+
# An array of attribute definitions, representing the attributes that
|
10
|
+
# have been defined.
|
11
|
+
attr_accessor :attrs
|
12
|
+
|
13
|
+
# The internals of an Active Resource Schema are very simple -
|
14
|
+
# unlike an Active Record TableDefinition (on which it is based).
|
15
|
+
# It provides a set of convenience methods for people to define their
|
16
|
+
# schema using the syntax:
|
17
|
+
# schema do
|
18
|
+
# string :foo
|
19
|
+
# integer :bar
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# The schema stores the name and type of each attribute. That is then
|
23
|
+
# read out by the schema method to populate the actual
|
24
|
+
# Resource's schema
|
25
|
+
def initialize
|
26
|
+
@attrs = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def attribute(name, type, options = {})
|
30
|
+
raise ArgumentError, "Unknown Attribute type: #{type.inspect} for key: #{name.inspect}" unless type.nil? || Schema::KNOWN_ATTRIBUTE_TYPES.include?(type.to_s)
|
31
|
+
|
32
|
+
the_type = type.to_s
|
33
|
+
# TODO: add defaults
|
34
|
+
#the_attr = [type.to_s]
|
35
|
+
#the_attr << options[:default] if options.has_key? :default
|
36
|
+
@attrs[name.to_s] = the_type
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# The following are the attribute types supported by Active Resource
|
41
|
+
# migrations.
|
42
|
+
# TODO: We should eventually support all of these:
|
43
|
+
# %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |attr_type|
|
44
|
+
KNOWN_ATTRIBUTE_TYPES.each do |attr_type|
|
45
|
+
class_eval <<-EOV
|
46
|
+
def #{attr_type.to_s}(*args)
|
47
|
+
options = args.extract_options!
|
48
|
+
attr_names = args
|
49
|
+
|
50
|
+
attr_names.each { |name| attribute(name, '#{attr_type}', options) }
|
51
|
+
end
|
52
|
+
EOV
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -1,207 +1,17 @@
|
|
1
|
+
require 'active_support/core_ext/array/wrap'
|
2
|
+
|
1
3
|
module ActiveResource
|
2
4
|
class ResourceInvalid < ClientError #:nodoc:
|
3
5
|
end
|
4
6
|
|
5
7
|
# Active Resource validation is reported to and from this object, which is used by Base#save
|
6
8
|
# to determine whether the object in a valid state to be saved. See usage example in Validations.
|
7
|
-
class Errors
|
8
|
-
include Enumerable
|
9
|
-
attr_reader :errors
|
10
|
-
|
11
|
-
delegate :empty?, :to => :errors
|
12
|
-
|
13
|
-
def initialize(base) # :nodoc:
|
14
|
-
@base, @errors = base, {}
|
15
|
-
end
|
16
|
-
|
17
|
-
# Add an error to the base Active Resource object rather than an attribute.
|
18
|
-
#
|
19
|
-
# ==== Examples
|
20
|
-
# my_folder = Folder.find(1)
|
21
|
-
# my_folder.errors.add_to_base("You can't edit an existing folder")
|
22
|
-
# my_folder.errors.on_base
|
23
|
-
# # => "You can't edit an existing folder"
|
24
|
-
#
|
25
|
-
# my_folder.errors.add_to_base("This folder has been tagged as frozen")
|
26
|
-
# my_folder.valid?
|
27
|
-
# # => false
|
28
|
-
# my_folder.errors.on_base
|
29
|
-
# # => ["You can't edit an existing folder", "This folder has been tagged as frozen"]
|
30
|
-
#
|
31
|
-
def add_to_base(msg)
|
32
|
-
add(:base, msg)
|
33
|
-
end
|
34
|
-
|
35
|
-
# Adds an error to an Active Resource object's attribute (named for the +attribute+ parameter)
|
36
|
-
# with the error message in +msg+.
|
37
|
-
#
|
38
|
-
# ==== Examples
|
39
|
-
# my_resource = Node.find(1)
|
40
|
-
# my_resource.errors.add('name', 'can not be "base"') if my_resource.name == 'base'
|
41
|
-
# my_resource.errors.on('name')
|
42
|
-
# # => 'can not be "base"!'
|
43
|
-
#
|
44
|
-
# my_resource.errors.add('desc', 'can not be blank') if my_resource.desc == ''
|
45
|
-
# my_resource.valid?
|
46
|
-
# # => false
|
47
|
-
# my_resource.errors.on('desc')
|
48
|
-
# # => 'can not be blank!'
|
49
|
-
#
|
50
|
-
def add(attribute, msg)
|
51
|
-
@errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
|
52
|
-
@errors[attribute.to_s] << msg
|
53
|
-
end
|
54
|
-
|
55
|
-
# Returns true if the specified +attribute+ has errors associated with it.
|
56
|
-
#
|
57
|
-
# ==== Examples
|
58
|
-
# my_resource = Disk.find(1)
|
59
|
-
# my_resource.errors.add('location', 'must be Main') unless my_resource.location == 'Main'
|
60
|
-
# my_resource.errors.on('location')
|
61
|
-
# # => 'must be Main!'
|
62
|
-
#
|
63
|
-
# my_resource.errors.invalid?('location')
|
64
|
-
# # => true
|
65
|
-
# my_resource.errors.invalid?('name')
|
66
|
-
# # => false
|
67
|
-
def invalid?(attribute)
|
68
|
-
!@errors[attribute.to_s].nil?
|
69
|
-
end
|
70
|
-
|
71
|
-
# A method to return the errors associated with +attribute+, which returns nil, if no errors are
|
72
|
-
# associated with the specified +attribute+, the error message if one error is associated with the specified +attribute+,
|
73
|
-
# or an array of error messages if more than one error is associated with the specified +attribute+.
|
74
|
-
#
|
75
|
-
# ==== Examples
|
76
|
-
# my_person = Person.new(params[:person])
|
77
|
-
# my_person.errors.on('login')
|
78
|
-
# # => nil
|
79
|
-
#
|
80
|
-
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
81
|
-
# my_person.errors.on('login')
|
82
|
-
# # => 'can not be empty'
|
83
|
-
#
|
84
|
-
# my_person.errors.add('login', 'can not be longer than 10 characters') if my_person.login.length > 10
|
85
|
-
# my_person.errors.on('login')
|
86
|
-
# # => ['can not be empty', 'can not be longer than 10 characters']
|
87
|
-
def on(attribute)
|
88
|
-
errors = @errors[attribute.to_s]
|
89
|
-
return nil if errors.nil?
|
90
|
-
errors.size == 1 ? errors.first : errors
|
91
|
-
end
|
92
|
-
|
93
|
-
alias :[] :on
|
94
|
-
|
95
|
-
# A method to return errors assigned to +base+ object through add_to_base, which returns nil, if no errors are
|
96
|
-
# associated with the specified +attribute+, the error message if one error is associated with the specified +attribute+,
|
97
|
-
# or an array of error messages if more than one error is associated with the specified +attribute+.
|
98
|
-
#
|
99
|
-
# ==== Examples
|
100
|
-
# my_account = Account.find(1)
|
101
|
-
# my_account.errors.on_base
|
102
|
-
# # => nil
|
103
|
-
#
|
104
|
-
# my_account.errors.add_to_base("This account is frozen")
|
105
|
-
# my_account.errors.on_base
|
106
|
-
# # => "This account is frozen"
|
107
|
-
#
|
108
|
-
# my_account.errors.add_to_base("This account has been closed")
|
109
|
-
# my_account.errors.on_base
|
110
|
-
# # => ["This account is frozen", "This account has been closed"]
|
111
|
-
#
|
112
|
-
def on_base
|
113
|
-
on(:base)
|
114
|
-
end
|
115
|
-
|
116
|
-
# Yields each attribute and associated message per error added.
|
117
|
-
#
|
118
|
-
# ==== Examples
|
119
|
-
# my_person = Person.new(params[:person])
|
120
|
-
#
|
121
|
-
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
122
|
-
# my_person.errors.add('password', 'can not be empty') if my_person.password == ''
|
123
|
-
# messages = ''
|
124
|
-
# my_person.errors.each {|attr, msg| messages += attr.humanize + " " + msg + "<br />"}
|
125
|
-
# messages
|
126
|
-
# # => "Login can not be empty<br />Password can not be empty<br />"
|
127
|
-
#
|
128
|
-
def each
|
129
|
-
@errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
|
130
|
-
end
|
131
|
-
|
132
|
-
# Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
|
133
|
-
# through iteration as "First name can't be empty".
|
134
|
-
#
|
135
|
-
# ==== Examples
|
136
|
-
# my_person = Person.new(params[:person])
|
137
|
-
#
|
138
|
-
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
139
|
-
# my_person.errors.add('password', 'can not be empty') if my_person.password == ''
|
140
|
-
# messages = ''
|
141
|
-
# my_person.errors.each_full {|msg| messages += msg + "<br/>"}
|
142
|
-
# messages
|
143
|
-
# # => "Login can not be empty<br />Password can not be empty<br />"
|
144
|
-
#
|
145
|
-
def each_full
|
146
|
-
full_messages.each { |msg| yield msg }
|
147
|
-
end
|
148
|
-
|
149
|
-
# Returns all the full error messages in an array.
|
150
|
-
#
|
151
|
-
# ==== Examples
|
152
|
-
# my_person = Person.new(params[:person])
|
153
|
-
#
|
154
|
-
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
155
|
-
# my_person.errors.add('password', 'can not be empty') if my_person.password == ''
|
156
|
-
# messages = ''
|
157
|
-
# my_person.errors.full_messages.each {|msg| messages += msg + "<br/>"}
|
158
|
-
# messages
|
159
|
-
# # => "Login can not be empty<br />Password can not be empty<br />"
|
160
|
-
#
|
161
|
-
def full_messages
|
162
|
-
full_messages = []
|
163
|
-
|
164
|
-
@errors.each_key do |attr|
|
165
|
-
@errors[attr].each do |msg|
|
166
|
-
next if msg.nil?
|
167
|
-
|
168
|
-
if attr == "base"
|
169
|
-
full_messages << msg
|
170
|
-
else
|
171
|
-
full_messages << [attr.humanize, msg].join(' ')
|
172
|
-
end
|
173
|
-
end
|
174
|
-
end
|
175
|
-
full_messages
|
176
|
-
end
|
177
|
-
|
178
|
-
def clear
|
179
|
-
@errors = {}
|
180
|
-
end
|
181
|
-
|
182
|
-
# Returns the total number of errors added. Two errors added to the same attribute will be counted as such
|
183
|
-
# with this as well.
|
184
|
-
#
|
185
|
-
# ==== Examples
|
186
|
-
# my_person = Person.new(params[:person])
|
187
|
-
# my_person.errors.size
|
188
|
-
# # => 0
|
189
|
-
#
|
190
|
-
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
191
|
-
# my_person.errors.add('password', 'can not be empty') if my_person.password == ''
|
192
|
-
# my_person.error.size
|
193
|
-
# # => 2
|
194
|
-
#
|
195
|
-
def size
|
196
|
-
@errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
|
197
|
-
end
|
198
|
-
|
199
|
-
alias_method :count, :size
|
200
|
-
alias_method :length, :size
|
201
|
-
|
9
|
+
class Errors < ActiveModel::Errors
|
202
10
|
# Grabs errors from an array of messages (like ActiveRecord::Validations)
|
203
|
-
|
204
|
-
|
11
|
+
# The second parameter directs the errors cache to be cleared (default)
|
12
|
+
# or not (by passing true)
|
13
|
+
def from_array(messages, save_cache = false)
|
14
|
+
clear unless save_cache
|
205
15
|
humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) }
|
206
16
|
messages.each do |message|
|
207
17
|
attr_message = humanized_attributes.keys.detect do |attr_name|
|
@@ -210,20 +20,20 @@ module ActiveResource
|
|
210
20
|
end
|
211
21
|
end
|
212
22
|
|
213
|
-
|
23
|
+
self[:base] << message if attr_message.nil?
|
214
24
|
end
|
215
25
|
end
|
216
26
|
|
217
|
-
# Grabs errors from
|
218
|
-
def from_json(json)
|
27
|
+
# Grabs errors from a json response.
|
28
|
+
def from_json(json, save_cache = false)
|
219
29
|
array = ActiveSupport::JSON.decode(json)['errors'] rescue []
|
220
|
-
from_array array
|
30
|
+
from_array array, save_cache
|
221
31
|
end
|
222
32
|
|
223
|
-
# Grabs errors from
|
224
|
-
def from_xml(xml)
|
33
|
+
# Grabs errors from an XML response.
|
34
|
+
def from_xml(xml, save_cache = false)
|
225
35
|
array = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue []
|
226
|
-
from_array array
|
36
|
+
from_array array, save_cache
|
227
37
|
end
|
228
38
|
end
|
229
39
|
|
@@ -243,32 +53,70 @@ module ActiveResource
|
|
243
53
|
# person.errors.empty? # => false
|
244
54
|
# person.errors.count # => 1
|
245
55
|
# person.errors.full_messages # => ["Last name can't be empty"]
|
246
|
-
# person.errors
|
56
|
+
# person.errors[:last_name] # => ["can't be empty"]
|
247
57
|
# person.last_name = "Halpert"
|
248
58
|
# person.save # => true (and person is now saved to the remote service)
|
249
59
|
#
|
250
60
|
module Validations
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
61
|
+
extend ActiveSupport::Concern
|
62
|
+
include ActiveModel::Validations
|
63
|
+
|
64
|
+
included do
|
65
|
+
alias_method_chain :save, :validation
|
255
66
|
end
|
256
67
|
|
257
68
|
# Validate a resource and save (POST) it to the remote web service.
|
258
|
-
|
259
|
-
|
260
|
-
|
69
|
+
# If any local validations fail - the save (POST) will not be attempted.
|
70
|
+
def save_with_validation(options=nil)
|
71
|
+
perform_validation = case options
|
72
|
+
when Hash
|
73
|
+
options[:validate] != false
|
74
|
+
when NilClass
|
75
|
+
true
|
76
|
+
else
|
77
|
+
ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
|
78
|
+
options
|
79
|
+
end
|
80
|
+
|
81
|
+
# clear the remote validations so they don't interfere with the local
|
82
|
+
# ones. Otherwise we get an endless loop and can never change the
|
83
|
+
# fields so as to make the resource valid
|
84
|
+
@remote_errors = nil
|
85
|
+
if perform_validation && valid? || !perform_validation
|
86
|
+
save_without_validation
|
87
|
+
true
|
88
|
+
else
|
89
|
+
false
|
90
|
+
end
|
261
91
|
rescue ResourceInvalid => error
|
92
|
+
# cache the remote errors because every call to <tt>valid?</tt> clears
|
93
|
+
# all errors. We must keep a copy to add these back after local
|
94
|
+
# validations
|
95
|
+
@remote_errors = error
|
96
|
+
load_remote_errors(@remote_errors, true)
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
# Loads the set of remote errors into the object's Errors based on the
|
102
|
+
# content-type of the error-block received
|
103
|
+
def load_remote_errors(remote_errors, save_cache = false ) #:nodoc:
|
262
104
|
case self.class.format
|
263
105
|
when ActiveResource::Formats[:xml]
|
264
|
-
errors.from_xml(
|
106
|
+
errors.from_xml(remote_errors.response.body, save_cache)
|
265
107
|
when ActiveResource::Formats[:json]
|
266
|
-
errors.from_json(
|
108
|
+
errors.from_json(remote_errors.response.body, save_cache)
|
267
109
|
end
|
268
|
-
false
|
269
110
|
end
|
270
111
|
|
271
112
|
# Checks for errors on an object (i.e., is resource.errors empty?).
|
113
|
+
#
|
114
|
+
# Runs all the specified local validations and returns true if no errors
|
115
|
+
# were added, otherwise false.
|
116
|
+
# Runs local validations (eg those on your Active Resource model), and
|
117
|
+
# also any errors returned from the remote system the last time we
|
118
|
+
# saved.
|
119
|
+
# Remote errors can only be cleared by trying to re-save the resource.
|
272
120
|
#
|
273
121
|
# ==== Examples
|
274
122
|
# my_person = Person.create(params[:person])
|
@@ -278,7 +126,10 @@ module ActiveResource
|
|
278
126
|
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
279
127
|
# my_person.valid?
|
280
128
|
# # => false
|
129
|
+
#
|
281
130
|
def valid?
|
131
|
+
super
|
132
|
+
load_remote_errors(@remote_errors, true) if defined?(@remote_errors) && @remote_errors.present?
|
282
133
|
errors.empty?
|
283
134
|
end
|
284
135
|
|