mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Added support for calling custom methods #6979 [rwdaigle]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@6584 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
ddd243a9c1
commit
9b8399fb7f
7 changed files with 212 additions and 1 deletions
|
@ -1,5 +1,10 @@
|
||||||
*SVN*
|
*SVN*
|
||||||
|
|
||||||
|
* Added support for calling custom methods #6979 [rwdaigle]
|
||||||
|
|
||||||
|
Person.find(:managers) # => GET /people/managers.xml
|
||||||
|
Kase.find(1).post(:close) # => POST /kases/1/close.xml
|
||||||
|
|
||||||
* Remove explicit prefix_options parameter for ActiveResource::Base#initialize. [Rick]
|
* Remove explicit prefix_options parameter for ActiveResource::Base#initialize. [Rick]
|
||||||
ActiveResource splits the prefix_options from it automatically.
|
ActiveResource splits the prefix_options from it automatically.
|
||||||
|
|
||||||
|
|
|
@ -37,9 +37,11 @@ end
|
||||||
require 'active_resource/base'
|
require 'active_resource/base'
|
||||||
require 'active_resource/struct'
|
require 'active_resource/struct'
|
||||||
require 'active_resource/validations'
|
require 'active_resource/validations'
|
||||||
|
require 'active_resource/custom_methods'
|
||||||
|
|
||||||
module ActiveResource
|
module ActiveResource
|
||||||
Base.class_eval do
|
Base.class_eval do
|
||||||
include Validations
|
include Validations
|
||||||
|
include CustomMethods
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -122,6 +122,7 @@ module ActiveResource
|
||||||
case scope
|
case scope
|
||||||
when :all then find_every(options)
|
when :all then find_every(options)
|
||||||
when :first then find_every(options).first
|
when :first then find_every(options).first
|
||||||
|
when Symbol then get(scope, options)
|
||||||
else find_single(scope, options)
|
else find_single(scope, options)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
104
activeresource/lib/active_resource/custom_methods.rb
Normal file
104
activeresource/lib/active_resource/custom_methods.rb
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
# Support custom methods and sub-resources for REST.
|
||||||
|
#
|
||||||
|
# Say you on the server configure your routes with:
|
||||||
|
#
|
||||||
|
# map.resources :people, :new => { :register => :post },
|
||||||
|
# :element => { :promote => :put, :deactivate => :delete }
|
||||||
|
# :collection => { :active => :get }
|
||||||
|
#
|
||||||
|
# Which creates routes for the following http requests:
|
||||||
|
#
|
||||||
|
# POST /people/new/register.xml #=> PeopleController.register
|
||||||
|
# PUT /people/1/promote.xml #=> PeopleController.promote with :id => 1
|
||||||
|
# DELETE /people/1/deactivate.xml #=> PeopleController.deactivate with :id => 1
|
||||||
|
# GET /people/active.xml #=> PeopleController.active
|
||||||
|
#
|
||||||
|
# This module provides the ability for Active Resource to call these
|
||||||
|
# custom REST methods and get the response back.
|
||||||
|
#
|
||||||
|
# class Person < ActiveResource::Base
|
||||||
|
# self.site = "http://37s.sunrise.i:3000"
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Person.new(:name => 'Ryan).post(:register) #=> { :id => 1, :name => 'Ryan' }
|
||||||
|
#
|
||||||
|
# Person.find(1).put(:promote, :position => 'Manager')
|
||||||
|
# Person.find(1).delete(:deactivate)
|
||||||
|
#
|
||||||
|
# Person.get(:active) #=> [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
|
||||||
|
module ActiveResource
|
||||||
|
module CustomMethods
|
||||||
|
def self.included(within)
|
||||||
|
within.class_eval do
|
||||||
|
class << self
|
||||||
|
include ActiveResource::CustomMethods::ClassMethods
|
||||||
|
|
||||||
|
alias :orig_delete :delete
|
||||||
|
|
||||||
|
def get(method_name, options = {})
|
||||||
|
connection.get(custom_method_collection_url(method_name, options))
|
||||||
|
end
|
||||||
|
|
||||||
|
def post(method_name, options = {}, body = nil)
|
||||||
|
connection.post(custom_method_collection_url(method_name, options), body)
|
||||||
|
end
|
||||||
|
|
||||||
|
def put(method_name, options = {}, body = nil)
|
||||||
|
connection.put(custom_method_collection_url(method_name, options), body)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Need to jump through some hoops to retain the original class 'delete' method
|
||||||
|
def delete(custom_method_name, options = {})
|
||||||
|
if (custom_method_name.is_a?(Symbol))
|
||||||
|
connection.delete(custom_method_collection_url(custom_method_name, options))
|
||||||
|
else
|
||||||
|
orig_delete(custom_method_name, options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
within.send(:include, ActiveResource::CustomMethods::InstanceMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def custom_method_collection_url(method_name, options = {})
|
||||||
|
prefix_options, query_options = split_options(options)
|
||||||
|
"#{prefix(prefix_options)}#{collection_name}/#{method_name}.xml#{query_string(query_options)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module InstanceMethods
|
||||||
|
def get(method_name, options = {})
|
||||||
|
connection.get(custom_method_element_url(method_name, options))
|
||||||
|
end
|
||||||
|
|
||||||
|
def post(method_name, options = {}, body = nil)
|
||||||
|
if new?
|
||||||
|
connection.post(custom_method_new_element_url(method_name, options), (body.nil? ? to_xml : body))
|
||||||
|
else
|
||||||
|
connection.post(custom_method_element_url(method_name, options), body)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def put(method_name, options = {}, body = nil)
|
||||||
|
connection.put(custom_method_element_url(method_name, options), body)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(method_name, options = {})
|
||||||
|
connection.delete(custom_method_element_url(method_name, options))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
private
|
||||||
|
def custom_method_element_url(method_name, options = {})
|
||||||
|
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/#{method_name}.xml#{self.class.send(:query_string, options)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def custom_method_new_element_url(method_name, options = {})
|
||||||
|
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}.xml#{self.class.send(:query_string, options)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -110,6 +110,14 @@ module ActiveResource
|
||||||
def []=(key, value)
|
def []=(key, value)
|
||||||
headers[key] = value
|
headers[key] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ==(other)
|
||||||
|
if (other.is_a?(Response))
|
||||||
|
other.body == body && other.message == message && other.headers == headers
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Connection
|
class Connection
|
||||||
|
|
87
activeresource/test/base/custom_methods_test.rb
Normal file
87
activeresource/test/base/custom_methods_test.rb
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
require "#{File.dirname(__FILE__)}/../abstract_unit"
|
||||||
|
require "#{File.dirname(__FILE__)}/../fixtures/person"
|
||||||
|
require "#{File.dirname(__FILE__)}/../fixtures/street_address"
|
||||||
|
|
||||||
|
class CustomMethodsTest < Test::Unit::TestCase
|
||||||
|
def setup
|
||||||
|
@matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person')
|
||||||
|
@matz_deep = { :id => 1, :name => 'Matz', :other => 'other' }.to_xml(:root => 'person')
|
||||||
|
@ryan = { :name => 'Ryan' }.to_xml(:root => 'person')
|
||||||
|
@addy = { :id => 1, :street => '12345 Street' }.to_xml(:root => 'address')
|
||||||
|
@addy_deep = { :id => 1, :street => '12345 Street', :zip => "27519" }.to_xml(:root => 'address')
|
||||||
|
@default_request_headers = { 'Content-Type' => 'application/xml' }
|
||||||
|
|
||||||
|
ActiveResource::HttpMock.respond_to do |mock|
|
||||||
|
mock.get "/people/1.xml", {}, @matz
|
||||||
|
mock.get "/people/1/shallow.xml", {}, @matz
|
||||||
|
mock.get "/people/1/deep.xml", {}, @matz_deep
|
||||||
|
mock.get "/people/retrieve.xml?name=Matz", {}, "<people>#{@matz}</people>"
|
||||||
|
mock.get "/people/managers.xml", {}, "<people>#{@matz}</people>"
|
||||||
|
mock.put "/people/1/promote.xml?position=Manager", {}, nil, 204
|
||||||
|
mock.put "/people/promote.xml?name=Matz", {}, nil, 204, {}
|
||||||
|
mock.put "/people/sort.xml?by=name", {}, nil, 204
|
||||||
|
mock.delete "/people/deactivate.xml?name=Matz", {}, nil, 200
|
||||||
|
mock.delete "/people/1/deactivate.xml", {}, nil, 200
|
||||||
|
mock.post "/people/new/register.xml", {}, @ryan, 201, 'Location' => '/people/5.xml'
|
||||||
|
mock.get "/people/1/addresses/1.xml", {}, @addy
|
||||||
|
mock.get "/people/1/addresses/1/deep.xml", {}, @addy_deep
|
||||||
|
mock.put "/people/1/addresses/1/normalize_phone.xml?locale=US", {}, nil, 204
|
||||||
|
mock.put "/people/1/addresses/sort.xml?by=name", {}, nil, 204
|
||||||
|
mock.post "/people/1/addresses/new/link.xml", {}, { :street => '12345 Street' }.to_xml(:root => 'address'), 201, 'Location' => '/people/1/addresses/2.xml'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
ActiveResource::HttpMock.reset!
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_custom_collection_method
|
||||||
|
# GET
|
||||||
|
assert_equal([{ "id" => 1, "name" => 'Matz' }], Person.get(:retrieve, :name => 'Matz'))
|
||||||
|
|
||||||
|
# PUT
|
||||||
|
assert_equal ActiveResource::Response.new("", 204, {}),
|
||||||
|
Person.put(:promote, {:name => 'Matz'}, 'atestbody')
|
||||||
|
assert_equal ActiveResource::Response.new("", 204, {}), Person.put(:sort, :by => 'name')
|
||||||
|
|
||||||
|
# DELETE
|
||||||
|
Person.delete :deactivate, :name => 'Matz'
|
||||||
|
|
||||||
|
# Nested resource
|
||||||
|
assert_equal ActiveResource::Response.new("", 204, {}), StreetAddress.put(:sort, :person_id => 1, :by => 'name')
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_custom_element_method
|
||||||
|
# Test GET against an element URL
|
||||||
|
assert_equal Person.find(1).get(:shallow), {"id" => 1, "name" => 'Matz'}
|
||||||
|
assert_equal Person.find(1).get(:deep), {"id" => 1, "name" => 'Matz', "other" => 'other'}
|
||||||
|
|
||||||
|
# Test PUT against an element URL
|
||||||
|
assert_equal ActiveResource::Response.new("", 204, {}), Person.find(1).put(:promote, {:position => 'Manager'}, 'body')
|
||||||
|
|
||||||
|
# Test DELETE against an element URL
|
||||||
|
assert_equal ActiveResource::Response.new("", 200, {}), Person.find(1).delete(:deactivate)
|
||||||
|
|
||||||
|
# With nested resources
|
||||||
|
assert_equal StreetAddress.find(1, :person_id => 1).get(:deep),
|
||||||
|
{ "id" => 1, "street" => '12345 Street', "zip" => "27519" }
|
||||||
|
assert_equal ActiveResource::Response.new("", 204, {}),
|
||||||
|
StreetAddress.find(1, :person_id => 1).put(:normalize_phone, :locale => 'US')
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_custom_new_element_method
|
||||||
|
# Test POST against a new element URL
|
||||||
|
ryan = Person.new(:name => 'Ryan')
|
||||||
|
assert_equal ActiveResource::Response.new(@ryan, 201, {'Location' => '/people/5.xml'}), ryan.post(:register)
|
||||||
|
|
||||||
|
# Test POST against a nested collection URL
|
||||||
|
addy = StreetAddress.new(:street => '123 Test Dr.', :person_id => 1)
|
||||||
|
assert_equal ActiveResource::Response.new({ :street => '12345 Street' }.to_xml(:root => 'address'),
|
||||||
|
201, {'Location' => '/people/1/addresses/2.xml'}),
|
||||||
|
addy.post(:link)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_find_custom_resources
|
||||||
|
assert_equal [{ "id" => 1, "name" => 'Matz' }], Person.find(:managers)
|
||||||
|
end
|
||||||
|
end
|
|
@ -95,7 +95,11 @@ class BaseTest < Test::Unit::TestCase
|
||||||
assert_equal '/people.xml?gender=', Person.collection_path(:gender => nil)
|
assert_equal '/people.xml?gender=', Person.collection_path(:gender => nil)
|
||||||
|
|
||||||
assert_equal '/people.xml?gender=male', Person.collection_path('gender' => 'male')
|
assert_equal '/people.xml?gender=male', Person.collection_path('gender' => 'male')
|
||||||
assert_equal '/people.xml?gender=male&student=true', Person.collection_path(:gender => 'male', :student => true)
|
|
||||||
|
# Use includes? because ordering of param hash is not guaranteed
|
||||||
|
assert Person.collection_path(:gender => 'male', :student => true).include?('/people.xml?')
|
||||||
|
assert Person.collection_path(:gender => 'male', :student => true).include?('gender=male')
|
||||||
|
assert Person.collection_path(:gender => 'male', :student => true).include?('student=true')
|
||||||
|
|
||||||
assert_equal '/people.xml?name%5B%5D=bob&name%5B%5D=your+uncle%2Bme&name%5B%5D=&name%5B%5D=false', Person.collection_path(:name => ['bob', 'your uncle+me', nil, false])
|
assert_equal '/people.xml?name%5B%5D=bob&name%5B%5D=your+uncle%2Bme&name%5B%5D=&name%5B%5D=false', Person.collection_path(:name => ['bob', 'your uncle+me', nil, false])
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue