Skip to content

Commit

Permalink
Merge pull request #1115 from joshk/active_resource_json_updates
Browse files Browse the repository at this point in the history
ActiveResource json updates
  • Loading branch information
josevalim committed May 18, 2011
2 parents 22ab231 + 0095869 commit 505defc
Show file tree
Hide file tree
Showing 22 changed files with 431 additions and 407 deletions.
15 changes: 0 additions & 15 deletions activeresource/examples/simple.rb

This file was deleted.

89 changes: 48 additions & 41 deletions activeresource/lib/active_resource/base.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -81,19 +81,19 @@ module ActiveResource
# <tt>post</tt>, <tt>put</tt> and <tt>\delete</tt> methods where you can specify a custom REST method # <tt>post</tt>, <tt>put</tt> and <tt>\delete</tt> methods where you can specify a custom REST method
# name to invoke. # name to invoke.
# #
# # POST to the custom 'register' REST method, i.e. POST /people/new/register.xml. # # POST to the custom 'register' REST method, i.e. POST /people/new/register.json.
# Person.new(:name => 'Ryan').post(:register) # Person.new(:name => 'Ryan').post(:register)
# # => { :id => 1, :name => 'Ryan', :position => 'Clerk' } # # => { :id => 1, :name => 'Ryan', :position => 'Clerk' }
# #
# # PUT an update by invoking the 'promote' REST method, i.e. PUT /people/1/promote.xml?position=Manager. # # PUT an update by invoking the 'promote' REST method, i.e. PUT /people/1/promote.json?position=Manager.
# Person.find(1).put(:promote, :position => 'Manager') # Person.find(1).put(:promote, :position => 'Manager')
# # => { :id => 1, :name => 'Ryan', :position => 'Manager' } # # => { :id => 1, :name => 'Ryan', :position => 'Manager' }
# #
# # GET all the positions available, i.e. GET /people/positions.xml. # # GET all the positions available, i.e. GET /people/positions.json.
# Person.get(:positions) # Person.get(:positions)
# # => [{:name => 'Manager'}, {:name => 'Clerk'}] # # => [{:name => 'Manager'}, {:name => 'Clerk'}]
# #
# # DELETE to 'fire' a person, i.e. DELETE /people/1/fire.xml. # # DELETE to 'fire' a person, i.e. DELETE /people/1/fire.json.
# Person.find(1).delete(:fire) # Person.find(1).delete(:fire)
# #
# For more information on using custom REST methods, see the # For more information on using custom REST methods, see the
Expand Down Expand Up @@ -164,7 +164,7 @@ module ActiveResource
# response code will be returned from the server which will raise an ActiveResource::ResourceNotFound # response code will be returned from the server which will raise an ActiveResource::ResourceNotFound
# exception. # exception.
# #
# # GET http://api.people.com:3000/people/999.xml # # GET http://api.people.com:3000/people/999.json
# ryan = Person.find(999) # 404, raises ActiveResource::ResourceNotFound # ryan = Person.find(999) # 404, raises ActiveResource::ResourceNotFound
# #
# #
Expand Down Expand Up @@ -218,7 +218,7 @@ module ActiveResource
# ryan.save # => false # ryan.save # => false
# #
# # When # # When
# # PUT http://api.people.com:3000/people/1.xml # # PUT http://api.people.com:3000/people/1.json
# # or # # or
# # PUT http://api.people.com:3000/people/1.json # # PUT http://api.people.com:3000/people/1.json
# # is requested with invalid values, the response is: # # is requested with invalid values, the response is:
Expand Down Expand Up @@ -489,7 +489,7 @@ def auth_type=(auth_type)
# Person.format = ActiveResource::Formats::XmlFormat # Person.format = ActiveResource::Formats::XmlFormat
# Person.find(1) # => GET /people/1.xml # Person.find(1) # => GET /people/1.xml
# #
# Default format is <tt>:xml</tt>. # Default format is <tt>:json</tt>.
def format=(mime_type_reference_or_format) def format=(mime_type_reference_or_format)
format = mime_type_reference_or_format.is_a?(Symbol) ? format = mime_type_reference_or_format.is_a?(Symbol) ?
ActiveResource::Formats[mime_type_reference_or_format] : mime_type_reference_or_format ActiveResource::Formats[mime_type_reference_or_format] : mime_type_reference_or_format
Expand All @@ -498,9 +498,9 @@ def format=(mime_type_reference_or_format)
connection.format = format if site connection.format = format if site
end end


