activeresource_csi 2.3.5.p6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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