2006-06-25 10:44:22 -04:00
|
|
|
require 'active_resource/connection'
|
2006-12-28 20:18:09 -05:00
|
|
|
require 'cgi'
|
|
|
|
require 'set'
|
2006-06-25 10:44:22 -04:00
|
|
|
|
|
|
|
module ActiveResource
|
|
|
|
class Base
|
2006-12-29 17:16:04 -05:00
|
|
|
# The logger for diagnosing and tracing ARes calls.
|
2006-10-23 18:21:09 -04:00
|
|
|
cattr_accessor :logger
|
|
|
|
|
2006-06-25 10:44:22 -04:00
|
|
|
class << self
|
2007-01-16 01:34:10 -05:00
|
|
|
# Gets the URI of the resource's site
|
2006-12-21 17:12:21 -05:00
|
|
|
def site
|
|
|
|
if defined?(@site)
|
|
|
|
@site
|
|
|
|
elsif superclass != Object and superclass.site
|
|
|
|
superclass.site.dup.freeze
|
|
|
|
end
|
|
|
|
end
|
2006-08-31 03:55:31 -04:00
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Set the URI for the REST resources
|
2006-08-31 21:15:10 -04:00
|
|
|
def site=(site)
|
2006-09-04 06:04:23 -04:00
|
|
|
@connection = nil
|
2006-12-29 17:16:04 -05:00
|
|
|
@site = create_site_uri_from(site)
|
2006-06-25 10:44:22 -04:00
|
|
|
end
|
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Base connection to remote service
|
2006-06-25 10:44:22 -04:00
|
|
|
def connection(refresh = false)
|
|
|
|
@connection = Connection.new(site) if refresh || @connection.nil?
|
|
|
|
@connection
|
|
|
|
end
|
2006-09-22 17:29:54 -04:00
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
attr_accessor_with_default(:element_name) { to_s.underscore } #:nodoc:
|
|
|
|
attr_accessor_with_default(:collection_name) { element_name.pluralize } #:nodoc:
|
|
|
|
attr_accessor_with_default(:primary_key, 'id') #:nodoc:
|
|
|
|
|
|
|
|
# Gets the resource prefix
|
|
|
|
# prefix/collectionname/1.xml
|
2006-08-31 21:15:10 -04:00
|
|
|
def prefix(options={})
|
|
|
|
default = site.path
|
|
|
|
default << '/' unless default[-1..-1] == '/'
|
2006-09-04 06:04:23 -04:00
|
|
|
self.prefix = default
|
2006-08-31 21:15:10 -04:00
|
|
|
prefix(options)
|
|
|
|
end
|
2006-09-22 17:29:54 -04:00
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Sets the resource prefix
|
|
|
|
# prefix/collectionname/1.xml
|
2006-09-04 06:04:23 -04:00
|
|
|
def prefix=(value = '/')
|
2006-12-29 17:16:04 -05:00
|
|
|
prefix_call = value.gsub(/:\w+/) { |key| "\#{options[#{key}]}" }
|
|
|
|
instance_eval <<-end_eval, __FILE__, __LINE__
|
|
|
|
def prefix_source() "#{value}" end
|
|
|
|
def prefix(options={}) "#{prefix_call}" end
|
|
|
|
end_eval
|
2006-12-28 20:18:09 -05:00
|
|
|
rescue
|
|
|
|
logger.error "Couldn't set prefix: #{$!}\n #{method_decl}"
|
|
|
|
raise
|
2006-08-31 21:15:10 -04:00
|
|
|
end
|
2006-12-28 20:18:09 -05:00
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
alias_method :set_prefix, :prefix= #:nodoc:
|
2006-09-22 17:29:54 -04:00
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
alias_method :set_element_name, :element_name= #:nodoc:
|
|
|
|
alias_method :set_collection_name, :collection_name= #:nodoc:
|
2006-08-31 21:15:10 -04:00
|
|
|
|
|
|
|
def element_path(id, options = {})
|
2006-12-28 20:18:09 -05:00
|
|
|
"#{prefix(options)}#{collection_name}/#{id}.xml#{query_string(options)}"
|
2006-08-31 21:15:10 -04:00
|
|
|
end
|
2006-09-22 17:29:54 -04:00
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
def collection_path(options = {})
|
2006-12-28 20:18:09 -05:00
|
|
|
"#{prefix(options)}#{collection_name}.xml#{query_string(options)}"
|
2006-08-31 21:15:10 -04:00
|
|
|
end
|
2006-09-22 17:29:54 -04:00
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
alias_method :set_primary_key, :primary_key= #:nodoc:
|
2006-09-22 17:29:54 -04:00
|
|
|
|
2007-01-23 21:40:32 -05:00
|
|
|
# Create a new resource instance and request to the remote service
|
|
|
|
# that it be saved. This is equivalent to the following simultaneous calls:
|
|
|
|
#
|
|
|
|
# ryan = Person.new(:first => 'ryan')
|
|
|
|
# ryan.save
|
|
|
|
#
|
|
|
|
# The newly created resource is returned. If a failure has occurred an
|
|
|
|
# exception will be raised (see save). If the resource is invalid and
|
|
|
|
# has not been saved then <tt>resource.valid?</tt> will return <tt>false</tt>,
|
|
|
|
# while <tt>resource.new?</tt> will still return <tt>true</tt>.
|
|
|
|
#
|
|
|
|
def create(attributes = {}, prefix_options = {})
|
|
|
|
returning(self.new(attributes, prefix_options)) { |res| res.save }
|
|
|
|
end
|
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Core method for finding resources. Used similarly to ActiveRecord's find method.
|
|
|
|
# Person.find(1) # => GET /people/1.xml
|
|
|
|
# StreetAddress.find(1, :person_id => 1) # => GET /people/1/street_addresses/1.xml
|
2006-06-25 10:44:22 -04:00
|
|
|
def find(*arguments)
|
2006-08-31 21:15:10 -04:00
|
|
|
scope = arguments.slice!(0)
|
|
|
|
options = arguments.slice!(0) || {}
|
2006-06-25 10:44:22 -04:00
|
|
|
|
|
|
|
case scope
|
2006-08-31 21:15:10 -04:00
|
|
|
when :all then find_every(options)
|
|
|
|
when :first then find_every(options).first
|
|
|
|
else find_single(scope, options)
|
2006-06-25 10:44:22 -04:00
|
|
|
end
|
|
|
|
end
|
2006-08-31 21:15:10 -04:00
|
|
|
|
2006-10-06 13:25:10 -04:00
|
|
|
def delete(id)
|
|
|
|
connection.delete(element_path(id))
|
|
|
|
end
|
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Evalutes to <tt>true</tt> if the resource is found.
|
2007-01-12 02:20:05 -05:00
|
|
|
def exists?(id, options = {})
|
|
|
|
id && !find_single(id, options).nil?
|
|
|
|
rescue ActiveResource::ResourceNotFound
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
2006-08-31 21:15:10 -04:00
|
|
|
private
|
2007-01-16 01:34:10 -05:00
|
|
|
# Find every resource.
|
2006-08-31 21:15:10 -04:00
|
|
|
def find_every(options)
|
2006-12-12 10:29:54 -05:00
|
|
|
collection = connection.get(collection_path(options)) || []
|
|
|
|
collection.collect! { |element| new(element, options) }
|
2006-08-31 21:15:10 -04:00
|
|
|
end
|
2006-09-22 17:29:54 -04:00
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Find a single resource.
|
|
|
|
# { :person => person1 }
|
2006-08-31 21:15:10 -04:00
|
|
|
def find_single(scope, options)
|
2006-12-12 10:29:54 -05:00
|
|
|
new(connection.get(element_path(scope, options)), options)
|
2006-08-31 21:15:10 -04:00
|
|
|
end
|
2006-12-21 17:12:21 -05:00
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Accepts a URI and creates the site URI from that.
|
2006-12-12 21:15:27 -05:00
|
|
|
def create_site_uri_from(site)
|
2006-12-21 17:12:21 -05:00
|
|
|
site.is_a?(URI) ? site.dup : URI.parse(site)
|
2006-12-12 21:15:27 -05:00
|
|
|
end
|
2006-12-28 20:18:09 -05:00
|
|
|
|
2006-12-29 17:16:04 -05:00
|
|
|
def prefix_parameters
|
|
|
|
@prefix_parameters ||= prefix_source.scan(/:\w+/).map { |key| key[1..-1].to_sym }.to_set
|
|
|
|
end
|
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Builds the query string for the request.
|
2006-12-28 20:18:09 -05:00
|
|
|
def query_string(options)
|
|
|
|
# Omit parameters which appear in the URI path.
|
2006-12-29 17:16:04 -05:00
|
|
|
query_params = options.reject { |key, value| prefix_parameters.include?(key) }
|
2006-12-28 20:18:09 -05:00
|
|
|
|
|
|
|
# Accumulate a list of escaped key=value pairs for the given parameters.
|
|
|
|
pairs = []
|
|
|
|
query_params.each do |key, value|
|
|
|
|
key = CGI.escape(key.to_s)
|
|
|
|
|
|
|
|
# a => b becomes a=b
|
|
|
|
# a => [b, c] becomes a[]=b&a[]=c
|
|
|
|
case value
|
|
|
|
when Array
|
|
|
|
value.each { |val| pairs << "#{key}[]=#{CGI.escape(val.to_s)}" }
|
|
|
|
else
|
|
|
|
pairs << "#{key}=#{CGI.escape(value.to_s)}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
"?#{pairs * '&'}" unless pairs.empty?
|
|
|
|
end
|
2006-06-25 10:44:22 -04:00
|
|
|
end
|
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
attr_accessor :attributes #:nodoc:
|
|
|
|
attr_accessor :prefix_options #:nodoc:
|
2006-09-22 17:29:54 -04:00
|
|
|
|
2006-08-31 21:15:10 -04:00
|
|
|
def initialize(attributes = {}, prefix_options = {})
|
2006-09-04 06:04:23 -04:00
|
|
|
@attributes = {}
|
|
|
|
self.load attributes
|
2006-08-31 21:15:10 -04:00
|
|
|
@prefix_options = prefix_options
|
2006-06-25 10:44:22 -04:00
|
|
|
end
|
2006-08-31 21:15:10 -04:00
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Is the resource a new object?
|
2006-09-04 19:36:13 -04:00
|
|
|
def new?
|
2006-08-31 21:15:10 -04:00
|
|
|
id.nil?
|
|
|
|
end
|
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Get the id of the object.
|
2006-06-25 10:44:22 -04:00
|
|
|
def id
|
2006-08-31 21:15:10 -04:00
|
|
|
attributes[self.class.primary_key]
|
2006-06-25 10:44:22 -04:00
|
|
|
end
|
2006-09-22 17:29:54 -04:00
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Set the id of the object.
|
2006-06-25 10:44:22 -04:00
|
|
|
def id=(id)
|
2006-08-31 21:15:10 -04:00
|
|
|
attributes[self.class.primary_key] = id
|
2006-06-25 10:44:22 -04:00
|
|
|
end
|
2006-09-22 17:29:54 -04:00
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# True if and only if +other+ is the same object or is an instance of the same class, is not +new?+, and has the same +id+.
|
2006-12-22 16:42:52 -05:00
|
|
|
def ==(other)
|
|
|
|
other.equal?(self) || (other.instance_of?(self.class) && !other.new? && other.id == id)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Delegates to ==
|
|
|
|
def eql?(other)
|
|
|
|
self == other
|
|
|
|
end
|
|
|
|
|
|
|
|
# Delegates to id in order to allow two resources of the same type and id to work with something like:
|
|
|
|
# [Person.find(1), Person.find(2)] & [Person.find(1), Person.find(4)] # => [Person.find(1)]
|
|
|
|
def hash
|
|
|
|
id.hash
|
|
|
|
end
|
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Delegates to +create+ if a new object, +update+ if its old.
|
2006-06-25 10:44:22 -04:00
|
|
|
def save
|
2006-09-04 19:36:13 -04:00
|
|
|
new? ? create : update
|
2006-06-25 10:44:22 -04:00
|
|
|
end
|
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Delete the resource.
|
2006-06-25 10:44:22 -04:00
|
|
|
def destroy
|
2006-12-05 14:12:51 -05:00
|
|
|
connection.delete(element_path)
|
2006-06-25 10:44:22 -04:00
|
|
|
end
|
2006-09-04 06:04:23 -04:00
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Evaluates to <tt>true</tt> if this resource is found.
|
2007-01-12 02:20:05 -05:00
|
|
|
def exists?
|
|
|
|
!new? && self.class.exists?(id, prefix_options)
|
|
|
|
end
|
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Convert the resource to an XML string
|
2006-10-24 15:05:43 -04:00
|
|
|
def to_xml(options={})
|
|
|
|
attributes.to_xml({:root => self.class.element_name}.merge(options))
|
2006-06-25 10:44:22 -04:00
|
|
|
end
|
2006-08-31 21:15:10 -04:00
|
|
|
|
|
|
|
# Reloads the attributes of this object from the remote web service.
|
|
|
|
def reload
|
2006-09-04 06:04:23 -04:00
|
|
|
self.load self.class.find(id, @prefix_options).attributes
|
|
|
|
end
|
|
|
|
|
|
|
|
# Manually load attributes from a hash. Recursively loads collections of
|
|
|
|
# resources.
|
|
|
|
def load(attributes)
|
2006-09-04 20:02:17 -04:00
|
|
|
raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
|
2006-09-04 06:04:23 -04:00
|
|
|
attributes.each do |key, value|
|
|
|
|
@attributes[key.to_s] =
|
|
|
|
case value
|
|
|
|
when Array
|
|
|
|
resource = find_or_create_resource_for_collection(key)
|
|
|
|
value.map { |attrs| resource.new(attrs) }
|
|
|
|
when Hash
|
2006-12-12 10:29:54 -05:00
|
|
|
resource = find_or_create_resource_for(key)
|
|
|
|
resource.new(value)
|
2006-09-04 06:04:23 -04:00
|
|
|
when ActiveResource::Base
|
|
|
|
value.class.new(value.attributes)
|
|
|
|
else
|
|
|
|
value.dup rescue value
|
|
|
|
end
|
|
|
|
end
|
2006-08-31 21:15:10 -04:00
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2006-06-25 10:44:22 -04:00
|
|
|
protected
|
|
|
|
def connection(refresh = false)
|
|
|
|
self.class.connection(refresh)
|
|
|
|
end
|
2006-09-22 17:29:54 -04:00
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Update the resource on the remote service.
|
2006-06-25 10:44:22 -04:00
|
|
|
def update
|
2006-12-05 14:12:51 -05:00
|
|
|
connection.put(element_path, to_xml)
|
2006-06-25 10:44:22 -04:00
|
|
|
end
|
2006-08-31 21:15:10 -04:00
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Create (i.e., save to the remote service) the new resource.
|
2006-08-31 21:15:10 -04:00
|
|
|
def create
|
2006-12-29 17:28:30 -05:00
|
|
|
returning connection.post(collection_path, to_xml) do |response|
|
|
|
|
self.id = id_from_response(response)
|
|
|
|
end
|
2006-08-31 21:15:10 -04:00
|
|
|
end
|
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
# Takes a response from a typical create post and pulls the ID out
|
2006-09-20 15:31:17 -04:00
|
|
|
def id_from_response(response)
|
|
|
|
response['Location'][/\/([^\/]*?)(\.\w+)?$/, 1]
|
|
|
|
end
|
|
|
|
|
2006-12-05 14:12:51 -05:00
|
|
|
def element_path(options = nil)
|
|
|
|
self.class.element_path(id, options || prefix_options)
|
|
|
|
end
|
|
|
|
|
|
|
|
def collection_path(options = nil)
|
|
|
|
self.class.collection_path(options || prefix_options)
|
|
|
|
end
|
|
|
|
|
2006-09-04 06:04:23 -04:00
|
|
|
private
|
2007-01-16 01:34:10 -05:00
|
|
|
# Tries to find a resource for a given collection name; if it fails, then the resource is created
|
2006-09-04 06:04:23 -04:00
|
|
|
def find_or_create_resource_for_collection(name)
|
|
|
|
find_or_create_resource_for(name.to_s.singularize)
|
|
|
|
end
|
2007-01-16 01:34:10 -05:00
|
|
|
|
|
|
|
# Tries to find a resource for a given name; if it fails, then the resource is created
|
2006-09-04 06:04:23 -04:00
|
|
|
def find_or_create_resource_for(name)
|
|
|
|
resource_name = name.to_s.camelize
|
|
|
|
resource_name.constantize
|
|
|
|
rescue NameError
|
|
|
|
resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
|
|
|
|
resource.prefix = self.class.prefix
|
2006-12-12 10:29:54 -05:00
|
|
|
resource.site = self.class.site
|
2006-09-04 06:04:23 -04:00
|
|
|
resource
|
|
|
|
end
|
|
|
|
|
2007-01-16 01:34:10 -05:00
|
|
|
def method_missing(method_symbol, *arguments) #:nodoc:
|
2006-06-25 10:44:22 -04:00
|
|
|
method_name = method_symbol.to_s
|
2006-09-22 17:29:54 -04:00
|
|
|
|
2006-06-25 10:44:22 -04:00
|
|
|
case method_name.last
|
|
|
|
when "="
|
|
|
|
attributes[method_name.first(-1)] = arguments.first
|
|
|
|
when "?"
|
2006-08-31 21:15:10 -04:00
|
|
|
attributes[method_name.first(-1)] == true
|
2006-06-25 10:44:22 -04:00
|
|
|
else
|
2006-08-31 21:15:10 -04:00
|
|
|
attributes.has_key?(method_name) ? attributes[method_name] : super
|
2006-06-25 10:44:22 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2006-08-31 03:55:31 -04:00
|
|
|
end
|