Permalink
Browse files

Added ActiveResource.format= which defaults to :xml but can also be s…

…et to :json [DHH]. Added one-off declarations of mock behavior [DHH]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@7518 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
1 parent bd311ce commit dc399b96c84bc66b7c20e92fb40e9ed00daf99c2 @dhh dhh committed Sep 20, 2007
View
@@ -1,5 +1,30 @@
*SVN*
+* Added one-off declarations of mock behavior [DHH]. Example:
+
+ Before:
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.get "/people/1.xml", {}, "<person><name>David</name></person>"
+ end
+
+ Now:
+ ActiveResource::HttpMock.respond_to.get "/people/1.xml", {}, "<person><name>David</name></person>"
+
+* Added ActiveResource.format= which defaults to :xml but can also be set to :json [DHH]. Example:
+
+ class Person < ActiveResource::Base
+ self.site = "http://app/"
+ self.format = :json
+ end
+
+ person = Person.find(1) # => GET http://app/people/1.json
+ person.name = "David"
+ person.save # => PUT http://app/people/1.json {name: "David"}
+
+ Person.format = :xml
+ person.name = "Mary"
+ person.save # => PUT http://app/people/1.json <person><name>Mary</name></person>
+
* Fix reload error when path prefix is used. #8727 [Ian Warshak]
* Remove ActiveResource::Struct because it hasn't proven very useful. Creating a new ActiveResource::Base subclass is often less code and always clearer. #8612 [Josh Peek]
@@ -34,6 +34,7 @@
end
end
+require 'active_resource/formats'
require 'active_resource/base'
require 'active_resource/validations'
require 'active_resource/custom_methods'
@@ -155,7 +155,7 @@ class << self
def site
if defined?(@site)
@site
- elsif superclass != Object and superclass.site
+ elsif superclass != Object && superclass.site
superclass.site.dup.freeze
end
end
@@ -167,12 +167,34 @@ def site=(site)
@site = site.nil? ? nil : create_site_uri_from(site)
end
+ # Sets the format that attributes are sent and received in from a mime type reference. Example:
+ #
+ # Person.format = :json
+ # Person.find(1) # => GET /people/1.json
+ #
+ # Person.format = ActiveResource::Formats::XmlFormat
+ # Person.find(1) # => GET /people/1.xml
+ #
+ # Default format is :xml.
+ def format=(mime_type_reference_or_format)
+ format = mime_type_reference_or_format.is_a?(Symbol) ?
+ ActiveResource::Formats[mime_type_reference_or_format] : mime_type_reference_or_format
+
+ write_inheritable_attribute("format", format)
+ connection.format = format
+ end
+
+ # Returns the current format, default is ActiveResource::Formats::XmlFormat
+ def format # :nodoc:
+ read_inheritable_attribute("format") || ActiveResource::Formats[:xml]
+ end
+
# An instance of ActiveResource::Connection that is the base connection to the remote service.
# The +refresh+ parameter toggles whether or not the connection is refreshed at every request
# or not (defaults to +false+).
def connection(refresh = false)
- if defined?(@connection) or superclass == Object
- @connection = Connection.new(site) if refresh || @connection.nil?
+ if defined?(@connection) || superclass == Object
+ @connection = Connection.new(site, format) if refresh || @connection.nil?
@connection
else
superclass.connection
@@ -252,7 +274,7 @@ def prefix(options={}) "#{prefix_call}" end
#
def element_path(id, prefix_options = {}, query_options = nil)
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
- "#{prefix(prefix_options)}#{collection_name}/#{id}.xml#{query_string(query_options)}"
+ "#{prefix(prefix_options)}#{collection_name}/#{id}.#{format.extension}#{query_string(query_options)}"
end
# Gets the collection path for the REST resources. If the +query_options+ parameter is omitted, Rails
@@ -278,7 +300,7 @@ def element_path(id, prefix_options = {}, query_options = nil)
#
def collection_path(prefix_options = {}, query_options = nil)
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
- "#{prefix(prefix_options)}#{collection_name}.xml#{query_string(query_options)}"
+ "#{prefix(prefix_options)}#{collection_name}.#{format.extension}#{query_string(query_options)}"
end
alias_method :set_primary_key, :primary_key= #:nodoc:
@@ -781,7 +803,7 @@ def create
def load_attributes_from_response(response)
if response['Content-size'] != "0" && response.body.strip.size > 0
- load(connection.xml_from_response(response))
+ load(self.class.format.decode(response.body))
end
end
@@ -47,23 +47,20 @@ def allowed_methods
# services.
class Connection
attr_reader :site
+ attr_accessor :format
class << self
def requests
@@requests ||= []
end
-
- def default_header
- class << self ; attr_reader :default_header end
- @default_header = { 'Content-Type' => 'application/xml' }
- end
end
# The +site+ parameter is required and will set the +site+
# attribute to the URI for the remote resource service.
- def initialize(site)
+ def initialize(site, format = ActiveResource::Formats[:xml])
raise ArgumentError, 'Missing site URI' unless site
self.site = site
+ self.format = format
end
# Set URI for remote service.
@@ -74,7 +71,7 @@ def site=(site)
# Execute a GET request.
# Used to get (find) resources.
def get(path, headers = {})
- xml_from_response(request(:get, path, build_request_headers(headers)))
+ format.decode(request(:get, path, build_request_headers(headers)).body)
end
# Execute a DELETE request (see HTTP protocol documentation if unfamiliar).
@@ -95,9 +92,6 @@ def post(path, body = '', headers = {})
request(:post, path, body.to_s, build_request_headers(headers))
end
- def xml_from_response(response)
- from_xml_data(Hash.from_xml(response.body))
- end
private
# Makes request to remote service.
@@ -144,10 +138,14 @@ def http
@http
end
+
+ def default_header
+ @default_header ||= { 'Content-Type' => format.mime_type }
+ end
# Builds headers for request to remote service.
def build_request_headers(headers)
- authorization_header.update(self.class.default_header).update(headers)
+ authorization_header.update(default_header).update(headers)
end
# Sets authorization header; authentication information is pulled from credentials provided with site URI.
@@ -158,15 +156,5 @@ def authorization_header
def logger #:nodoc:
ActiveResource::Base.logger
end
-
- # Manipulate from_xml Hash, because xml_simple is not exactly what we
- # want for ActiveResource.
- def from_xml_data(data)
- if data.is_a?(Hash) && data.keys.size == 1
- data.values.first
- else
- data
- end
- end
end
end
@@ -0,0 +1,14 @@
+module ActiveResource
+ module Formats
+ # Lookup the format class from a mime type reference symbol. Example:
+ #
+ # ActiveResource::Formats[:xml] # => ActiveResource::Formats::XmlFormat
+ # ActiveResource::Formats[:json] # => ActiveResource::Formats::JsonFormat
+ def self.[](mime_type_reference)
+ ActiveResource::Formats.const_get(mime_type_reference.to_s.camelize + "Format")
+ end
+ end
+end
+
+require 'active_resource/formats/xml_format'
+require 'active_resource/formats/json_format'
@@ -0,0 +1,23 @@
+module ActiveResource
+ module Formats
+ module JsonFormat
+ extend self
+
+ def extension
+ "json"
+ end
+
+ def mime_type
+ "application/json"
+ end
+
+ def encode(hash)
+ hash.to_json
+ end
+
+ def decode(json)
+ ActiveSupport::JSON.decode(json)
+ end
+ end
+ end
+end
@@ -0,0 +1,34 @@
+module ActiveResource
+ module Formats
+ module XmlFormat
+ extend self
+
+ def extension
+ "xml"
+ end
+
+ def mime_type
+ "application/xml"
+ end
+
+ def encode(hash)
+ hash.to_xml
+ end
+
+ def decode(xml)
+ from_xml_data(Hash.from_xml(xml))
+ end
+
+ private
+ # Manipulate from_xml Hash, because xml_simple is not exactly what we
+ # want for ActiveResource.
+ def from_xml_data(data)
+ if data.is_a?(Hash) && data.keys.size == 1
+ data.values.first
+ else
+ data
+ end
+ end
+ end
+ end
+end
@@ -32,7 +32,12 @@ def respond_to(pairs = {})
pairs.each do |(path, response)|
responses[path] = response
end
- yield Responder.new(responses) if block_given?
+
+ if block_given?
+ yield Responder.new(responses)
+ else
+ Responder.new(responses)
+ end
end
def reset!
@@ -0,0 +1,42 @@
+require "#{File.dirname(__FILE__)}/abstract_unit"
+require "fixtures/person"
+
+class FormatTest < Test::Unit::TestCase
+ def setup
+ @matz = { :id => 1, :name => 'Matz' }
+ @david = { :id => 2, :name => 'David' }
+
+ @programmers = [ @matz, @david ]
+ end
+
+ def test_formats_on_single_element
+ for format in [ :json, :xml ]
+ using_format(Person, format) do
+ ActiveResource::HttpMock.respond_to.get "/people/1.#{format}", {}, ActiveResource::Formats[format].encode(@david)
+ assert_equal @david[:name], Person.find(1).name
+ end
+ end
+ end
+
+ def test_formats_on_collection
+ for format in [ :json, :xml ]
+ using_format(Person, format) do
+ ActiveResource::HttpMock.respond_to.get "/people.#{format}", {}, ActiveResource::Formats[format].encode(@programmers)
+ remote_programmers = Person.find(:all)
+ assert_equal 2, remote_programmers.size
+ assert remote_programmers.select { |p| p.name == 'David' }
+ end
+ end
+ end
+
+
+ private
+ def using_format(klass, mime_type_reference)
+ previous_format = klass.format
+ klass.format = mime_type_reference
+
+ yield
+ ensure
+ klass.format = previous_format
+ end
+end

0 comments on commit dc399b9

Please sign in to comment.