spec/unit/properties_spec.rb in couchrest_model-2.1.0.rc1 vs spec/unit/properties_spec.rb in couchrest_model-2.2.0.beta1
- old
+ new
@@ -1,37 +1,487 @@
# encoding: utf-8
require "spec_helper"
describe CouchRest::Model::Properties do
- before(:each) do
- @obj = WithDefaultValues.new
+ context "general functionality" do
+
+ before(:each) do
+ reset_test_db!
+ @card = Card.new(:first_name => "matt")
+ end
+
+ it "should be accessible from the object" do
+ expect(@card.properties).to be_an_instance_of(Array)
+ expect(@card.properties.map{|p| p.name}).to include("first_name")
+ end
+
+ it "should list object properties with values" do
+ expect(@card.attributes).to be_an_instance_of(Hash)
+ expect(@card.read_attributes).to be_an_instance_of(Hash)
+ expect(@card.attributes["first_name"]).to eql("matt")
+ end
+
+ it "should let you access a property value (getter)" do
+ expect(@card.first_name).to eql("matt")
+ end
+
+ it "should let you set a property value (setter)" do
+ @card.last_name = "Aimonetti"
+ expect(@card.last_name).to eq("Aimonetti")
+ end
+
+ it "should not let you set a property value if it's read only" do
+ expect{@card.read_only_value = "test"}.to raise_error(NoMethodError)
+ end
+
+ it "should let you use an alias for an attribute" do
+ @card.last_name = "Aimonetti"
+ expect(@card.family_name).to eq("Aimonetti")
+ expect(@card.family_name).to eq(@card.last_name)
+ end
+
+ it "should let you use an alias for a casted attribute" do
+ @card.cast_alias = Person.new(:name => ["Aimonetti"])
+ expect(@card.cast_alias.name).to eq(["Aimonetti"])
+ expect(@card.calias.name).to eq(["Aimonetti"])
+ card = Card.new(:first_name => "matt", :cast_alias => {:name => ["Aimonetti"]})
+ expect(card.cast_alias.name).to eq(["Aimonetti"])
+ expect(card.calias.name).to eq(["Aimonetti"])
+ end
+
+ it "should raise error if property name coincides with model type key" do
+ expect { Cat.property(Cat.model_type_key) }.to raise_error(/already used/)
+ end
+
+ it "should not raise error if property name coincides with model type key on non-model" do
+ expect { Person.property(Article.model_type_key) }.not_to raise_error
+ end
+
+ it "should be auto timestamped" do
+ expect(@card.created_at).to be_nil
+ expect(@card.updated_at).to be_nil
+ expect(@card.save).to be_truthy
+ expect(@card.created_at).not_to be_nil
+ expect(@card.updated_at).not_to be_nil
+ end
+
+ describe "#as_couch_json" do
+
+ it "should provide a simple hash from model" do
+ expect(@card.as_couch_json.class).to eql(Hash)
+ end
+
+ it "should remove properties from Hash if value is nil" do
+ @card.last_name = nil
+ expect(@card.as_couch_json.keys.include?('last_name')).to be_falsey
+ end
+
+ end
+
+ describe "#as_json" do
+
+ it "should provide a simple hash from model" do
+ expect(@card.as_json.class).to eql(Hash)
+ end
+
+ it "should pass options to Active Support's as_json" do
+ @card.last_name = "Aimonetti"
+ expect(@card.as_json(:only => 'last_name')).to eql('last_name' => 'Aimonetti')
+ end
+
+ end
+
+ describe '#read_attribute' do
+ it "should let you use read_attribute method" do
+ @card.last_name = "Aimonetti"
+ expect(@card.read_attribute(:last_name)).to eql('Aimonetti')
+ expect(@card.read_attribute('last_name')).to eql('Aimonetti')
+ last_name_prop = @card.properties.find{|p| p.name == 'last_name'}
+ expect(@card.read_attribute(last_name_prop)).to 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')
+ expect(@card.last_name).to eql('Aimonetti 1')
+ @card.write_attribute('last_name', 'Aimonetti 2')
+ expect(@card.last_name).to eql('Aimonetti 2')
+ last_name_prop = @card.properties.find{|p| p.name == 'last_name'}
+ @card.write_attribute(last_name_prop, 'Aimonetti 3')
+ expect(@card.last_name).to 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
+ expect {
+ @card.read_only_value = "foo"
+ }.to raise_error(NoMethodError)
+ @card.write_attribute(:read_only_value, "foo")
+ expect(@card.read_only_value).to eq('foo')
+ end
+
+ it "should cast via write_attribute" do
+ @card.write_attribute(:cast_alias, {:name => ["Sam", "Lown"]})
+ expect(@card.cast_alias.class).to eql(Person)
+ expect(@card.cast_alias.name.last).to eql("Lown")
+ end
+
+ it "should not cast via write_attribute if property not casted" do
+ @card.write_attribute(:first_name, {:name => "Sam"})
+ expect(@card.first_name.class).to eql(Hash)
+ expect(@card.first_name[:name]).to eql("Sam")
+ end
+ end
+
+ # These tests are light as functionality is covered elsewhere
+ describe "#write_attributes" do
+
+ let :obj do
+ Card.new(:first_name => "matt")
+ end
+
+ it "should update attributes" do
+ obj.write_attributes( :last_name => 'foo' )
+ expect(obj.last_name).to eql('foo')
+ end
+
+ it "should not update protected attributes" do
+ obj.write_attributes(:bg_color => '#000000')
+ expect(obj.bg_color).to_not eql('#000000')
+ end
+
+ it "should not update read_only attributes" do
+ obj.write_attributes(:read_only_value => 'bar')
+ expect(obj.read_only_value).to_not eql('bar')
+ end
+
+ it "should have an #attributes= alias" do
+ expect {
+ obj.attributes = { :last_name => 'foo' }
+ }.to_not raise_error
+ end
+
+ end
+
+ # These tests are light as functionality is covered elsewhere
+ describe "#write_all_attributes" do
+
+ let :obj do
+ Card.new(:first_name => "matt")
+ end
+
+ it "should set regular properties" do
+ obj.write_all_attributes(:last_name => 'foo')
+ expect(obj.last_name).to eql('foo')
+ end
+
+ it "should set read-only and protected properties" do
+ obj.write_all_attributes(
+ :read_only_value => 'foo',
+ :bg_color => '#111111'
+ )
+ expect(obj.read_only_value).to eql('foo')
+ expect(obj.bg_color).to eql('#111111')
+ 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'}
+ expect(@card['test']).to be_nil
+ end
+
+ it 'should not allow them to be updated with update_attributes' do
+ @card.update_attributes(:test => 'fooobar')
+ expect(@card['test']).to be_nil
+ end
+
+ it 'should not have a different revision after update_attributes' do
+ @card.save
+ rev = @card.rev
+ @card.update_attributes(:test => 'fooobar')
+ expect(@card.rev).to eql(rev)
+ end
+
+ it 'should not have a different revision after save' do
+ @card.save
+ rev = @card.rev
+ @card.attributes = {:test => 'fooobar'}
+ @card.save
+ expect(@card.rev).to eql(rev)
+ 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 = {:testing => 'fooobar'}
+ expect(@card['testing']).to eql('fooobar')
+ end
+
+ it 'should allow them to be updated with update_attributes' do
+ @card.update_attributes(:testing => 'fooobar')
+ expect(@card['testing']).to eql('fooobar')
+ end
+
+ it 'should have a different revision after update_attributes' do
+ @card.save
+ rev = @card.rev
+ @card.update_attributes(:testing => 'fooobar')
+ expect(@card.rev).not_to eql(rev)
+ end
+
+ it 'should have a different revision after save' do
+ @card.save
+ rev = @card.rev
+ @card.attributes = {:testing => 'fooobar'}
+ @card.save
+ expect(@card.rev).not_to eql(rev)
+ 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)
+ expect(cat.number).to be_nil
+ cat.number = 1
+ cat.save
+ expect(cat.number).to eq(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)
+ expect(user.admin).to 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)
+ expect(user.admin).to 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
+ expect(@card.valid?).to eq(true)
+ end
+
+ it "should let you validate the presence of an attribute" do
+ @card.first_name = nil
+ expect(@card).not_to be_valid
+ expect(@card.errors).not_to be_empty
+ expect(@card.errors[:first_name]).to eq(["can't be blank"])
+ end
+
+ it "should let you look up errors for a field by a string name" do
+ @card.first_name = nil
+ expect(@card).not_to be_valid
+ expect(@card.errors['first_name']).to eq(["can't be blank"])
+ end
+
+ it "should validate the presence of 2 attributes" do
+ @invoice.clear
+ expect(@invoice).not_to be_valid
+ expect(@invoice.errors).not_to be_empty
+ expect(@invoice.errors[:client_name]).to eq(["can't be blank"])
+ expect(@invoice.errors[:employee_name]).not_to be_empty
+ end
+
+ it "should let you set an error message" do
+ @invoice.location = nil
+ @invoice.valid?
+ expect(@invoice.errors[:location]).to eq(["Hey stupid!, you forgot the location"])
+ end
+
+ it "should validate before saving" do
+ @invoice.location = nil
+ expect(@invoice).not_to be_valid
+ expect(@invoice.save).to be_falsey
+ expect(@invoice).to be_new
+ end
+ end
+
end
+
+ context "casting" do
+
+ 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
+ expect(chain.keys).to eq(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?")]
+ expect(@course.questions.length).to eql(2)
+ end
+
+ it "should allow attribute to be set from an array of hashes" do
+ @course.questions = [{:q => "works?"}, {:q => "Meaning of Life?"}]
+ expect(@course.questions.length).to eql(2)
+ expect(@course.questions.last.q).to eql("Meaning of Life?")
+ expect(@course.questions.last.class).to 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') }
+ expect(@course.questions.length).to eql(2)
+ expect(@course.questions.last.q).to eql('Test2')
+ expect(@course.questions.last.class).to 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'} }
+ expect(@course.questions.length).to eql(3)
+ expect(@course.questions.last.q).to eql('Test10')
+ expect(@course.questions.last.class).to 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
+ expect(@course.questions.length).to eql(2)
+ expect(@course.questions.last.q).to eql('Test2')
+ expect(@course.questions.last.class).to eql(Question)
+ end
+
+ it "should allow attribute to be set from Hash subclass with ordered keys" do
+ ourhash = Class.new(HashWithIndifferentAccess)
+ hash = ourhash.new({ '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} })
+ @course.questions = hash
+ expect(@course.questions.length).to eql(2)
+ expect(@course.questions.last.q).to eql('Test2')
+ expect(@course.questions.last.class).to eql(Question)
+ end
+
+ it "should raise an error if attempting to set single value for array type" do
+ expect {
+ @course.questions = Question.new(:q => 'test1')
+ }.to 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
+ expect(@cat.favorite_toy.casted_by).to be === @cat
+ end
+ end
+
+ describe "from a casted collection" do
+ it "should already be casted_by its parent" do
+ expect(@cat.toys[0].casted_by).to be === @cat
+ expect(@cat.toys[1].casted_by).to be === @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
+ expect(@cat.mother.name).to eql('Stinky')
+ expect(@cat.mother.casted_by).to eql(@cat)
+ end
+
+ it "should correctly save collection" do
+ expect(@cat.siblings.first.name).to eql("Feather")
+ expect(@cat.siblings.last.casted_by).to eql(@cat)
+ end
+ end
+
+ end
+
+ context "multipart attributes" do
+
+ before(:each) do
+ @obj = WithDefaultValues.new
+ end
- describe "multipart attributes" do
context "with valid params" do
it "should parse a legal date" do
valid_date_params = { "exec_date(1i)"=>"2011",
"exec_date(2i)"=>"10",
"exec_date(3i)"=>"18"}
@obj = WithDateAndTime.new valid_date_params
- @obj.exec_date.should_not be_nil
- @obj.exec_date.should be_kind_of(Date)
- @obj.exec_date.should == Date.new(2011, 10 ,18)
+ expect(@obj.exec_date).not_to be_nil
+ expect(@obj.exec_date).to be_kind_of(Date)
+ expect(@obj.exec_date).to eq(Date.new(2011, 10 ,18))
end
it "should parse a legal time" do
valid_time_params = { "exec_time(1i)"=>"2011",
"exec_time(2i)"=>"10",
"exec_time(3i)"=>"18",
"exec_time(4i)"=>"15",
"exec_time(5i)"=>"15",
"exec_time(6i)"=>"15",}
@obj = WithDateAndTime.new valid_time_params
- @obj.exec_time.should_not be_nil
- @obj.exec_time.should be_kind_of(Time)
- @obj.exec_time.should == Time.utc(2011, 10 ,18, 15, 15, 15)
+ expect(@obj.exec_time).not_to be_nil
+ expect(@obj.exec_time).to be_kind_of(Time)
+ expect(@obj.exec_time).to eq(Time.utc(2011, 10 ,18, 15, 15, 15))
end
end
context "with invalid params" do
before(:each) do
@@ -39,19 +489,19 @@
"exec_date(2i)"=>"foo",
"exec_date(3i)"=>"18"}
end
it "should still create a model if there are invalid attributes" do
@obj = WithDateAndTime.new @invalid_date_params
- @obj.should_not be_nil
- @obj.should be_kind_of(WithDateAndTime)
+ expect(@obj).not_to be_nil
+ expect(@obj).to be_kind_of(WithDateAndTime)
end
it "should not crash because of an empty value" do
@invalid_date_params["exec_date(2i)"] = ""
@obj = WithDateAndTime.new @invalid_date_params
- @obj.should_not be_nil
- @obj.exec_date.should_not be_kind_of(Date)
- @obj.should be_kind_of(WithDateAndTime)
+ expect(@obj).not_to be_nil
+ expect(@obj.exec_date).not_to be_kind_of(Date)
+ expect(@obj).to be_kind_of(WithDateAndTime)
end
end
# Specific use case for Ruby 2.0.0
context "with brackets in value" do
@@ -62,12 +512,12 @@
end
klass
end
it "should be accepted" do
- lambda {
+ expect {
@obj = klass.new(:name => 'test (object)')
- }.should_not raise_error
+ }.not_to raise_error
end
end
end