# Returns the current format, default is ActiveResource::Formats::XmlFormat. # Returns the current format, default is ActiveResource::Formats::JsonFormat.
def format def format
self._format || ActiveResource::Formats::XmlFormat self._format || ActiveResource::Formats::JsonFormat
end end


# Sets the number of seconds after which requests to the REST API should time out. # Sets the number of seconds after which requests to the REST API should time out.
Expand Down Expand Up @@ -570,7 +570,7 @@ def headers


attr_accessor_with_default(:primary_key, 'id') #:nodoc: attr_accessor_with_default(:primary_key, 'id') #:nodoc:


# Gets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>) # 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. # This method is regenerated at runtime based on what the \prefix is set to.
def prefix(options={}) def prefix(options={})
default = site.path default = site.path
Expand All @@ -587,7 +587,7 @@ def prefix_source
prefix_source prefix_source
end end


# Sets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>). # Sets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.json</tt>).
# Default value is <tt>site.path</tt>. # Default value is <tt>site.path</tt>.
def prefix=(value = '/') def prefix=(value = '/')
# Replace :placeholders with '#{embedded options[:lookups]}' # Replace :placeholders with '#{embedded options[:lookups]}'
Expand Down Expand Up @@ -618,21 +618,21 @@ def prefix(options={}) "#{prefix_call}" end
# #
# ==== Options # ==== Options
# +prefix_options+ - A \hash to add a \prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt> # +prefix_options+ - A \hash to add a \prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
# would yield a URL like <tt>/accounts/19/purchases.xml</tt>). # would yield a URL like <tt>/accounts/19/purchases.json</tt>).
# +query_options+ - A \hash to add items to the query string for the request. # +query_options+ - A \hash to add items to the query string for the request.
# #
# ==== Examples # ==== Examples
# Post.element_path(1) # Post.element_path(1)
# # => /posts/1.xml # # => /posts/1.json
# #
# Comment.element_path(1, :post_id => 5) # Comment.element_path(1, :post_id => 5)
# # => /posts/5/comments/1.xml # # => /posts/5/comments/1.json
# #
# Comment.element_path(1, :post_id => 5, :active => 1) # Comment.element_path(1, :post_id => 5, :active => 1)
# # => /posts/5/comments/1.xml?active=1 # # => /posts/5/comments/1.json?active=1
# #
# Comment.element_path(1, {:post_id => 5}, {:active => 1}) # Comment.element_path(1, {:post_id => 5}, {:active => 1})
# # => /posts/5/comments/1.xml?active=1 # # => /posts/5/comments/1.json?active=1
# #
def element_path(id, prefix_options = {}, query_options = nil) def element_path(id, prefix_options = {}, query_options = nil)
check_prefix_options(prefix_options) check_prefix_options(prefix_options)
Expand All @@ -645,14 +645,14 @@ def element_path(id, prefix_options = {}, query_options = nil)
# #
# ==== Options # ==== Options
# * +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt> # * +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
# would yield a URL like <tt>/accounts/19/purchases/new.xml</tt>). # would yield a URL like <tt>/accounts/19/purchases/new.json</tt>).
# #
# ==== Examples # ==== Examples
# Post.new_element_path # Post.new_element_path
# # => /posts/new.xml # # => /posts/new.json
# #
# Comment.collection_path(:post_id => 5) # Comment.collection_path(:post_id => 5)
# # => /posts/5/comments/new.xml # # => /posts/5/comments/new.json
def new_element_path(prefix_options = {}) def new_element_path(prefix_options = {})
"#{prefix(prefix_options)}#{collection_name}/new.#{format.extension}" "#{prefix(prefix_options)}#{collection_name}/new.#{format.extension}"
end end
Expand All @@ -662,21 +662,21 @@ def new_element_path(prefix_options = {})
# #
# ==== Options # ==== Options
# * +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt> # * +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
# would yield a URL like <tt>/accounts/19/purchases.xml</tt>). # would yield a URL like <tt>/accounts/19/purchases.json</tt>).
# * +query_options+ - A hash to add items to the query string for the request. # * +query_options+ - A hash to add items to the query string for the request.
# #
# ==== Examples # ==== Examples
# Post.collection_path # Post.collection_path
# # => /posts.xml # # => /posts.json
# #
# Comment.collection_path(:post_id => 5) # Comment.collection_path(:post_id => 5)
# # => /posts/5/comments.xml # # => /posts/5/comments.json
# #
# Comment.collection_path(:post_id => 5, :active => 1) # Comment.collection_path(:post_id => 5, :active => 1)
# # => /posts/5/comments.xml?active=1 # # => /posts/5/comments.json?active=1
# #
# Comment.collection_path({:post_id => 5}, {:active => 1}) # Comment.collection_path({:post_id => 5}, {:active => 1})
# # => /posts/5/comments.xml?active=1 # # => /posts/5/comments.json?active=1
# #
def collection_path(prefix_options = {}, query_options = nil) def collection_path(prefix_options = {}, query_options = nil)
check_prefix_options(prefix_options) check_prefix_options(prefix_options)
Expand Down Expand Up @@ -745,34 +745,34 @@ def create(attributes = {})
# #
# ==== Examples # ==== Examples
# Person.find(1) # Person.find(1)
# # => GET /people/1.xml # # => GET /people/1.json
# #
# Person.find(:all) # Person.find(:all)
# # => GET /people.xml # # => GET /people.json
# #
# Person.find(:all, :params => { :title => "CEO" }) # Person.find(:all, :params => { :title => "CEO" })
# # => GET /people.xml?title=CEO # # => GET /people.json?title=CEO
# #
# Person.find(:first, :from => :managers) # Person.find(:first, :from => :managers)
# # => GET /people/managers.xml # # => GET /people/managers.json
# #
# Person.find(:last, :from => :managers) # Person.find(:last, :from => :managers)
# # => GET /people/managers.xml # # => GET /people/managers.json
# #
# Person.find(:all, :from => "/companies/1/people.xml") # Person.find(:all, :from => "/companies/1/people.json")
# # => GET /companies/1/people.xml # # => GET /companies/1/people.json
# #
# Person.find(:one, :from => :leader) # Person.find(:one, :from => :leader)
# # => GET /people/leader.xml # # => GET /people/leader.json
# #
# Person.find(:all, :from => :developers, :params => { :language => 'ruby' }) # Person.find(:all, :from => :developers, :params => { :language => 'ruby' })
# # => GET /people/developers.xml?language=ruby # # => GET /people/developers.json?language=ruby
# #
# Person.find(:one, :from => "/companies/1/manager.xml") # Person.find(:one, :from => "/companies/1/manager.json")
# # => GET /companies/1/manager.xml # # => GET /companies/1/manager.json
# #
# StreetAddress.find(1, :params => { :person_id => 1 }) # StreetAddress.find(1, :params => { :person_id => 1 })
# # => GET /people/1/street_addresses/1.xml # # => GET /people/1/street_addresses/1.json
# #
# == Failure or missing data # == Failure or missing data
# A failure to find the requested object raises a ResourceNotFound # A failure to find the requested object raises a ResourceNotFound
Expand Down Expand Up @@ -833,7 +833,7 @@ def all(*args)
# my_event = Event.find(:first) # let's assume this is event with ID 7 # my_event = Event.find(:first) # let's assume this is event with ID 7
# Event.delete(my_event.id) # sends DELETE /events/7 # Event.delete(my_event.id) # sends DELETE /events/7
# #
# # Let's assume a request to events/5/cancel.xml # # Let's assume a request to events/5/cancel.json
# Event.delete(params[:id]) # sends DELETE /events/5 # Event.delete(params[:id]) # sends DELETE /events/5
def delete(id, options = {}) def delete(id, options = {})
connection.delete(element_path(id, options)) connection.delete(element_path(id, options))
Expand Down Expand Up @@ -1121,7 +1121,7 @@ def dup


