diff options
Diffstat (limited to 'activeresource')
-rw-r--r-- | activeresource/CHANGELOG | 15 | ||||
-rw-r--r-- | activeresource/README.rdoc | 30 | ||||
-rwxr-xr-x | activeresource/Rakefile | 4 | ||||
-rw-r--r-- | activeresource/activeresource.gemspec | 3 | ||||
-rw-r--r-- | activeresource/lib/active_resource/base.rb | 52 | ||||
-rw-r--r-- | activeresource/lib/active_resource/connection.rb | 7 | ||||
-rw-r--r-- | activeresource/lib/active_resource/http_mock.rb | 4 | ||||
-rw-r--r-- | activeresource/lib/active_resource/schema.rb | 10 | ||||
-rw-r--r-- | activeresource/lib/active_resource/version.rb | 4 | ||||
-rw-r--r-- | activeresource/test/abstract_unit.rb | 6 | ||||
-rw-r--r-- | activeresource/test/cases/authorization_test.rb | 6 | ||||
-rw-r--r-- | activeresource/test/cases/base/load_test.rb | 20 | ||||
-rw-r--r-- | activeresource/test/cases/base/schema_test.rb | 10 | ||||
-rw-r--r-- | activeresource/test/cases/base_test.rb | 44 | ||||
-rw-r--r-- | activeresource/test/cases/connection_test.rb | 19 | ||||
-rw-r--r-- | activeresource/test/cases/finder_test.rb | 2 | ||||
-rw-r--r-- | activeresource/test/cases/log_subscriber_test.rb | 2 | ||||
-rw-r--r-- | activeresource/test/cases/observing_test.rb | 2 |
18 files changed, 192 insertions, 48 deletions
diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG index 25f9242b98..bd496a1263 100644 --- a/activeresource/CHANGELOG +++ b/activeresource/CHANGELOG @@ -1,4 +1,17 @@ -*Rails 3.1.0 (unreleased)* +*Rails 3.2.0 (unreleased)* + +* Redirect responses: 303 See Other and 307 Temporary Redirect now behave like + 301 Moved Permanently and 302 Found. GH #3302. + + [Jim Herz] + + +*Rails 3.1.1 (October 7, 2011)* + +* No changes. + + +*Rails 3.1.0 (August 30, 2011)* * The default format has been changed to JSON for all requests. If you want to continue to use XML you will need to set `self.format = :xml` in the class. eg. diff --git a/activeresource/README.rdoc b/activeresource/README.rdoc index afa25e1676..c86289c5fe 100644 --- a/activeresource/README.rdoc +++ b/activeresource/README.rdoc @@ -20,13 +20,23 @@ Model classes are mapped to remote REST resources by Active Resource much the sa tables. When a request is made to a remote resource, a REST XML request is generated, transmitted, and the result received and serialized into a usable Ruby object. +== Download and installation + +The latest version of Active Support can be installed with RubyGems: + + % [sudo] gem install activeresource + +Source code can be downloaded as part of the Rails project on GitHub + +* https://github.com/rails/rails/tree/master/activeresource + === Configuration and Usage Putting Active Resource to use is very similar to Active Record. It's as simple as creating a model class that inherits from ActiveResource::Base and providing a <tt>site</tt> class variable to it: class Person < ActiveResource::Base - self.site = "http://api.people.com:3000/" + self.site = "http://api.people.com:3000" end Now the Person class is REST enabled and can invoke REST services very similarly to how Active Record invokes @@ -125,8 +135,8 @@ as the id of the ARes object. ==== Update -'save' is also used to update an existing resource - and follows the same protocol as creating a resource -with the exception that no response headers are needed - just an empty response when the update on the +'save' is also used to update an existing resource and follows the same protocol as creating a resource +with the exception that no response headers are needed -- just an empty response when the update on the server side was successful. # <person><first>Ryan</first></person> @@ -160,6 +170,18 @@ Destruction of a resource can be invoked as a class and instance method of the r Person.delete(2) # => true Person.exists?(2) # => false +== License -You can find more usage information in the ActiveResource::Base documentation. +Active Support is released under the MIT license. +== Support + +API documentation is at + +* http://api.rubyonrails.org + +Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here: + +* https://github.com/rails/rails/issues + +You can find more usage information in the ActiveResource::Base documentation. diff --git a/activeresource/Rakefile b/activeresource/Rakefile index 42e450da66..b1c18ff189 100755 --- a/activeresource/Rakefile +++ b/activeresource/Rakefile @@ -1,7 +1,7 @@ #!/usr/bin/env rake require 'rake/testtask' require 'rake/packagetask' -require 'rake/gempackagetask' +require 'rubygems/package_task' desc "Default Task" task :default => [ :test ] @@ -26,7 +26,7 @@ end spec = eval(File.read('activeresource.gemspec')) -Rake::GemPackageTask.new(spec) do |p| +Gem::PackageTask.new(spec) do |p| p.gem_spec = spec end diff --git a/activeresource/activeresource.gemspec b/activeresource/activeresource.gemspec index c2fd707e9b..a8772ecf8c 100644 --- a/activeresource/activeresource.gemspec +++ b/activeresource/activeresource.gemspec @@ -12,9 +12,8 @@ Gem::Specification.new do |s| s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' s.homepage = 'http://www.rubyonrails.org' - s.rubyforge_project = 'activeresource' - s.files = Dir['CHANGELOG', 'README.rdoc', 'examples/**/*', 'lib/**/*'] + s.files = Dir['CHANGELOG', 'MIT-LICENSE', 'README.rdoc', 'examples/**/*', 'lib/**/*'] s.require_path = 'lib' s.extra_rdoc_files = %w( README.rdoc ) diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 65d285249b..10cc727bd9 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -3,7 +3,6 @@ require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/kernel/reporting' -require 'active_support/core_ext/module/attr_accessor_with_default' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/object/blank' @@ -171,8 +170,8 @@ module ActiveResource # <tt>404</tt> is just one of the HTTP error response codes that Active Resource will handle with its own exception. The # following HTTP response codes will also result in these exceptions: # - # * 200..399 - Valid response, no exception (other than 301, 302) - # * 301, 302 - ActiveResource::Redirection + # * 200..399 - Valid response. No exceptions, other than these redirects: + # * 301, 302, 303, 307 - ActiveResource::Redirection # * 400 - ActiveResource::BadRequest # * 401 - ActiveResource::UnauthorizedAccess # * 403 - ActiveResource::ForbiddenAccess @@ -284,7 +283,7 @@ module ActiveResource # attribute 'name', :string # # # or use the convenience methods and pass >=1 attribute names - # string 'eye_colour', 'hair_colour' + # string 'eye_color', 'hair_color' # integer 'age' # float 'height', 'weight' # @@ -396,7 +395,7 @@ module ActiveResource # Subclass.site.user = 'david' # Parent.site # => 'http://david@test.com' # - # Without superclass_delegating_reader (expected behaviour) + # Without superclass_delegating_reader (expected behavior) # # Parent.site = 'http://anonymous@test.com' # Subclass.site # => 'http://anonymous@test.com' @@ -565,10 +564,23 @@ module ActiveResource @headers ||= {} end - attr_accessor_with_default(:element_name) { model_name.element } #:nodoc: - attr_accessor_with_default(:collection_name) { ActiveSupport::Inflector.pluralize(element_name) } #:nodoc: + attr_writer :element_name - attr_accessor_with_default(:primary_key, 'id') #:nodoc: + def element_name + @element_name ||= model_name.element + end + + attr_writer :collection_name + + def collection_name + @collection_name ||= ActiveSupport::Inflector.pluralize(element_name) + end + + attr_writer :primary_key + + def primary_key + @primary_key ||= 'id' + end # Gets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.json</tt>) # This method is regenerated at runtime based on what the \prefix is set to. @@ -625,6 +637,10 @@ module ActiveResource # Post.element_path(1) # # => /posts/1.json # + # class Comment < ActiveResource::Base + # self.site = "http://37s.sunrise.i/posts/:post_id/" + # end + # # Comment.element_path(1, :post_id => 5) # # => /posts/5/comments/1.json # @@ -651,6 +667,10 @@ module ActiveResource # Post.new_element_path # # => /posts/new.json # + # class Comment < ActiveResource::Base + # self.site = "http://37s.sunrise.i/posts/:post_id/" + # end + # # Comment.collection_path(:post_id => 5) # # => /posts/5/comments/new.json def new_element_path(prefix_options = {}) @@ -943,7 +963,7 @@ module ActiveResource prefix_options, query_options = {}, {} (options || {}).each do |key, value| - next if key.blank? + next if key.blank? || !key.respond_to?(:to_sym) (prefix_parameters.include?(key.to_sym) ? prefix_options : query_options)[key.to_sym] = value end @@ -1345,7 +1365,9 @@ module ActiveResource end def load_attributes_from_response(response) - if !response['Content-Length'].blank? && response['Content-Length'] != "0" && !response.body.nil? && response.body.strip.size > 0 + if (response_code_allows_body?(response.code) && + (response['Content-Length'].nil? || response['Content-Length'] != "0") && + !response.body.nil? && response.body.strip.size > 0) load(self.class.format.decode(response.body), true) @persisted = true end @@ -1369,6 +1391,16 @@ module ActiveResource end private + + def read_attribute_for_serialization(n) + attributes[n] + end + + # Determine whether the response is allowed to have a body per HTTP 1.1 spec section 4.4.1 + def response_code_allows_body?(c) + !((100..199).include?(c) || [204,304].include?(c)) + end + # Tries to find a resource for a given collection name; if it fails, then the resource is created def find_or_create_resource_for_collection(name) find_or_create_resource_for(ActiveSupport::Inflector.singularize(name.to_s)) diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index d923204dde..94839c8c25 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -122,7 +122,7 @@ module ActiveResource # Handles response and error codes from the remote service. def handle_response(response) case response.code.to_i - when 301,302 + when 301, 302, 303, 307 raise(Redirection.new(response)) when 200...400 response @@ -238,8 +238,11 @@ module ActiveResource def digest_auth_header(http_method, uri) params = extract_params_from_response + request_uri = uri.path + request_uri << "?#{uri.query}" if uri.query + ha1 = Digest::MD5.hexdigest("#{@user}:#{params['realm']}:#{@password}") - ha2 = Digest::MD5.hexdigest("#{http_method.to_s.upcase}:#{uri.path}") + ha2 = Digest::MD5.hexdigest("#{http_method.to_s.upcase}:#{request_uri}") params.merge!('cnonce' => client_nonce) request_digest = Digest::MD5.hexdigest([ha1, params['nonce'], "0", params['cnonce'], params['qop'], ha2].join(":")) diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb index e90580be4f..36f52d61d3 100644 --- a/activeresource/lib/active_resource/http_mock.rb +++ b/activeresource/lib/active_resource/http_mock.rb @@ -55,7 +55,7 @@ module ActiveResource @responses = responses end - for method in [ :post, :put, :get, :delete, :head ] + [ :post, :put, :get, :delete, :head ].each do |method| # def post(path, request_headers = {}, body = nil, status = 200, response_headers = {}) # @responses[Request.new(:post, path, nil, request_headers)] = Response.new(body || "", status, response_headers) # end @@ -149,7 +149,7 @@ module ActiveResource # Note, by default, every time you call +respond_to+, any previous request and response pairs stored # in HttpMock will be deleted giving you a clean slate to work on. # - # If you want to override this behaviour, pass in +false+ as the last argument to +respond_to+ + # If you want to override this behavior, pass in +false+ as the last argument to +respond_to+ # # === Example # diff --git a/activeresource/lib/active_resource/schema.rb b/activeresource/lib/active_resource/schema.rb index 3fd37a9bb6..5957969aa2 100644 --- a/activeresource/lib/active_resource/schema.rb +++ b/activeresource/lib/active_resource/schema.rb @@ -20,8 +20,8 @@ module ActiveResource # :nodoc: # end # # The schema stores the name and type of each attribute. That is then - # read out by the schema method to populate the actual - # Resource's schema + # read out by the schema method to populate the schema of the actual + # resource. def initialize @attrs = {} end @@ -40,6 +40,12 @@ module ActiveResource # :nodoc: # The following are the attribute types supported by Active Resource # migrations. KNOWN_ATTRIBUTE_TYPES.each do |attr_type| + # def string(*args) + # options = args.extract_options! + # attr_names = args + # + # attr_names.each { |name| attribute(name, 'string', options) } + # end class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{attr_type.to_s}(*args) options = args.extract_options! diff --git a/activeresource/lib/active_resource/version.rb b/activeresource/lib/active_resource/version.rb index f26e2312b9..d53374b261 100644 --- a/activeresource/lib/active_resource/version.rb +++ b/activeresource/lib/active_resource/version.rb @@ -1,9 +1,9 @@ module ActiveResource module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "beta1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activeresource/test/abstract_unit.rb b/activeresource/test/abstract_unit.rb index 948dd94a1d..9c1e9a526d 100644 --- a/activeresource/test/abstract_unit.rb +++ b/activeresource/test/abstract_unit.rb @@ -3,7 +3,6 @@ require File.expand_path('../../../load_paths', __FILE__) lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") $:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) -require 'rubygems' require 'test/unit' require 'active_resource' require 'active_support' @@ -14,11 +13,6 @@ require 'setter_trap' require 'logger' ActiveResource::Base.logger = Logger.new("#{File.dirname(__FILE__)}/debug.log") -begin - require 'ruby-debug' -rescue LoadError -end - def setup_response matz_hash = { 'person' => { :id => 1, :name => 'Matz' } } diff --git a/activeresource/test/cases/authorization_test.rb b/activeresource/test/cases/authorization_test.rb index 69ef9a2821..17cd9b30fc 100644 --- a/activeresource/test/cases/authorization_test.rb +++ b/activeresource/test/cases/authorization_test.rb @@ -131,6 +131,12 @@ class AuthorizationTest < Test::Unit::TestCase assert_equal blank_digest_auth_header("/people/2.json", "fad396f6a34aeba28e28b9b96ddbb671"), authorization_header['Authorization'] end + def test_authorization_header_with_query_string_if_auth_type_is_digest + @authenticated_conn.auth_type = :digest + authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json?only=name')) + assert_equal blank_digest_auth_header("/people/2.json?only=name", "f8457b0b5d21b6b80737a386217afb24"), authorization_header['Authorization'] + end + def test_get david = decode(@authenticated_conn.get("/people/2.json")) assert_equal "David", david["name"] diff --git a/activeresource/test/cases/base/load_test.rb b/activeresource/test/cases/base/load_test.rb index d6b04cfaa8..784e7dd036 100644 --- a/activeresource/test/cases/base/load_test.rb +++ b/activeresource/test/cases/base/load_test.rb @@ -51,9 +51,28 @@ class BaseLoadTest < Test::Unit::TestCase :votes => [ true, false, true ], :places => [ "Columbia City", "Unknown" ]}}} + + # List of books formated as [{timestamp_of_publication => name}, ...] + @books = {:books => [ + {1009839600 => "Ruby in a Nutshell"}, + {1199142000 => "The Ruby Programming Language"} + ]} + + @books_date = {:books => [ + {Time.at(1009839600) => "Ruby in a Nutshell"}, + {Time.at(1199142000) => "The Ruby Programming Language"} + ]} @person = Person.new end + def test_load_hash_with_integers_as_keys + assert_nothing_raised{@person.load(@books)} + end + + def test_load_hash_with_dates_as_keys + assert_nothing_raised{@person.load(@books_date)} + end + def test_load_expects_hash assert_raise(ArgumentError) { @person.load nil } assert_raise(ArgumentError) { @person.load '<person id="1"/>' } @@ -72,7 +91,6 @@ class BaseLoadTest < Test::Unit::TestCase def test_after_load_attributes_are_accessible_via_indifferent_access assert_equal Hash.new, @person.attributes - matz_attributes = @matz.values.first assert_equal @matz.stringify_keys, @person.load(@matz).attributes assert_equal @matz[:name], @person.attributes['name'] assert_equal @matz[:name], @person.attributes[:name] diff --git a/activeresource/test/cases/base/schema_test.rb b/activeresource/test/cases/base/schema_test.rb index 48fdeb13df..d29eaf5fb6 100644 --- a/activeresource/test/cases/base/schema_test.rb +++ b/activeresource/test/cases/base/schema_test.rb @@ -139,7 +139,7 @@ class SchemaTest < ActiveModel::TestCase assert_nothing_raised { Person.schema = new_schema assert_equal new_schema, Person.schema, "should have saved the schema on the class" - assert_equal new_schema, Person.new.schema, "should have mde the schema available to every instance" + assert_equal new_schema, Person.new.schema, "should have made the schema available to every instance" } end @@ -283,8 +283,8 @@ class SchemaTest < ActiveModel::TestCase new_attr_name_two = :another_new_schema_attribute assert Person.schema.blank?, "sanity check - should have a blank class schema" - assert !Person.new.respond_do?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet" - assert !Person.new.respond_do?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet" + assert !Person.new.respond_to?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet" + assert !Person.new.respond_to?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet" assert_nothing_raised do Person.schema = {new_attr_name.to_s => 'string'} @@ -301,8 +301,8 @@ class SchemaTest < ActiveModel::TestCase assert Person.schema.blank?, "sanity check - should have a blank class schema" - assert !Person.new.respond_do?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet" - assert !Person.new.respond_do?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet" + assert !Person.new.respond_to?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet" + assert !Person.new.respond_to?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet" assert_nothing_raised do Person.schema { string new_attr_name_two } diff --git a/activeresource/test/cases/base_test.rb b/activeresource/test/cases/base_test.rb index f45652d988..7b42f64a35 100644 --- a/activeresource/test/cases/base_test.rb +++ b/activeresource/test/cases/base_test.rb @@ -636,13 +636,37 @@ class BaseTest < Test::Unit::TestCase assert_nil p.__send__(:id_from_response, resp) end - def test_load_attributes_from_response - p = Person.new + def test_not_persisted_with_no_body_and_positive_content_length resp = ActiveResource::Response.new(nil) resp['Content-Length'] = "100" - assert_nil p.__send__(:load_attributes_from_response, resp) + Person.connection.expects(:post).returns(resp) + assert !Person.create.persisted? + end + + def test_not_persisted_with_body_and_zero_content_length + resp = ActiveResource::Response.new(@rick) + resp['Content-Length'] = "0" + Person.connection.expects(:post).returns(resp) + assert !Person.create.persisted? end + # These response codes aren't allowed to have bodies per HTTP spec + def test_not_persisted_with_empty_response_codes + [100,101,204,304].each do |status_code| + resp = ActiveResource::Response.new(@rick, status_code) + Person.connection.expects(:post).returns(resp) + assert !Person.create.persisted? + end + end + + # Content-Length is not required by HTTP 1.1, so we should read + # the body anyway in its absence. + def test_persisted_with_no_content_length + resp = ActiveResource::Response.new(@rick) + resp['Content-Length'] = nil + Person.connection.expects(:post).returns(resp) + assert Person.create.persisted? + end def test_create_with_custom_prefix matzs_house = StreetAddress.new(:person_id => 1) @@ -980,9 +1004,17 @@ class BaseTest < Test::Unit::TestCase def test_to_xml_with_private_method_name_as_attribute Person.format = :xml - assert_nothing_raised(ArgumentError) { - Customer.new(:test => true).to_xml - } + + customer = Customer.new(:foo => "foo") + customer.singleton_class.class_eval do + def foo + "bar" + end + private :foo + end + + assert !customer.to_xml.include?("<foo>bar</foo>") + assert customer.to_xml.include?("<foo>foo</foo>") ensure Person.format = :json end diff --git a/activeresource/test/cases/connection_test.rb b/activeresource/test/cases/connection_test.rb index 09df0fb678..535107aeef 100644 --- a/activeresource/test/cases/connection_test.rb +++ b/activeresource/test/cases/connection_test.rb @@ -2,6 +2,7 @@ require 'abstract_unit' class ConnectionTest < Test::Unit::TestCase ResponseCodeStub = Struct.new(:code) + RedirectResponseStub = Struct.new(:code, :Location) def setup @conn = ActiveResource::Connection.new('http://localhost') @@ -38,6 +39,18 @@ class ConnectionTest < Test::Unit::TestCase assert_equal expected, handle_response(expected) end + # 301 is moved permanently (redirect) + assert_redirect_raises 301 + + # 302 is found (redirect) + assert_redirect_raises 302 + + # 303 is see other (redirect) + assert_redirect_raises 303 + + # 307 is temporary redirect + assert_redirect_raises 307 + # 400 is a bad request (e.g. malformed URI or missing request parameter) assert_response_raises ActiveResource::BadRequest, 400 @@ -247,6 +260,12 @@ class ConnectionTest < Test::Unit::TestCase end end + def assert_redirect_raises(code) + assert_raise(ActiveResource::Redirection, "Expected response code #{code} to raise ActiveResource::Redirection") do + handle_response RedirectResponseStub.new(code, 'http://example.com/') + end + end + def handle_response(response) @conn.__send__(:handle_response, response) end diff --git a/activeresource/test/cases/finder_test.rb b/activeresource/test/cases/finder_test.rb index 9c51f2a390..5fbbfeef6e 100644 --- a/activeresource/test/cases/finder_test.rb +++ b/activeresource/test/cases/finder_test.rb @@ -95,7 +95,7 @@ class FinderTest < Test::Unit::TestCase def test_find_all_sub_objects_not_found assert_nothing_raised do - addys = StreetAddress.find(:all, :params => { :person_id => 2 }) + StreetAddress.find(:all, :params => { :person_id => 2 }) end end diff --git a/activeresource/test/cases/log_subscriber_test.rb b/activeresource/test/cases/log_subscriber_test.rb index b9143f5b67..ab5c22a783 100644 --- a/activeresource/test/cases/log_subscriber_test.rb +++ b/activeresource/test/cases/log_subscriber_test.rb @@ -23,7 +23,7 @@ class LogSubscriberTest < ActiveSupport::TestCase end def test_request_notification - matz = Person.find(1) + Person.find(1) wait assert_equal 2, @logger.logged(:info).size assert_equal "GET http://37s.sunrise.i:3000/people/1.json", @logger.logged(:info)[0] diff --git a/activeresource/test/cases/observing_test.rb b/activeresource/test/cases/observing_test.rb index ca3ab5d03d..58d3d389ff 100644 --- a/activeresource/test/cases/observing_test.rb +++ b/activeresource/test/cases/observing_test.rb @@ -37,7 +37,7 @@ class ObservingTest < Test::Unit::TestCase end def test_create_fires_save_and_create_notifications - rick = Person.create(:name => 'Rick') + Person.create(:name => 'Rick') assert_equal [:before_save, :before_create, :after_create, :after_save], self.history end |