aboutsummaryrefslogtreecommitdiffstats
path: root/activeresource
diff options
context:
space:
mode:
Diffstat (limited to 'activeresource')
-rw-r--r--activeresource/lib/active_resource/base.rb144
-rw-r--r--activeresource/lib/active_resource/schema_definition.rb58
-rw-r--r--activeresource/test/cases/base/schema_test.rb409
3 files changed, 609 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
diff --git a/activeresource/lib/active_resource/schema_definition.rb b/activeresource/lib/active_resource/schema_definition.rb
new file mode 100644
index 0000000000..abcbf3ba4e
--- /dev/null
+++ b/activeresource/lib/active_resource/schema_definition.rb
@@ -0,0 +1,58 @@
+require 'active_resource/exceptions'
+
+module ActiveResource # :nodoc:
+ class SchemaDefinition # :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 SchemaDefinition 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:
+ # define_schema do |s|
+ # s.string :foo
+ # s.integer :bar
+ # end
+ #
+ # The schema stores the name and type of each attribute. That is then
+ # read out by the define_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? || SchemaDefinition::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) # def string(*args)
+ options = args.extract_options! # options = args.extract_options!
+ attr_names = args # attr_names = args
+ #
+ attr_names.each { |name| attribute(name, '#{attr_type}', options) } # attr_names.each { |name| attribute(name, 'string', options) }
+ end # end
+ EOV
+
+ end
+
+ end
+end
diff --git a/activeresource/test/cases/base/schema_test.rb b/activeresource/test/cases/base/schema_test.rb
new file mode 100644
index 0000000000..398e7cf539
--- /dev/null
+++ b/activeresource/test/cases/base/schema_test.rb
@@ -0,0 +1,409 @@
+require 'abstract_unit'
+require "fixtures/person"
+require "fixtures/street_address"
+
+########################################################################
+# Testing the schema of your Active Resource models
+########################################################################
+class SchemaTest < ActiveModel::TestCase
+ def setup
+ @matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person')
+ @david = { :id => 2, :name => 'David' }.to_xml(:root => 'person')
+ @greg = { :id => 3, :name => 'Greg' }.to_xml(:root => 'person')
+ @addy = { :id => 1, :street => '12345 Street', :country => 'Australia' }.to_xml(:root => 'address')
+ @default_request_headers = { 'Content-Type' => 'application/xml' }
+ @rick = { :name => "Rick", :age => 25 }.to_xml(:root => "person")
+ @people = [{ :id => 1, :name => 'Matz' }, { :id => 2, :name => 'David' }].to_xml(:root => 'people')
+ @people_david = [{ :id => 2, :name => 'David' }].to_xml(:root => 'people')
+ @addresses = [{ :id => 1, :street => '12345 Street', :country => 'Australia' }].to_xml(:root => 'addresses')
+
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.get "/people/1.xml", {}, @matz
+ mock.get "/people/2.xml", {}, @david
+ mock.get "/people/Greg.xml", {}, @greg
+ mock.get "/people/4.xml", {'key' => 'value'}, nil, 404
+ mock.get "/people/5.xml", {}, @rick
+ mock.put "/people/1.xml", {}, nil, 204
+ mock.delete "/people/1.xml", {}, nil, 200
+ mock.delete "/people/2.xml", {}, nil, 400
+ mock.get "/people/99.xml", {}, nil, 404
+ mock.post "/people.xml", {}, @rick, 201, 'Location' => '/people/5.xml'
+ mock.get "/people.xml", {}, @people
+ mock.get "/people/1/addresses.xml", {}, @addresses
+ mock.get "/people/1/addresses/1.xml", {}, @addy
+ mock.get "/people/1/addresses/2.xml", {}, nil, 404
+ mock.get "/people/2/addresses/1.xml", {}, nil, 404
+ mock.get "/people/Greg/addresses/1.xml", {}, @addy
+ mock.put "/people/1/addresses/1.xml", {}, nil, 204
+ mock.delete "/people/1/addresses/1.xml", {}, nil, 200
+ mock.post "/people/1/addresses.xml", {}, nil, 201, 'Location' => '/people/1/addresses/5'
+ mock.get "/people//addresses.xml", {}, nil, 404
+ mock.get "/people//addresses/1.xml", {}, nil, 404
+ mock.put "/people//addressaddresseses/1.xml", {}, nil, 404
+ mock.delete "/people//addresses/1.xml", {}, nil, 404
+ mock.post "/people//addresses.xml", {}, nil, 404
+ mock.head "/people/1.xml", {}, nil, 200
+ mock.head "/people/Greg.xml", {}, nil, 200
+ mock.head "/people/99.xml", {}, nil, 404
+ mock.head "/people/1/addresses/1.xml", {}, nil, 200
+ mock.head "/people/1/addresses/2.xml", {}, nil, 404
+ mock.head "/people/2/addresses/1.xml", {}, nil, 404
+ mock.head "/people/Greg/addresses/1.xml", {}, nil, 200
+ end
+
+ Person.user = nil
+ Person.password = nil
+ end
+ def teardown
+ Person.schema = nil # hack to stop test bleedthrough...
+ end
+
+
+ #####################################################
+ # Passing in a schema directly and returning it
+ ####
+
+ test "schema on a new model should be empty" do
+ assert Person.schema.blank?, "should have a blank class schema"
+ assert Person.new.schema.blank?, "should have a blank instance schema"
+ end
+
+ test "schema should only accept a hash" do
+ ["blahblah", ['one','two'], [:age, :name], Person.new].each do |bad_schema|
+ assert_raises(ArgumentError,"should only accept a hash (or nil), but accepted: #{bad_schema.inspect}") do
+ Person.schema = bad_schema
+ end
+ end
+ end
+
+ test "schema should accept a simple hash" do
+ new_schema = {'age' => 'integer', 'name' => 'string'}
+
+ assert_nothing_raised { Person.schema = new_schema }
+ assert_equal new_schema, Person.schema
+ end
+
+ test "schema should accept a hash with simple values" do
+ new_schema = {'age' => 'integer', 'name' => 'string', 'height' => 'float', 'mydatetime' => 'string'}
+
+ assert_nothing_raised { Person.schema = new_schema }
+ assert_equal new_schema, Person.schema
+ end
+
+ test "schema should accept all known attribute types as values" do
+ # I'd prefer to use here...
+ ActiveResource::SchemaDefinition::KNOWN_ATTRIBUTE_TYPES.each do |the_type|
+ assert_nothing_raised("should have accepted #{the_type.inspect}"){ Person.schema = {'my_key' => the_type }}
+ end
+ end
+
+ test "schema should not accept unknown values" do
+ bad_values = [ :oogle, :blob, 'thing']
+
+ bad_values.each do |bad_value|
+ assert_raises(ArgumentError,"should only accept a known attribute type, but accepted: #{bad_value.inspect}") do
+ Person.schema = {'key' => bad_value}
+ end
+ end
+ end
+
+ test "schema should accept nil and remove the schema" do
+ new_schema = {'age' => 'integer', 'name' => 'string'}
+ assert_nothing_raised { Person.schema = new_schema }
+ assert_equal new_schema, Person.schema # sanity check
+
+
+ assert_nothing_raised { Person.schema = nil }
+ assert_nil Person.schema, "should have nulled out the schema, but still had: #{Person.schema.inspect}"
+ end
+
+
+ test "schema should be with indifferent access" do
+ new_schema = {:age => :integer, 'name' => 'string'}
+ new_schema_syms = new_schema.keys
+
+ assert_nothing_raised { Person.schema = new_schema }
+ new_schema_syms.each do |col|
+ assert Person.new.respond_to?(col.to_s), "should respond to the schema's string key, but failed on: #{col.to_s}"
+ assert Person.new.respond_to?(col.to_sym), "should respond to the schema's symbol key, but failed on: #{col.to_sym}"
+ end
+ end
+
+
+ test "schema on a fetched resource should return all the attributes of that model instance" do
+ p = Person.find(1)
+ s = p.schema
+
+ assert s.present?, "should have found a non-empty schema!"
+
+ p.attributes.each do |the_attr, val|
+ assert s.has_key?(the_attr), "should have found attr: #{the_attr} in schema, but only had: #{s.inspect}"
+ end
+ end
+
+ test "with two instances, default schema should match the attributes of the individual instances - even if they differ" do
+ matz = Person.find(1)
+ rick = Person.find(5)
+
+ m_attrs = matz.attributes.keys.sort
+ r_attrs = rick.attributes.keys.sort
+
+ assert_not_equal m_attrs, r_attrs, "should have different attributes on each model"
+
+ assert_not_equal matz.schema, rick.schema, "should have had different schemas too"
+ end
+
+ test "defining a schema should return it when asked" do
+ assert Person.schema.blank?, "should have a blank class schema"
+ new_schema = {'name' => 'string', 'age' => 'integer', 'height' => 'float', 'weight' => 'float'}
+ assert_nothing_raised {
+ Person.schema = new_schema
+ assert_equal new_schema, Person.schema, "should have saved the schema on the class"
+ assert_equal new_schema, Person.new.schema, "should have mde the schema available to every instance"
+ }
+ end
+
+ test "defining a schema, then fetching a model should still match the defined schema" do
+ # sanity checks
+ assert Person.schema.blank?, "should have a blank class schema"
+ new_schema = {'name' => 'string', 'age' => 'integer', 'height' => 'float', 'weight' => 'float'}
+
+ matz = Person.find(1)
+ assert !matz.schema.blank?, "should have some sort of schema on an instance variable"
+ assert_not_equal new_schema, matz.schema, "should not have the class-level schema until it's been added to the class!"
+
+ assert_nothing_raised {
+ Person.schema = new_schema
+ assert_equal new_schema, matz.schema, "class-level schema should override instance-level schema"
+ }
+ end
+
+
+ #####################################################
+ # Using the define_schema syntax
+ ####
+
+ test "should be able to use define_schema" do
+ assert Person.respond_to?(:define_schema), "should at least respond to the define_schema method"
+
+ assert_nothing_raised("Should allow the define_schema to take a block") do
+ Person.define_schema do |s|
+ assert s.kind_of?(ActiveResource::SchemaDefinition), "the 's' should be a schema definition or we're way off track..."
+ end
+ end
+ end
+
+ test "schema definition should store and return attribute set" do
+ assert_nothing_raised do
+ Person.define_schema do |s|
+ assert s.respond_to?(:attrs), "should return attributes in theory"
+ s.attribute :foo, :string
+ assert_equal({'foo' => 'string' }, s.attrs, "should return attributes in practice")
+ end
+ end
+ end
+
+ test "should be able to add attributes through define_schema" do
+ assert_nothing_raised do
+ Person.define_schema do |s|
+ assert s.attribute('foo', 'string'), "should take a simple attribute"
+ assert s.attrs.has_key?('foo'), "should have saved the attribute name"
+ assert_equal 'string', s.attrs['foo'], "should have saved the attribute type"
+ end
+ end
+ end
+
+ test "should convert symbol attributes to strings" do
+ assert_nothing_raised do
+ Person.define_schema do |s|
+ assert s.attribute(:foo, :integer), "should take a simple attribute as symbols"
+ assert s.attrs.has_key?('foo'), "should have saved the attribute name as a string"
+ assert_equal 'integer', s.attrs['foo'], "should have saved the attribute type as a string"
+ end
+ end
+ end
+
+ test "should be able to add all known attribute types" do
+ Person.define_schema do |s|
+ ActiveResource::SchemaDefinition::KNOWN_ATTRIBUTE_TYPES.each do |the_type|
+ assert_nothing_raised do
+ assert s.attribute('foo', the_type), "should take a simple attribute of type: #{the_type}"
+ assert s.attrs.has_key?('foo'), "should have saved the attribute name"
+ assert_equal the_type.to_s, s.attrs['foo'], "should have saved the attribute type of: #{the_type}"
+ end
+ end
+ end
+ end
+
+ test "attributes should not accept unknown values" do
+ bad_values = [ :oogle, :blob, 'thing']
+
+ Person.define_schema do |s|
+ bad_values.each do |bad_value|
+ assert_raises(ArgumentError,"should only accept a known attribute type, but accepted: #{bad_value.inspect}") do
+ s.attribute 'key', bad_value
+ end
+ assert !s.respond_to?(bad_value), "should only respond to a known attribute type, but accepted: #{bad_value.inspect}"
+ assert_raises(NoMethodError,"should only have methods for known attribute types, but accepted: #{bad_value.inspect}") do
+ s.send bad_value, 'key'
+ end
+ end
+ end
+ end
+
+
+ test "should accept attribute types as the type's name as the method" do
+ Person.define_schema do |s|
+ ActiveResource::SchemaDefinition::KNOWN_ATTRIBUTE_TYPES.each do |the_type|
+ assert s.respond_to?(the_type), "should recognise the attribute-type: #{the_type} as a method"
+ assert_nothing_raised("should take the method #{the_type} with the attribute name") do
+ s.send(the_type,'foo') # eg s.string :foo
+ end
+ assert s.attrs.has_key?('foo'), "should now have saved the attribute name"
+ assert_equal the_type.to_s, s.attrs['foo'], "should have saved the attribute type of: #{the_type}"
+ end
+ end
+ end
+
+ test "should accept multiple attribute names for an attribute method" do
+ names = ['foo','bar','baz']
+ Person.define_schema do |s|
+ assert_nothing_raised("should take strings with multiple attribute names as params") do
+ s.string( *names)
+ end
+ names.each do |the_name|
+ assert s.attrs.has_key?(the_name), "should now have saved the attribute name: #{the_name}"
+ assert_equal 'string', s.attrs[the_name], "should have saved the attribute as a string"
+ end
+ end
+ end
+
+ #####################################################
+ # What a schema does for us
+ ####
+
+ # respond_to?
+
+ test "should respond positively to attributes that are only in the schema" do
+ new_attr_name = :my_new_schema_attribute
+ new_attr_name_two = :another_new_schema_attribute
+ assert Person.schema.blank?, "sanity check - should have a blank class schema"
+
+ assert !Person.new.respond_do?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet"
+ assert !Person.new.respond_do?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet"
+
+ assert_nothing_raised do
+ Person.schema = {new_attr_name.to_s => 'string'}
+ Person.define_schema {|s| s.string new_attr_name_two }
+ end
+
+ assert Person.new.respond_to?(new_attr_name), "should respond to the attribute in a passed-in schema, but failed on: #{new_attr_name}"
+ assert Person.new.respond_to?(new_attr_name_two), "should respond to the attribute from the define_schema, but failed on: #{new_attr_name_two}"
+ end
+
+ test "should not care about ordering of schema definitions" do
+ new_attr_name = :my_new_schema_attribute
+ new_attr_name_two = :another_new_schema_attribute
+
+ assert Person.schema.blank?, "sanity check - should have a blank class schema"
+
+ assert !Person.new.respond_do?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet"
+ assert !Person.new.respond_do?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet"
+
+ assert_nothing_raised do
+ Person.define_schema {|s| s.string new_attr_name_two }
+ Person.schema = {new_attr_name.to_s => 'string'}
+ end
+
+ assert Person.new.respond_to?(new_attr_name), "should respond to the attribute in a passed-in schema, but failed on: #{new_attr_name}"
+ assert Person.new.respond_to?(new_attr_name_two), "should respond to the attribute from the define_schema, but failed on: #{new_attr_name_two}"
+ end
+
+ # method_missing effects
+
+ test "should not give method_missing for attribute only in schema" do
+ new_attr_name = :another_new_schema_attribute
+ new_attr_name_two = :another_new_schema_attribute
+
+ assert Person.schema.blank?, "sanity check - should have a blank class schema"
+
+ assert_raises(NoMethodError, "should not have found the attribute: #{new_attr_name} as a method") do
+ Person.new.send(new_attr_name)
+ end
+ assert_raises(NoMethodError, "should not have found the attribute: #{new_attr_name_two} as a method") do
+ Person.new.send(new_attr_name_two)
+ end
+
+ Person.schema = {new_attr_name.to_s => :float}
+ Person.define_schema {|s| s.string new_attr_name_two }
+
+ assert_nothing_raised do
+ Person.new.send(new_attr_name)
+ Person.new.send(new_attr_name_two)
+ end
+ end
+
+
+ ########
+ # Known attributes
+ #
+ # Attributes can be known to be attributes even if they aren't actually
+ # 'set' on a particular instance.
+ # This will only differ from 'attributes' if a schema has been set.
+
+ test "new model should have no known attributes" do
+ assert Person.known_attributes.blank?, "should have no known attributes"
+ assert Person.new.known_attributes.blank?, "should have no known attributes on a new instance"
+ end
+
+ test "setting schema should set known attributes on class and instance" do
+ new_schema = {'age' => 'integer', 'name' => 'string'}
+
+ assert_nothing_raised { Person.schema = new_schema }
+
+ assert_equal new_schema.keys, Person.known_attributes
+ assert_equal new_schema.keys, Person.new.known_attributes
+ end
+
+ test "known attributes on a fetched resource should return all the attributes of the instance" do
+ p = Person.find(1)
+ attrs = p.known_attributes
+
+ assert attrs.present?, "should have found some attributes!"
+
+ p.attributes.each do |the_attr, val|
+ assert attrs.include?(the_attr), "should have found attr: #{the_attr} in known attributes, but only had: #{attrs.inspect}"
+ end
+ end
+
+ test "with two instances, known attributes should match the attributes of the individual instances - even if they differ" do
+ matz = Person.find(1)
+ rick = Person.find(5)
+
+ m_attrs = matz.attributes.keys.sort
+ r_attrs = rick.attributes.keys.sort
+
+ assert_not_equal m_attrs, r_attrs, "should have different attributes on each model"
+
+ assert_not_equal matz.known_attributes, rick.known_attributes, "should have had different known attributes too"
+ end
+
+ test "setting schema then fetching should add schema attributes to the intance attributes" do
+ # an attribute in common with fetched instance and one that isn't
+ new_schema = {'name' => 'string', 'my_strange_attribute' => 'string'}
+
+ assert_nothing_raised { Person.schema = new_schema }
+
+ matz = Person.find(1)
+ known_attrs = matz.known_attributes
+
+ matz.attributes.keys.each do |the_attr|
+ assert known_attrs.include?(the_attr), "should have found instance attr: #{the_attr} in known attributes, but only had: #{known_attrs.inspect}"
+ end
+ new_schema.keys.each do |the_attr|
+ assert known_attrs.include?(the_attr), "should have found schema attr: #{the_attr} in known attributes, but only had: #{known_attrs.inspect}"
+ end
+ end
+
+
+end