couchrest_model/spec/unit/property_spec.rb

482 lines
16 KiB
Ruby

# encoding: utf-8
require 'spec_helper'
describe CouchRest::Model::Property do
before(:each) do
reset_test_db!
@card = Card.new(:first_name => "matt")
end
it "should be accessible from the object" do
@card.properties.should be_an_instance_of(Array)
@card.properties.map{|p| p.name}.should include("first_name")
end
it "should list object properties with values" do
@card.properties_with_values.should be_an_instance_of(Hash)
@card.properties_with_values["first_name"].should == "matt"
end
it "should let you access a property value (getter)" do
@card.first_name.should == "matt"
end
it "should let you set a property value (setter)" do
@card.last_name = "Aimonetti"
@card.last_name.should == "Aimonetti"
end
it "should not let you set a property value if it's read only" do
lambda{@card.read_only_value = "test"}.should raise_error
end
it "should let you use an alias for an attribute" do
@card.last_name = "Aimonetti"
@card.family_name.should == "Aimonetti"
@card.family_name.should == @card.last_name
end
it "should let you use an alias for a casted attribute" do
@card.cast_alias = Person.new(:name => ["Aimonetti"])
@card.cast_alias.name.should == ["Aimonetti"]
@card.calias.name.should == ["Aimonetti"]
card = Card.new(:first_name => "matt", :cast_alias => {:name => ["Aimonetti"]})
card.cast_alias.name.should == ["Aimonetti"]
card.calias.name.should == ["Aimonetti"]
end
it "should raise error if property name coincides with model type key" do
lambda { Cat.property(Cat.model_type_key) }.should raise_error(/already used/)
end
it "should not raise error if property name coincides with model type key on non-model" do
lambda { Person.property(Article.model_type_key) }.should_not raise_error
end
it "should be auto timestamped" do
@card.created_at.should be_nil
@card.updated_at.should be_nil
@card.save.should be_true
@card.created_at.should_not be_nil
@card.updated_at.should_not be_nil
end
describe "#as_couch_json" do
it "should provide a simple hash from model" do
@card.as_couch_json.class.should eql(Hash)
end
it "should remove properties from Hash if value is nil" do
@card.last_name = nil
@card.as_couch_json.keys.include?('last_name').should be_false
end
end
describe "#as_json" do
it "should provide a simple hash from model" do
@card.as_json.class.should eql(Hash)
end
it "should pass options to Active Support's as_json" do
@card.last_name = "Aimonetti"
@card.as_json(:only => 'last_name').should eql('last_name' => 'Aimonetti')
end
end
describe '#read_attribute' do
it "should let you use read_attribute method" do
@card.last_name = "Aimonetti"
@card.read_attribute(:last_name).should eql('Aimonetti')
@card.read_attribute('last_name').should eql('Aimonetti')
last_name_prop = @card.properties.find{|p| p.name == 'last_name'}
@card.read_attribute(last_name_prop).should eql('Aimonetti')
end
it 'should raise an error if the property does not exist' do
expect { @card.read_attribute(:this_property_should_not_exist) }.to raise_error(ArgumentError)
end
end
describe '#write_attribute' do
it "should let you use write_attribute method" do
@card.write_attribute(:last_name, 'Aimonetti 1')
@card.last_name.should eql('Aimonetti 1')
@card.write_attribute('last_name', 'Aimonetti 2')
@card.last_name.should eql('Aimonetti 2')
last_name_prop = @card.properties.find{|p| p.name == 'last_name'}
@card.write_attribute(last_name_prop, 'Aimonetti 3')
@card.last_name.should eql('Aimonetti 3')
end
it 'should raise an error if the property does not exist' do
expect { @card.write_attribute(:this_property_should_not_exist, 823) }.to raise_error(ArgumentError)
end
it "should let you use write_attribute on readonly properties" do
lambda {
@card.read_only_value = "foo"
}.should raise_error
@card.write_attribute(:read_only_value, "foo")
@card.read_only_value.should == 'foo'
end
it "should cast via write_attribute" do
@card.write_attribute(:cast_alias, {:name => ["Sam", "Lown"]})
@card.cast_alias.class.should eql(Person)
@card.cast_alias.name.last.should eql("Lown")
end
it "should not cast via write_attribute if property not casted" do
@card.write_attribute(:first_name, {:name => "Sam"})
@card.first_name.class.should eql(Hash)
@card.first_name[:name].should eql("Sam")
end
end
describe "mass updating attributes without property" do
describe "when mass_assign_any_attribute false" do
it "should not allow them to be set" do
@card.attributes = {:test => 'fooobar'}
@card['test'].should be_nil
end
end
describe "when mass_assign_any_attribute true" do
before(:each) do
# dup Card class so that no other tests are effected
card_class = Card.dup
card_class.class_eval do
mass_assign_any_attribute true
end
@card = card_class.new(:first_name => 'Sam')
end
it 'should allow them to be updated' do
@card.attributes = {:test => 'fooobar'}
@card['test'].should eql('fooobar')
end
end
end
describe "mass assignment protection" do
it "should not store protected attribute using mass assignment" do
cat_toy = CatToy.new(:name => "Zorro")
cat = Cat.create(:name => "Helena", :toys => [cat_toy], :favorite_toy => cat_toy, :number => 1)
cat.number.should be_nil
cat.number = 1
cat.save
cat.number.should == 1
end
it "should not store protected attribute when 'declare accessible poperties, assume all the rest are protected'" do
user = User.create(:name => "Marcos Tapajós", :admin => true)
user.admin.should be_nil
end
it "should not store protected attribute when 'declare protected properties, assume all the rest are accessible'" do
user = SpecialUser.create(:name => "Marcos Tapajós", :admin => true)
user.admin.should be_nil
end
end
describe "validation" do
before(:each) do
@invoice = Invoice.new(:client_name => "matt", :employee_name => "Chris", :location => "San Diego, CA")
end
it "should be able to be validated" do
@card.valid?.should == true
end
it "should let you validate the presence of an attribute" do
@card.first_name = nil
@card.should_not be_valid
@card.errors.should_not be_empty
@card.errors[:first_name].should == ["can't be blank"]
end
it "should let you look up errors for a field by a string name" do
@card.first_name = nil
@card.should_not be_valid
@card.errors['first_name'].should == ["can't be blank"]
end
it "should validate the presence of 2 attributes" do
@invoice.clear
@invoice.should_not be_valid
@invoice.errors.should_not be_empty
@invoice.errors[:client_name].should == ["can't be blank"]
@invoice.errors[:employee_name].should_not be_empty
end
it "should let you set an error message" do
@invoice.location = nil
@invoice.valid?
@invoice.errors[:location].should == ["Hey stupid!, you forgot the location"]
end
it "should validate before saving" do
@invoice.location = nil
@invoice.should_not be_valid
@invoice.save.should be_false
@invoice.should be_new
end
end
end
describe "properties of hash of casted models" do
it "should be able to assign a casted hash to a hash property" do
chain = KeyChain.new
keys = {"House" => "8==$", "Office" => "<>==U"}
chain.keys = keys
chain.keys = chain.keys
chain.keys.should == keys
end
end
describe "properties of array of casted models" do
before(:each) do
@course = Course.new :title => 'Test Course'
end
it "should allow attribute to be set from an array of objects" do
@course.questions = [Question.new(:q => "works?"), Question.new(:q => "Meaning of Life?")]
@course.questions.length.should eql(2)
end
it "should allow attribute to be set from an array of hashes" do
@course.questions = [{:q => "works?"}, {:q => "Meaning of Life?"}]
@course.questions.length.should eql(2)
@course.questions.last.q.should eql("Meaning of Life?")
@course.questions.last.class.should eql(Question) # typecasting
end
it "should allow attribute to be set from hash with ordered keys and objects" do
@course.questions = { '0' => Question.new(:q => "Test1"), '1' => Question.new(:q => 'Test2') }
@course.questions.length.should eql(2)
@course.questions.last.q.should eql('Test2')
@course.questions.last.class.should eql(Question)
end
it "should allow attribute to be set from hash with ordered keys and sub-hashes" do
@course.questions = { '10' => {:q => 'Test10'}, '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} }
@course.questions.length.should eql(3)
@course.questions.last.q.should eql('Test10')
@course.questions.last.class.should eql(Question)
end
it "should allow attribute to be set from hash with ordered keys and HashWithIndifferentAccess" do
# This is similar to what you'd find in an HTML POST parameters
hash = HashWithIndifferentAccess.new({ '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} })
@course.questions = hash
@course.questions.length.should eql(2)
@course.questions.last.q.should eql('Test2')
@course.questions.last.class.should eql(Question)
end
it "should raise an error if attempting to set single value for array type" do
lambda {
@course.questions = Question.new(:q => 'test1')
}.should raise_error(/Expecting an array/)
end
end
describe "a casted model retrieved from the database" do
before(:each) do
reset_test_db!
@cat = Cat.new(:name => 'Stimpy')
@cat.favorite_toy = CatToy.new(:name => 'Stinky')
@cat.toys << CatToy.new(:name => 'Feather')
@cat.toys << CatToy.new(:name => 'Mouse')
@cat.save
@cat = Cat.get(@cat.id)
end
describe "as a casted property" do
it "should already be casted_by its parent" do
@cat.favorite_toy.casted_by.should === @cat
end
end
describe "from a casted collection" do
it "should already be casted_by its parent" do
@cat.toys[0].casted_by.should === @cat
@cat.toys[1].casted_by.should === @cat
end
end
end
describe "nested models (not casted)" do
before(:each) do
reset_test_db!
@cat = ChildCat.new(:name => 'Stimpy')
@cat.mother = {:name => 'Stinky'}
@cat.siblings = [{:name => 'Feather'}, {:name => 'Felix'}]
@cat.save
@cat = ChildCat.get(@cat.id)
end
it "should correctly save single relation" do
@cat.mother.name.should eql('Stinky')
@cat.mother.casted_by.should eql(@cat)
end
it "should correctly save collection" do
@cat.siblings.first.name.should eql("Feather")
@cat.siblings.last.casted_by.should eql(@cat)
end
end
describe "Property Class" do
it "should provide name as string" do
property = CouchRest::Model::Property.new(:test, String)
property.name.should eql('test')
property.to_s.should eql('test')
end
it "should provide class from type" do
property = CouchRest::Model::Property.new(:test, String)
property.type_class.should eql(String)
end
it "should provide base class from type in array" do
property = CouchRest::Model::Property.new(:test, [String])
property.type_class.should eql(String)
end
it "should raise error if type as string requested" do
lambda {
property = CouchRest::Model::Property.new(:test, 'String')
}.should raise_error
end
it "should leave type nil and return class as nil also" do
property = CouchRest::Model::Property.new(:test, nil)
property.type.should be_nil
property.type_class.should be_nil
end
it "should convert empty type array to [Object]" do
property = CouchRest::Model::Property.new(:test, [])
property.type_class.should eql(Object)
end
it "should set init method option or leave as 'new'" do
# (bad example! Time already typecast)
property = CouchRest::Model::Property.new(:test, Time)
property.init_method.should eql('new')
property = CouchRest::Model::Property.new(:test, Time, :init_method => 'parse')
property.init_method.should eql('parse')
end
describe "#build" do
it "should allow instantiation of new object" do
property = CouchRest::Model::Property.new(:test, Date)
obj = property.build(2011, 05, 21)
obj.should eql(Date.new(2011, 05, 21))
end
it "should use init_method if provided" do
property = CouchRest::Model::Property.new(:test, Date, :init_method => 'parse')
obj = property.build("2011-05-21")
obj.should eql(Date.new(2011, 05, 21))
end
it "should use init_method Proc if provided" do
property = CouchRest::Model::Property.new(:test, Date, :init_method => Proc.new{|v| Date.parse(v)})
obj = property.build("2011-05-21")
obj.should eql(Date.new(2011, 05, 21))
end
it "should raise error if no class" do
property = CouchRest::Model::Property.new(:test)
lambda { property.build }.should raise_error(StandardError, /Cannot build/)
end
end
## Property Casting method. More thoroughly tested in typecast_spec.
describe "casting" do
it "should cast a value" do
property = CouchRest::Model::Property.new(:test, Date)
parent = mock("FooObject")
property.cast(parent, "2010-06-16").should eql(Date.new(2010, 6, 16))
property.cast_value(parent, "2010-06-16").should eql(Date.new(2010, 6, 16))
end
it "should cast an array of values" do
property = CouchRest::Model::Property.new(:test, [Date])
parent = mock("FooObject")
property.cast(parent, ["2010-06-01", "2010-06-02"]).should eql([Date.new(2010, 6, 1), Date.new(2010, 6, 2)])
end
it "should set a CastedArray on array of Objects" do
property = CouchRest::Model::Property.new(:test, [Object])
parent = mock("FooObject")
property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should eql(CouchRest::Model::CastedArray)
end
it "should set a CastedArray on array of Strings" do
property = CouchRest::Model::Property.new(:test, [String])
parent = mock("FooObject")
property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should eql(CouchRest::Model::CastedArray)
end
it "should allow instantion of model via CastedArray#build" do
property = CouchRest::Model::Property.new(:dates, [Date])
parent = Article.new
ary = property.cast(parent, [])
obj = ary.build(2011, 05, 21)
ary.length.should eql(1)
ary.first.should eql(Date.new(2011, 05, 21))
obj = ary.build(2011, 05, 22)
ary.length.should eql(2)
ary.last.should eql(Date.new(2011, 05, 22))
end
it "should cast an object that provides an array" do
prop = Class.new do
attr_accessor :ary
def initialize(val); self.ary = val; end
def as_json; ary; end
end
property = CouchRest::Model::Property.new(:test, prop)
parent = mock("FooClass")
cast = property.cast(parent, [1, 2])
cast.ary.should eql([1, 2])
end
it "should set parent as casted_by object in CastedArray" do
property = CouchRest::Model::Property.new(:test, [Object])
parent = mock("FooObject")
property.cast(parent, ["2010-06-01", "2010-06-02"]).casted_by.should eql(parent)
end
it "should set casted_by on new value" do
property = CouchRest::Model::Property.new(:test, CatToy)
parent = mock("CatObject")
cast = property.cast(parent, {:name => 'catnip'})
cast.casted_by.should eql(parent)
end
end
end