Skip to content

Commit

Permalink
Merge pull request #4 from kenmazaika/master
Browse files Browse the repository at this point in the history
Support Singleton Routes
  • Loading branch information
SweeD committed Apr 4, 2012
2 parents a05a725 + 5ecd87b commit fd79797
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/active_resource.rb
Expand Up @@ -36,5 +36,6 @@ module ActiveResource
autoload :HttpMock autoload :HttpMock
autoload :Observing autoload :Observing
autoload :Schema autoload :Schema
autoload :Singleton
autoload :Validations autoload :Validations
end end
114 changes: 114 additions & 0 deletions lib/active_resource/singleton.rb
@@ -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
13 changes: 13 additions & 0 deletions test/fixtures/inventory.rb
@@ -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

19 changes: 19 additions & 0 deletions test/fixtures/weather.rb
@@ -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
136 changes: 136 additions & 0 deletions test/singleton_test.rb
@@ -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 fd79797

Please sign in to comment.