# Saves (+POST+) or \updates (+PUT+) a resource. Delegates to +create+ if the object is \new, # Saves (+POST+) or \updates (+PUT+) a resource. Delegates to +create+ if the object is \new,
# +update+ if it exists. If the response to the \save includes a body, it will be assumed that this body # +update+ if it exists. If the response to the \save includes a body, it will be assumed that this body
# is XML for the final object as it looked after the \save (which would include attributes like +created_at+ # is Json for the final object as it looked after the \save (which would include attributes like +created_at+
# that weren't part of the original submit). # that weren't part of the original submit).
# #
# ==== Examples # ==== Examples
Expand Down Expand Up @@ -1232,9 +1232,16 @@ def reload
# your_supplier = Supplier.new # your_supplier = Supplier.new
# your_supplier.load(my_attrs) # your_supplier.load(my_attrs)
# your_supplier.save # your_supplier.save
def load(attributes) def load(attributes, remove_root = false)
raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash) raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
@prefix_options, attributes = split_options(attributes) @prefix_options, attributes = split_options(attributes)

if attributes.keys.size == 1
remove_root = self.class.element_name == attributes.keys.first.to_s
end

attributes = Formats.remove_root(attributes) if remove_root

attributes.each do |key, value| attributes.each do |key, value|
@attributes[key.to_s] = @attributes[key.to_s] =
case value case value
Expand Down Expand Up @@ -1285,7 +1292,7 @@ def update_attribute(name, value)
# resource's attributes, the full body of the request will still be sent # resource's attributes, the full body of the request will still be sent
# in the save request to the remote service. # in the save request to the remote service.
def update_attributes(attributes) def update_attributes(attributes)
load(attributes) && save load(attributes, false) && save
end end


