Skip to content
Permalink
Browse files

Add support for Singleton Routes.

Implement ActiveResource::Singleton as a mixin, instead of using ActiveResource::Singleton::Base as a subclass.
  • Loading branch information...
kenmazaika committed Mar 25, 2012
1 parent a05a725 commit 5ecd87b6a9e0f586273b218a2a84121748e7a689
Showing with 283 additions and 0 deletions.
  1. +1 −0 lib/active_resource.rb
  2. +114 −0 lib/active_resource/singleton.rb
  3. +13 −0 test/fixtures/inventory.rb
  4. +19 −0 test/fixtures/weather.rb
  5. +136 −0 test/singleton_test.rb
@@ -36,5 +36,6 @@ module ActiveResource
autoload :HttpMock
autoload :Observing
autoload :Schema
autoload :Singleton
autoload :Validations
end
@@ -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
@@ -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

@@ -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
@@ -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

0 comments on commit 5ecd87b

Please sign in to comment.
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.