aboutsummaryrefslogtreecommitdiffstats
path: root/activeresource/lib/active_resource
diff options
context:
space:
mode:
Diffstat (limited to 'activeresource/lib/active_resource')
-rw-r--r--activeresource/lib/active_resource/base.rb140
-rw-r--r--activeresource/lib/active_resource/schema.rb55
2 files changed, 193 insertions, 2 deletions
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index 18105e8887..b833e9c8ce 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -241,6 +241,124 @@ module ActiveResource
cattr_accessor :logger
class << self
+ # Creates a schema for this resource - setting the attributes that are
+ # known prior to fetching an instance from the remote system.
+ #
+ # The schema helps define the set of <tt>known_attributes</tt> of the
+ # current resource.
+ #
+ # There is no need to specify a schema for your Active Resource. If
+ # you do not, the <tt>known_attributes</tt> will be guessed from the
+ # instance attributes returned when an instance is fetched from the
+ # remote system.
+ #
+ # example:
+ # class Person < ActiveResource::Base
+ # schema do
+ # # define each attribute separately
+ # attribute 'name', :string
+ #
+ # # or use the convenience methods and pass >=1 attribute names
+ # string 'eye_colour', 'hair_colour'
+ # integer 'age'
+ # float 'height', 'weight'
+ #
+ # # unsupported types should be left as strings
+ # # overload the accessor methods if you need to convert them
+ # attribute 'created_at', 'string'
+ # end
+ # end
+ #
+ # p = Person.new
+ # p.respond_to? :name # => true
+ # p.respond_to? :age # => true
+ # p.name # => nil
+ # p.age # => nil
+ #
+ # j = Person.find_by_name('John') # <person><name>John</name><age>34</age><num_children>3</num_children></person>
+ # j.respond_to? :name # => true
+ # j.respond_to? :age # => true
+ # j.name # => 'John'
+ # j.age # => '34' # note this is a string!
+ # j.num_children # => '3' # note this is a string!
+ #
+ # p.num_children # => NoMethodError
+ #
+ # Attribute-types must be one of:
+ # string, integer, float
+ #
+ # Note: at present the attribute-type doesn't do anything, but stay
+ # tuned...
+ # Shortly it will also *cast* the value of the returned attribute.
+ # ie:
+ # j.age # => 34 # cast to an integer
+ # j.weight # => '65' # still a string!
+ #
+ def schema(&block)
+ if block_given?
+ schema_definition = Schema.new
+ schema_definition.instance_eval(&block)
+
+ # skip out if we didn't define anything
+ return unless schema_definition.attrs.present?
+
+ @schema ||= {}.with_indifferent_access
+ @known_attributes ||= []
+
+ schema_definition.attrs.each do |k,v|
+ @schema[k] = v
+ @known_attributes << k
+ end
+
+ schema
+ else
+ @schema ||= nil
+ end
+ end
+
+ # Alternative, direct way to specify a <tt>schema</tt> for this
+ # Resource. <tt>schema</tt> is more flexible, but this is quick
+ # for a very simple schema.
+ #
+ # Pass the schema as a hash with the keys being the attribute-names
+ # and the value being one of the accepted attribute types (as defined
+ # in <tt>schema</tt>)
+ #
+ # example:
+ #
+ # class Person < ActiveResource::Base
+ # schema = {'name' => :string, 'age' => :integer }
+ # end
+ #
+ # The keys/values can be strings or symbols. They will be converted to
+ # strings.
+ #
+ def schema=(the_schema)
+ unless the_schema.present?
+ # purposefully nulling out the schema
+ @schema = nil
+ @known_attributes = []
+ return
+ end
+
+ raise ArgumentError, "Expected a hash" unless the_schema.kind_of? Hash
+
+ schema do
+ the_schema.each {|k,v| attribute(k,v) }
+ end
+ end
+
+ # Returns the list of known attributes for this resource, gathered
+ # from the provided <tt>schema</tt>
+ # Attributes that are known will cause your resource to return 'true'
+ # when <tt>respond_to?</tt> is called on them. A known attribute will
+ # return nil if not set (rather than <t>MethodNotFound</tt>); thus
+ # known attributes can be used with <tt>validates_presence_of</tt>
+ # without a getter-method.
+ def known_attributes
+ @known_attributes ||= []
+ end
+
# Gets the URI of the REST resources to map for this class. The site variable is required for
# Active Resource's mapping to work.
def site
@@ -776,6 +894,21 @@ module ActiveResource
attr_accessor :attributes #:nodoc:
attr_accessor :prefix_options #:nodoc:
+ # If no schema has been defined for the class (see
+ # <tt>ActiveResource::schema=</tt>), the default automatic schema is
+ # generated from the current instance's attributes
+ def schema
+ self.class.schema || self.attributes
+ end
+
+ # This is a list of known attributes for this resource. Either
+ # gathered fromthe provided <tt>schema</tt>, or from the attributes
+ # set on this instance after it has been fetched from the remote system.
+ def known_attributes
+ self.class.known_attributes + self.attributes.keys.map(&:to_s)
+ end
+
+
# Constructor method for \new resources; the optional +attributes+ parameter takes a \hash
# of attributes for the \new resource.
#
@@ -1157,7 +1290,7 @@ module ActiveResource
method_name = method.to_s
if attributes.nil?
super
- elsif attributes.has_key?(method_name)
+ elsif known_attributes.include?(method_name)
true
elsif method_name =~ /(?:=|\?)$/ && attributes.include?($`)
true
@@ -1262,7 +1395,10 @@ module ActiveResource
attributes[$`]
end
else
- attributes.include?(method_name) ? attributes[method_name] : super
+ return attributes[method_name] if attributes.include?(method_name)
+ # not set right now but we know about it
+ return nil if known_attributes.include?(method_name)
+ super
end
end
end
diff --git a/activeresource/lib/active_resource/schema.rb b/activeresource/lib/active_resource/schema.rb
new file mode 100644
index 0000000000..8368b652c2
--- /dev/null
+++ b/activeresource/lib/active_resource/schema.rb
@@ -0,0 +1,55 @@
+require 'active_resource/exceptions'
+
+module ActiveResource # :nodoc:
+ class Schema # :nodoc:
+ # attributes can be known to be one of these types. They are easy to
+ # cast to/from.
+ KNOWN_ATTRIBUTE_TYPES = %w( string integer float )
+
+ # An array of attribute definitions, representing the attributes that
+ # have been defined.
+ attr_accessor :attrs
+
+ # The internals of an Active Resource Schema are very simple -
+ # unlike an Active Record TableDefinition (on which it is based).
+ # It provides a set of convenience methods for people to define their
+ # schema using the syntax:
+ # schema do
+ # string :foo
+ # integer :bar
+ # end
+ #
+ # The schema stores the name and type of each attribute. That is then
+ # read out by the schema method to populate the actual
+ # Resource's schema
+ def initialize
+ @attrs = {}
+ end
+
+ def attribute(name, type, options = {})
+ raise ArgumentError, "Unknown Attribute type: #{type.inspect} for key: #{name.inspect}" unless type.nil? || Schema::KNOWN_ATTRIBUTE_TYPES.include?(type.to_s)
+
+ the_type = type.to_s
+ # TODO: add defaults
+ #the_attr = [type.to_s]
+ #the_attr << options[:default] if options.has_key? :default
+ @attrs[name.to_s] = the_type
+ self
+ end
+
+ # The following are the attribute types supported by Active Resource
+ # migrations.
+ # TODO: We should eventually support all of these:
+ # %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |attr_type|
+ KNOWN_ATTRIBUTE_TYPES.each do |attr_type|
+ class_eval <<-EOV
+ def #{attr_type.to_s}(*args)
+ options = args.extract_options!
+ attr_names = args
+
+ attr_names.each { |name| attribute(name, '#{attr_type}', options) }
+ end
+ EOV
+ end
+ end
+end