# For checking <tt>respond_to?</tt> without searching the attributes (which is faster). # For checking <tt>respond_to?</tt> without searching the attributes (which is faster).
Expand Down Expand Up @@ -1339,7 +1346,7 @@ def create


def load_attributes_from_response(response) 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['Content-Length'].blank? && response['Content-Length'] != "0" && !response.body.nil? && response.body.strip.size > 0
load(self.class.format.decode(response.body)) load(self.class.format.decode(response.body), true)
@persisted = true @persisted = true
end end
end end
Expand Down
2 changes: 1 addition & 1 deletion activeresource/lib/active_resource/connection.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def requests


# The +site+ parameter is required and will set the +site+ # The +site+ parameter is required and will set the +site+
# attribute to the URI for the remote resource service. # attribute to the URI for the remote resource service.
def initialize(site, format = ActiveResource::Formats::XmlFormat) def initialize(site, format = ActiveResource::Formats::JsonFormat)
raise ArgumentError, 'Missing site URI' unless site raise ArgumentError, 'Missing site URI' unless site
@user = @password = nil @user = @password = nil
self.site = site self.site = site
Expand Down
24 changes: 13 additions & 11 deletions activeresource/lib/active_resource/custom_methods.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ module ActiveResource
# #
# This route set creates routes for the following HTTP requests: # This route set creates routes for the following HTTP requests:
# #
# POST /people/new/register.xml # PeopleController.register # POST /people/new/register.json # PeopleController.register
# PUT /people/1/promote.xml # PeopleController.promote with :id => 1 # PUT /people/1/promote.json # PeopleController.promote with :id => 1
# DELETE /people/1/deactivate.xml # PeopleController.deactivate with :id => 1 # DELETE /people/1/deactivate.json # PeopleController.deactivate with :id => 1
# GET /people/active.xml # PeopleController.active # GET /people/active.json # PeopleController.active
# #
# Using this module, Active Resource can use these custom REST methods just like the # Using this module, Active Resource can use these custom REST methods just like the
# standard methods. # standard methods.
Expand All @@ -23,13 +23,13 @@ module ActiveResource
# self.site = "http://37s.sunrise.i:3000" # self.site = "http://37s.sunrise.i:3000"
# end # end
# #
# Person.new(:name => 'Ryan).post(:register) # POST /people/new/register.xml # Person.new(:name => 'Ryan).post(:register) # POST /people/new/register.json
# # => { :id => 1, :name => 'Ryan' } # # => { :id => 1, :name => 'Ryan' }
# #
# Person.find(1).put(:promote, :position => 'Manager') # PUT /people/1/promote.xml # Person.find(1).put(:promote, :position => 'Manager') # PUT /people/1/promote.json
# Person.find(1).delete(:deactivate) # DELETE /people/1/deactivate.xml # Person.find(1).delete(:deactivate) # DELETE /people/1/deactivate.json
# #
# Person.get(:active) # GET /people/active.xml # Person.get(:active) # GET /people/active.json
# # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}] # # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
# #
module CustomMethods module CustomMethods
Expand All @@ -41,10 +41,10 @@ class << self


