Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/active_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ module ActiveResource
autoload :HttpMock
autoload :Observing
autoload :Schema
autoload :Singleton
autoload :Validations
end
114 changes: 114 additions & 0 deletions lib/active_resource/singleton.rb
Original file line number 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
13 changes: 13 additions & 0 deletions test/fixtures/inventory.rb
Original file line number 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

19 changes: 19 additions & 0 deletions test/fixtures/weather.rb
Original file line number 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
136 changes: 136 additions & 0 deletions test/singleton_test.rb
Original file line number 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