Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from kenmazaika/master
Support Singleton Routes
- Loading branch information
Showing
5 changed files
with
283 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,114 @@ | |||
module ActiveResource | |||
module Singleton | |||
extend ActiveSupport::Concern | |||
|
|||
module ClassMethods | |||
attr_writer :singleton_name | |||
|
|||
def singleton_name | |||
@singleton_name ||= model_name.element | |||
end | |||
|
|||
# Gets the singleton path for the object. If the +query_options+ parameter is omitted, Rails | |||
# will split from the \prefix options. | |||
# | |||
# ==== Options | |||
# +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.json</tt>). | |||
# +query_options+ - A \hash to add items to the query string for the request. | |||
# | |||
# ==== Examples | |||
# Weather.singleton_path | |||
# # => /weather.json | |||
# | |||
# class Inventory < ActiveResource::Base | |||
# self.site = "https://37s.sunrise.com" | |||
# self.prefix = "/products/:product_id/" | |||
# end | |||
# | |||
# Inventory.singleton_path(:product_id => 5) | |||
# # => /products/5/inventory.json | |||
# | |||
# Inventory.singleton_path({:product_id => 5}, {:sold => true}) | |||
# # => /products/5/inventory.json?sold=true | |||
# | |||
def singleton_path(prefix_options = {}, query_options = nil) | |||
check_prefix_options(prefix_options) | |||
|
|||
prefix_options, query_options = split_options(prefix_options) if query_options.nil? | |||
"#{prefix(prefix_options)}#{singleton_name}.#{format.extension}#{query_string(query_options)}" | |||
end | |||
|
|||
# Core method for finding singleton resources. | |||
# | |||
# ==== Arguments | |||
# Takes a single argument of options | |||
# | |||
# ==== Options | |||
# * <tt>:params</tt> - Sets the query and \prefix (nested URL) parameters. | |||
# | |||
# ==== Examples | |||
# Weather.find | |||
# # => GET /weather.json | |||
# | |||
# Weather.find(:params => {:degrees => 'fahrenheit'}) | |||
# # => GET /weather.json?degrees=fahrenheit | |||
# | |||
# == Failure or missing data | |||
# A failure to find the requested object raises a ResourceNotFound | |||
# exception. | |||
# | |||
# Inventory.find | |||
# # => raises ResourceNotFound | |||
def find(options={}) | |||
find_singleton(options) | |||
end | |||
|
|||
private | |||
# Find singleton resource | |||
def find_singleton(options) | |||
prefix_options, query_options = split_options(options[:params]) | |||
|
|||
path = singleton_path(prefix_options, query_options) | |||
resp = self.format.decode(self.connection.get(path, self.headers).body) | |||
instantiate_record(resp, {}) | |||
end | |||
|
|||
end | |||
# Deletes the resource from the remove service. | |||
# | |||
# ==== Examples | |||
# weather = Weather.find | |||
# weather.destroy | |||
# Weather.find # 404 (Resource Not Found) | |||
def destroy | |||
connection.delete(singleton_path, self.class.headers) | |||
end | |||
|
|||
|
|||
protected | |||
|
|||
# Update the resource on the remote service | |||
def update | |||
connection.put(singleton_path(prefix_options), encode, self.class.headers).tap do |response| | |||
load_attributes_from_response(response) | |||
end | |||
end | |||
|
|||
# Create (i.e. \save to the remote service) the \new resource. | |||
def create | |||
connection.post(singleton_path, encode, self.class.headers).tap do |response| | |||
self.id = id_from_response(response) | |||
load_attributes_from_response(response) | |||
end | |||
end | |||
|
|||
private | |||
|
|||
def singleton_path(options = nil) | |||
self.class.singleton_path(options || prefix_options) | |||
end | |||
|
|||
end | |||
|
|||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,13 @@ | |||
class Inventory < ActiveResource::Base | |||
include ActiveResource::Singleton | |||
self.site = 'http://37s.sunrise.i:3000' | |||
self.prefix = '/products/:product_id/' | |||
|
|||
schema do | |||
integer :total | |||
integer :used | |||
|
|||
string :status | |||
end | |||
end | |||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,19 @@ | |||
class Weather < ActiveResource::Base | |||
include ActiveResource::Singleton | |||
self.site = 'http://37s.sunrise.i:3000' | |||
|
|||
schema do | |||
string :status | |||
string :temperature | |||
end | |||
end | |||
|
|||
class WeatherDashboard < ActiveResource::Base | |||
include ActiveResource::Singleton | |||
self.site = 'http://37s.sunrise.i:3000' | |||
self.singleton_name = 'dashboard' | |||
|
|||
schema do | |||
string :status | |||
end | |||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,136 @@ | |||
require 'abstract_unit' | |||
require 'fixtures/weather' | |||
require 'fixtures/inventory' | |||
|
|||
class SingletonTest < ActiveSupport::TestCase | |||
def setup_weather | |||
weather = { :status => 'Sunny', :temperature => 67 } | |||
ActiveResource::HttpMock.respond_to do |mock| | |||
mock.get '/weather.json', {}, weather.to_json | |||
mock.get '/weather.json?degrees=fahrenheit', {}, weather.merge(:temperature => 100).to_json | |||
mock.post '/weather.json', {}, weather.to_json, 201, 'Location' => '/weather.json' | |||
mock.delete '/weather.json', {}, nil | |||
mock.put '/weather.json', {}, nil, 204 | |||
end | |||
end | |||
|
|||
def setup_weather_not_found | |||
ActiveResource::HttpMock.respond_to do |mock| | |||
mock.get '/weather.json', {}, nil, 404 | |||
end | |||
end | |||
|
|||
def setup_inventory | |||
inventory = {:status => 'Sold Out', :total => 10, :used => 10}.to_json | |||
|
|||
ActiveResource::HttpMock.respond_to do |mock| | |||
mock.get '/products/5/inventory.json', {}, inventory | |||
end | |||
end | |||
|
|||
def test_custom_singleton_name | |||
assert_equal 'dashboard', WeatherDashboard.singleton_name | |||
end | |||
|
|||
def test_singleton_path | |||
assert_equal '/weather.json', Weather.singleton_path | |||
end | |||
|
|||
def test_singleton_path_with_parameters | |||
assert_equal '/weather.json?degrees=fahrenheit', Weather.singleton_path(:degrees => 'fahrenheit') | |||
assert_equal '/weather.json?degrees=false', Weather.singleton_path(:degrees => false) | |||
assert_equal '/weather.json?degrees=', Weather.singleton_path(:degrees => nil) | |||
|
|||
assert_equal '/weather.json?degrees=fahrenheit', Weather.singleton_path('degrees' => 'fahrenheit') | |||
|
|||
# Use include? because ordering of param hash is not guaranteed | |||
path = Weather.singleton_path(:degrees => 'fahrenheit', :lunar => true) | |||
assert path.include?('weather.json') | |||
assert path.include?('degrees=fahrenheit') | |||
assert path.include?('lunar=true') | |||
|
|||
path = Weather.singleton_path(:days => ['monday', 'saturday and sunday', nil, false]) | |||
assert_equal '/weather.json?days%5B%5D=monday&days%5B%5D=saturday+and+sunday&days%5B%5D=&days%5B%5D=false', path | |||
|
|||
path = Inventory.singleton_path(:product_id => 5) | |||
assert_equal '/products/5/inventory.json', path | |||
|
|||
path = Inventory.singleton_path({:product_id =>5}, {:sold => true}) | |||
assert_equal '/products/5/inventory.json?sold=true', path | |||
end | |||
|
|||
def test_find_singleton | |||
setup_weather | |||
weather = Weather.send(:find_singleton, Hash.new) | |||
assert_not_nil weather | |||
assert_equal 'Sunny', weather.status | |||
assert_equal 67, weather.temperature | |||
end | |||
|
|||
def test_find | |||
setup_weather | |||
weather = Weather.find | |||
assert_not_nil weather | |||
assert_equal 'Sunny', weather.status | |||
assert_equal 67, weather.temperature | |||
end | |||
|
|||
def test_find_with_param_options | |||
setup_inventory | |||
inventory = Inventory.find(:params => {:product_id => 5}) | |||
|
|||
assert_not_nil inventory | |||
assert_equal 'Sold Out', inventory.status | |||
assert_equal 10, inventory.used | |||
assert_equal 10, inventory.total | |||
end | |||
|
|||
def test_find_with_query_options | |||
setup_weather | |||
|
|||
weather = Weather.find(:params => {:degrees => 'fahrenheit'}) | |||
assert_not_nil weather | |||
assert_equal 'Sunny', weather.status | |||
assert_equal 100, weather.temperature | |||
end | |||
|
|||
def test_not_found | |||
setup_weather_not_found | |||
|
|||
assert_raise ActiveResource::ResourceNotFound do | |||
Weather.find | |||
end | |||
end | |||
|
|||
def test_create_singleton | |||
setup_weather | |||
weather = Weather.create(:status => 'Sunny', :temperature => 67) | |||
assert_not_nil weather | |||
assert_equal 'Sunny', weather.status | |||
assert_equal 67, weather.temperature | |||
end | |||
|
|||
def test_destroy | |||
setup_weather | |||
|
|||
# First Create the Weather | |||
weather = Weather.create(:status => 'Sunny', :temperature => 67) | |||
assert_not_nil weather | |||
|
|||
# Now Destroy it | |||
weather.destroy | |||
end | |||
|
|||
def test_update | |||
setup_weather | |||
|
|||
# First Create the Weather | |||
weather = Weather.create(:status => 'Sunny', :temperature => 67) | |||
assert_not_nil weather | |||
|
|||
# Then update it | |||
weather.status = 'Rainy' | |||
weather.save | |||
end | |||
end | |||
|