# Invokes a GET to a given custom REST method. For example: # Invokes a GET to a given custom REST method. For example:
# #
# Person.get(:active) # GET /people/active.xml # Person.get(:active) # GET /people/active.json
# # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}] # # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
# #
# Person.get(:active, :awesome => true) # GET /people/active.xml?awesome=true # Person.get(:active, :awesome => true) # GET /people/active.json?awesome=true
# # => [{:id => 1, :name => 'Ryan'}] # # => [{:id => 1, :name => 'Ryan'}]
# #
# Note: the objects returned from this method are not automatically converted # Note: the objects returned from this method are not automatically converted
Expand All @@ -54,7 +54,9 @@ class << self
# #
# Person.find(:all, :from => :active) # Person.find(:all, :from => :active)
def get(custom_method_name, options = {}) def get(custom_method_name, options = {})
format.decode(connection.get(custom_method_collection_url(custom_method_name, options), headers).body) hashified = format.decode(connection.get(custom_method_collection_url(custom_method_name, options), headers).body)
derooted = Formats.remove_root(hashified)
derooted.is_a?(Array) ? derooted.map { |e| Formats.remove_root(e) } : derooted
end end


def post(custom_method_name, options = {}, body = '') def post(custom_method_name, options = {}, body = '')
Expand Down
8 changes: 8 additions & 0 deletions activeresource/lib/active_resource/formats.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -10,5 +10,13 @@ module Formats
def self.[](mime_type_reference) def self.[](mime_type_reference)
ActiveResource::Formats.const_get(ActiveSupport::Inflector.camelize(mime_type_reference.to_s) + "Format") ActiveResource::Formats.const_get(ActiveSupport::Inflector.camelize(mime_type_reference.to_s) + "Format")
end end

def self.remove_root(data)
if data.is_a?(Hash) && data.keys.size == 1
data.values.first
else
data
end
end
end end
end end
2 changes: 1 addition & 1 deletion activeresource/lib/active_resource/formats/json_format.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def encode(hash, options = nil)
end end


def decode(json) def decode(json)
ActiveSupport::JSON.decode(json) Formats.remove_root(ActiveSupport::JSON.decode(json))
end end
end end
end end
Expand Down
13 changes: 1 addition & 12 deletions activeresource/lib/active_resource/formats/xml_format.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -18,19 +18,8 @@ def encode(hash, options={})
end end


def decode(xml) def decode(xml)
from_xml_data(Hash.from_xml(xml)) Formats.remove_root(Hash.from_xml(xml))
end end

private
# Manipulate from_xml Hash, because xml_simple is not exactly what we
# want for Active Resource.
def from_xml_data(data)
if data.is_a?(Hash) && data.keys.size == 1
data.values.first
else
data
end
end
end end
end end
end end
Loading

0 comments on commit 505defc

Please sign in to comment.