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,207 @@
1
+ require 'active_resource/connection'
2
+
3
+ module ActiveResource
4
+ class InvalidRequestError < StandardError; end #:nodoc:
5
+
6
+ # One thing that has always been a pain with remote web services is testing. The HttpMock
7
+ # class makes it easy to test your Active Resource models by creating a set of mock responses to specific
8
+ # requests.
9
+ #
10
+ # To test your Active Resource model, you simply call the ActiveResource::HttpMock.respond_to
11
+ # method with an attached block. The block declares a set of URIs with expected input, and the output
12
+ # each request should return. The passed in block has any number of entries in the following generalized
13
+ # format:
14
+ #
15
+ # mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
16
+ #
17
+ # * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +put+, +delete+ or
18
+ # +head+.
19
+ # * <tt>path</tt> - A string, starting with a "/", defining the URI that is expected to be
20
+ # called.
21
+ # * <tt>request_headers</tt> - Headers that are expected along with the request. This argument uses a
22
+ # hash format, such as <tt>{ "Content-Type" => "application/xml" }</tt>. This mock will only trigger
23
+ # if your tests sends a request with identical headers.
24
+ # * <tt>body</tt> - The data to be returned. This should be a string of Active Resource parseable content,
25
+ # such as XML.
26
+ # * <tt>status</tt> - The HTTP response code, as an integer, to return with the response.
27
+ # * <tt>response_headers</tt> - Headers to be returned with the response. Uses the same hash format as
28
+ # <tt>request_headers</tt> listed above.
29
+ #
30
+ # In order for a mock to deliver its content, the incoming request must match by the <tt>http_method</tt>,
31
+ # +path+ and <tt>request_headers</tt>. If no match is found an InvalidRequestError exception
32
+ # will be raised letting you know you need to create a new mock for that request.
33
+ #
34
+ # ==== Example
35
+ # def setup
36
+ # @matz = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
37
+ # ActiveResource::HttpMock.respond_to do |mock|
38
+ # mock.post "/people.xml", {}, @matz, 201, "Location" => "/people/1.xml"
39
+ # mock.get "/people/1.xml", {}, @matz
40
+ # mock.put "/people/1.xml", {}, nil, 204
41
+ # mock.delete "/people/1.xml", {}, nil, 200
42
+ # end
43
+ # end
44
+ #
45
+ # def test_get_matz
46
+ # person = Person.find(1)
47
+ # assert_equal "Matz", person.name
48
+ # end
49
+ #
50
+ class HttpMock
51
+ class Responder #:nodoc:
52
+ def initialize(responses)
53
+ @responses = responses
54
+ end
55
+
56
+ for method in [ :post, :put, :get, :delete, :head ]
57
+ # def post(path, request_headers = {}, body = nil, status = 200, response_headers = {})
58
+ # @responses[Request.new(:post, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
59
+ # end
60
+ module_eval <<-EOE, __FILE__, __LINE__
61
+ def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {})
62
+ @responses << [Request.new(:#{method}, path, nil, request_headers), Response.new(body || "", status, response_headers)]
63
+ end
64
+ EOE
65
+ end
66
+ end
67
+
68
+ class << self
69
+
70
+ # Returns an array of all request objects that have been sent to the mock. You can use this to check
71
+ # if your model actually sent an HTTP request.
72
+ #
73
+ # ==== Example
74
+ # def setup
75
+ # @matz = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
76
+ # ActiveResource::HttpMock.respond_to do |mock|
77
+ # mock.get "/people/1.xml", {}, @matz
78
+ # end
79
+ # end
80
+ #
81
+ # def test_should_request_remote_service
82
+ # person = Person.find(1) # Call the remote service
83
+ #
84
+ # # This request object has the same HTTP method and path as declared by the mock
85
+ # expected_request = ActiveResource::Request.new(:get, "/people/1.xml")
86
+ #
87
+ # # Assert that the mock received, and responded to, the expected request from the model
88
+ # assert ActiveResource::HttpMock.requests.include?(expected_request)
89
+ # end
90
+ def requests
91
+ @@requests ||= []
92
+ end
93
+
94
+ # Returns the list of requests and their mocked responses. Look up a
95
+ # response for a request using responses.assoc(request).
96
+ def responses
97
+ @@responses ||= []
98
+ end
99
+
100
+ # Accepts a block which declares a set of requests and responses for the HttpMock to respond to. See the main
101
+ # ActiveResource::HttpMock description for a more detailed explanation.
102
+ def respond_to(pairs = {}) #:yields: mock
103
+ reset!
104
+ responses.concat pairs.to_a
105
+ if block_given?
106
+ yield Responder.new(responses)
107
+ else
108
+ Responder.new(responses)
109
+ end
110
+ end
111
+
112
+ # Deletes all logged requests and responses.
113
+ def reset!
114
+ requests.clear
115
+ responses.clear
116
+ end
117
+ end
118
+
119
+ # body? methods
120
+ { true => %w(post put),
121
+ false => %w(get delete head) }.each do |has_body, methods|
122
+ methods.each do |method|
123
+ # def post(path, body, headers)
124
+ # request = ActiveResource::Request.new(:post, path, body, headers)
125
+ # self.class.requests << request
126
+ # self.class.responses.assoc(request).try(:second) || raise(InvalidRequestError.new("No response recorded for #{request}"))
127
+ # end
128
+ module_eval <<-EOE, __FILE__, __LINE__
129
+ def #{method}(path, #{'body, ' if has_body}headers)
130
+ request = ActiveResource::Request.new(:#{method}, path, #{has_body ? 'body, ' : 'nil, '}headers)
131
+ self.class.requests << request
132
+ self.class.responses.assoc(request).try(:second) || raise(InvalidRequestError.new("No response recorded for \#{request}"))
133
+ end
134
+ EOE
135
+ end
136
+ end
137
+
138
+ def initialize(site) #:nodoc:
139
+ @site = site
140
+ end
141
+ end
142
+
143
+ class Request
144
+ attr_accessor :path, :method, :body, :headers
145
+
146
+ def initialize(method, path, body = nil, headers = {})
147
+ @method, @path, @body, @headers = method, path, body, headers.merge(ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[method] => 'application/xml')
148
+ end
149
+
150
+ def ==(req)
151
+ path == req.path && method == req.method && headers == req.headers
152
+ end
153
+
154
+ def to_s
155
+ "<#{method.to_s.upcase}: #{path} [#{headers}] (#{body})>"
156
+ end
157
+ end
158
+
159
+ class Response
160
+ attr_accessor :body, :message, :code, :headers
161
+
162
+ def initialize(body, message = 200, headers = {})
163
+ @body, @message, @headers = body, message.to_s, headers
164
+ @code = @message[0,3].to_i
165
+
166
+ resp_cls = Net::HTTPResponse::CODE_TO_OBJ[@code.to_s]
167
+ if resp_cls && !resp_cls.body_permitted?
168
+ @body = nil
169
+ end
170
+
171
+ if @body.nil?
172
+ self['Content-Length'] = "0"
173
+ else
174
+ self['Content-Length'] = body.size.to_s
175
+ end
176
+ end
177
+
178
+ def success?
179
+ (200..299).include?(code)
180
+ end
181
+
182
+ def [](key)
183
+ headers[key]
184
+ end
185
+
186
+ def []=(key, value)
187
+ headers[key] = value
188
+ end
189
+
190
+ def ==(other)
191
+ if (other.is_a?(Response))
192
+ other.body == body && other.message == message && other.headers == headers
193
+ else
194
+ false
195
+ end
196
+ end
197
+ end
198
+
199
+ class Connection
200
+ private
201
+ silence_warnings do
202
+ def http
203
+ @http ||= HttpMock.new(@site)
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,290 @@
1
+ module ActiveResource
2
+ class ResourceInvalid < ClientError #:nodoc:
3
+ end
4
+
5
+ # Active Resource validation is reported to and from this object, which is used by Base#save
6
+ # 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
+
202
+ # Grabs errors from an array of messages (like ActiveRecord::Validations)
203
+ def from_array(messages)
204
+ clear
205
+ humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) }
206
+ messages.each do |message|
207
+ attr_message = humanized_attributes.keys.detect do |attr_name|
208
+ if message[0, attr_name.size + 1] == "#{attr_name} "
209
+ add humanized_attributes[attr_name], message[(attr_name.size + 1)..-1]
210
+ end
211
+ end
212
+
213
+ add_to_base message if attr_message.nil?
214
+ end
215
+ end
216
+
217
+ # Grabs errors from the json response.
218
+ def from_json(json)
219
+ array = ActiveSupport::JSON.decode(json)['errors'] rescue []
220
+ from_array array
221
+ end
222
+
223
+ # Grabs errors from the XML response.
224
+ def from_xml(xml)
225
+ array = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue []
226
+ from_array array
227
+ end
228
+ end
229
+
230
+ # Module to support validation and errors with Active Resource objects. The module overrides
231
+ # Base#save to rescue ActiveResource::ResourceInvalid exceptions and parse the errors returned
232
+ # in the web service response. The module also adds an +errors+ collection that mimics the interface
233
+ # of the errors provided by ActiveRecord::Errors.
234
+ #
235
+ # ==== Example
236
+ #
237
+ # Consider a Person resource on the server requiring both a +first_name+ and a +last_name+ with a
238
+ # <tt>validates_presence_of :first_name, :last_name</tt> declaration in the model:
239
+ #
240
+ # person = Person.new(:first_name => "Jim", :last_name => "")
241
+ # person.save # => false (server returns an HTTP 422 status code and errors)
242
+ # person.valid? # => false
243
+ # person.errors.empty? # => false
244
+ # person.errors.count # => 1
245
+ # person.errors.full_messages # => ["Last name can't be empty"]
246
+ # person.errors.on(:last_name) # => "can't be empty"
247
+ # person.last_name = "Halpert"
248
+ # person.save # => true (and person is now saved to the remote service)
249
+ #
250
+ module Validations
251
+ def self.included(base) # :nodoc:
252
+ base.class_eval do
253
+ alias_method_chain :save, :validation
254
+ end
255
+ end
256
+
257
+ # Validate a resource and save (POST) it to the remote web service.
258
+ def save_with_validation
259
+ save_without_validation
260
+ true
261
+ rescue ResourceInvalid => error
262
+ case error.response['Content-Type']
263
+ when /xml/
264
+ errors.from_xml(error.response.body)
265
+ when /json/
266
+ errors.from_json(error.response.body)
267
+ end
268
+ false
269
+ end
270
+
271
+ # Checks for errors on an object (i.e., is resource.errors empty?).
272
+ #
273
+ # ==== Examples
274
+ # my_person = Person.create(params[:person])
275
+ # my_person.valid?
276
+ # # => true
277
+ #
278
+ # my_person.errors.add('login', 'can not be empty') if my_person.login == ''
279
+ # my_person.valid?
280
+ # # => false
281
+ def valid?
282
+ errors.empty?
283
+ end
284
+
285
+ # Returns the Errors object that holds all information about attribute error messages.
286
+ def errors
287
+ @errors ||= Errors.new(self)
288
+ end
289
+ end
290
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveResource
2
+ module VERSION #:nodoc:
3
+ MAJOR = 2
4
+ MINOR = 3
5
+ TINY = 5
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,44 @@
1
+ #--
2
+ # Copyright (c) 2006 David Heinemeier Hansson
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ begin
25
+ require 'active_support'
26
+ rescue LoadError
27
+ activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
28
+ if File.directory?(activesupport_path)
29
+ $:.unshift activesupport_path
30
+ require 'active_support'
31
+ end
32
+ end
33
+
34
+ require 'active_resource/formats'
35
+ require 'active_resource/base'
36
+ require 'active_resource/validations'
37
+ require 'active_resource/custom_methods'
38
+
39
+ module ActiveResource
40
+ Base.class_eval do
41
+ include Validations
42
+ include CustomMethods
43
+ end
44
+ end
@@ -0,0 +1,2 @@
1
+ require 'active_resource'
2
+ ActiveSupport::Deprecation.warn 'require "activeresource" is deprecated and will be removed in Rails 3. Use require "active_resource" instead.'
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'active_support/test_case'
4
+
5
+ $:.unshift "#{File.dirname(__FILE__)}/../lib"
6
+ $:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib"
7
+ require 'active_resource'
8
+ require 'active_resource/http_mock'
9
+
10
+ $:.unshift "#{File.dirname(__FILE__)}/../test"
11
+ require 'setter_trap'
12
+
13
+ ActiveResource::Base.logger = Logger.new("#{File.dirname(__FILE__)}/debug.log")
14
+
15
+ def uses_gem(gem_name, test_name, version = '> 0')
16
+ gem gem_name.to_s, version
17
+ require gem_name.to_s
18
+ yield
19
+ rescue LoadError
20
+ $stderr.puts "Skipping #{test_name} tests. `gem install #{gem_name}` and try again."
21
+ end