diff options
Diffstat (limited to 'activeresource/lib/active_resource/base.rb')
-rw-r--r-- | activeresource/lib/active_resource/base.rb | 144 |
1 files changed, 142 insertions, 2 deletions
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 18105e8887..60bd573911 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -13,6 +13,7 @@ require 'set' require 'uri' require 'active_resource/exceptions' +require 'active_resource/schema_definition' module ActiveResource # ActiveResource::Base is the main class for mapping RESTful resources as models in a Rails application. @@ -241,6 +242,127 @@ module ActiveResource cattr_accessor :logger class << self + # This will shortly disappear to be replaced by the migration-style + # usage of this for defining a schema. At that point, all the doc + # currenlty on <tt>schema=</tt> will move back here... + def schema # :nodoc: + @schema ||= nil + end + # 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 + # define_schema do |s| + # # define each attribute separately + # s.attribute 'name', :string + # + # # or use the convenience methods and pass >=1 attribute names + # s.string 'eye_colour', 'hair_colour' + # s.integer 'age' + # s.float 'height', 'weight' + # + # # unsupported types should be left as strings + # # overload the accessor methods if you need to convert them + # s.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 define_schema + schema_definition = SchemaDefinition.new + yield schema_definition if block_given? + + # 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 + end + + + # Alternative, direct way to specify a <tt>schema</tt> for this + # Resource. <tt>define_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>define_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 + + define_schema do |s| + the_schema.each {|k,v| s.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 +898,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 +1294,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 +1399,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 |