require File.expand_path(File.dirname(__FILE__) + '/spec_helper') # Raised when unknown attributes are supplied via mass assignment. #class UnknownAttributeError < NoMethodError #end describe DattsRight do before do reset_database @page = Page.create end describe ".add_dynamic_attribute(attr_key, object_type)" do it "should be aliased by add_datt" do Page.instance_method(:add_dynamic_attribute).should == Page.instance_method(:add_datt) end it "should add a dynamic attribute" do @page.add_dynamic_attribute(:rocks, "string") @page.dynamic_attribute_details(:rocks).value.should be_nil @page.add_dynamic_attribute(:rock, "string", "123") @page.read_datt(:rock).should == "123" @page.dynamic_attribute_details(:rock).value.should == "123" end it "should ignore when trying to add same attribute" do @page.add_dynamic_attribute(:rocks, "string") @page.add_dynamic_attribute(:rocks, "integer") @page.dynamic_attribute_details(:rocks).object_type.should == "string" end it "should return false if the method that is being added already exists" do @page.add_dynamic_attribute(:name, "text").should be_false end it "should not make any changes to the original attribute" do @page.update_attribute(:name, "juno") @page.add_dynamic_attribute(:name, "text") @page.name.should == "juno" end end describe ".remove_dynamic_attribute(attr_key)" do it "should be aliased by remove_datt" do Page.instance_method(:remove_dynamic_attribute).should == Page.instance_method(:remove_datt) end it "should remove the attribute completely" do @page.add_dynamic_attribute(:rocks, "string") @page.remove_dynamic_attribute(:rocks) lambda { @page.rocks }.should raise_error(NoMethodError) end it "should not explode if the attribute being removed isn't there" do @page.add_dynamic_attribute(:rocks, "string") @page.remove_dynamic_attribute(:rocks) lambda { @page.remove_dynamic_attribute(:rocks) }.should_not raise_error(NoMethodError) end end it "should not allow arbitrary creation of attributes" do lambda { @page.some_field = "woot" }.should raise_error(NoMethodError) end it "should allow saving of integers" do @page.add_dynamic_attribute(:price, "integer") @page.write_dynamic_attribute :price, 300 @page.save @page = Page.last @page.read_dynamic_attribute(:price).should == 300 end it "should allow saving of floats" do @page.add_dynamic_attribute(:price, "float") @page.write_dynamic_attribute :price, 300.0 @page.save @page = Page.last @page.read_dynamic_attribute(:price).should == 300.0 end it "should allow saving of true" do @page.add_dynamic_attribute(:real, "boolean") @page.write_dynamic_attribute :real, true @page.save @page = Page.last @page.read_dynamic_attribute(:real).should be_true end it "should allow saving of false" do @page.add_dynamic_attribute(:real, "boolean") @page.write_dynamic_attribute :real, false @page.save @page = Page.last @page.read_dynamic_attribute(:real).should be_false end it "should allow saving of strings" do @page.add_dynamic_attribute(:real, "string") @page.write_dynamic_attribute :real, "its real alright" @page.save @page = Page.last @page.read_dynamic_attribute(:real).should == "its real alright" end it "should allow saving of text" do @page.add_dynamic_attribute(:real, "text") @page.write_dynamic_attribute :real, "t"*256 @page.save @page = Page.last @page.read_dynamic_attribute(:real).should == "t"*256 end it "should cache the attribute" do @page.add_dynamic_attribute(:price, "integer") @page.write_dynamic_attribute :price, 200 @page.read_dynamic_attribute(:price).should == 200 end it "should not save the attribute if save on the attributable object isn't called" do @page.add_dynamic_attribute(:price, "integer") @page.write_dynamic_attribute :price, 200 @page = Page.last @page.read_dynamic_attribute(:price).should be_nil end it "should allow overwriting of values" do @page.add_dynamic_attribute(:price, "integer") @page.write_dynamic_attribute :price, 200 @page.save @page = Page.last @page.write_dynamic_attribute :price, 300 @page.save @page = Page.last @page.read_dynamic_attribute(:price).should == 300 end it "should allow nullifying of attributes, but keeping the fields there" do @page.add_dynamic_attribute(:farce, "string") @page.write_dynamic_attribute :farce, "Nothing here my friend" @page.save @page.write_dynamic_attribute :farce, nil @page.dynamic_attribute?(:farce).should be_true end describe ".read_dynamic_attribute" do it "should be aliased by read_datt" do Page.instance_method(:read_dynamic_attribute).should == Page.instance_method(:read_datt) end end describe "on dynamic find_by_dynamic_attribute methods" do it "should be aliased by find_by_datt" do @page.add_dynamic_attribute(:price, "integer") @page.write_dynamic_attribute :price, 400 @page.save Page.find_by_datt_price(400).should == Page.find_by_dynamic_attribute_price(400) end it "should be able to return single records" do @page.add_dynamic_attribute(:price, "integer") @page.write_dynamic_attribute :price, 400 @page.save @page_2 = Page.create @page_2.add_dynamic_attribute(:price, "integer") @page_2.write_dynamic_attribute :price, 500 @page_2.save @results = Page.find_by_dynamic_attribute_price(400) @results.should == @page @results.should_not == @page_2 end it "should be able to return all matching records" do @page.add_dynamic_attribute(:price, "integer") @page.write_dynamic_attribute :price, 400 @page.save @page_2 = Page.create @page_2.add_dynamic_attribute(:price, "integer") @page_2.write_dynamic_attribute :price, 500 @page_2.save @page_3 = Page.create @page_3.add_dynamic_attribute(:price, "integer") @page_3.write_dynamic_attribute :price, 400 @page_3.save @results = Page.find_all_by_dynamic_attribute_price(400) @results.should include(@page) @results.should_not include(@page_2) @results.should include(@page_3) end it "should still allow raising of NoMethodError if the original attribute does not exist" do lambda { Page.find_by_faker("hi") }.should raise_error(NoMethodError) end it "should be able to return the last record" do @page.add_dynamic_attribute(:price, "integer") @page.write_dynamic_attribute :price, 400 @page.save @page_2 = Page.create @page_2.add_dynamic_attribute(:price, "integer") @page_2.write_dynamic_attribute :price, 400 @page_2.save Page.find_last_by_dynamic_attribute_price(400) == @page_2 end it "should allow normal attributes to work" do @page.name = "Roland" @page.save Page.find_by_name("Roland").should == @page end it "should allow chaining" do @page.add_dynamic_attribute(:price, "integer") @page.name = "fixed" @page.write_dynamic_attribute :price, 400 @page.save @page_2 = Page.create @page_2.add_dynamic_attribute(:price, "integer") @page_2.write_dynamic_attribute :price, 500 @page_2.save @pages = Page.where(:name => "fixed").find_by_dynamic_attribute_price(400) @pages.should == @page @pages.should_not == @page_2 end end it "should allow multiple attributes" do @page.add_dynamic_attribute(:price, "integer") @page.write_dynamic_attribute :price, 400 @page.add_dynamic_attribute(:farce, "string") @page.write_dynamic_attribute :farce, "hitt" @page.save @page_2 = Page.create @page_2.add_dynamic_attribute(:price, "integer") @page_2.write_dynamic_attribute :price, 400 @page_2.add_dynamic_attribute(:farce, "string") @page_2.write_dynamic_attribute :farce, "hi" @page_2.save @pages = Page.find_by_dynamic_attribute_price_and_farce(400, "hi") @pages.should == @page_2 @pages.should_not == @page end describe "when the value being assigned is not of the same type (with exceptions)" do before do @page.add_dynamic_attribute(:price, "integer") end it "should not save the value" do @page.write_dynamic_attribute :price, 200 @page.save @page.write_dynamic_attribute :price, "hi there" @page.save Page.last.read_dynamic_attribute(:price).should == 200 end end it "should allow mass assignment" do @page.add_dynamic_attribute(:price, "integer") @page.add_dynamic_attribute(:farce, "string") @page.update_dynamic_attributes(:price => 200, :farce => "hi") @page.read_dynamic_attribute(:price).should == 200 @page.read_dynamic_attribute(:farce).should == "hi" @page.update_dynamic_attributes!(:price => 300, :farce => "not farce!") @page.read_dynamic_attribute(:price).should == 300 @page.read_dynamic_attribute(:farce).should == "not farce!" end it "should allow use of update_dynamic_attribute" do @page.add_dynamic_attribute(:price, "integer") @page.update_dynamic_attribute(:price, 200) @page.read_dynamic_attribute(:price).should == 200 end describe "order_by_dynamic_attribute" do it "should be aliased by order_by_datt" do @page.add_dynamic_attribute(:price, "integer") @page.write_dynamic_attribute :price, 2 @page.save Page.order_by_dynamic_attribute("price", "integer").should == Page.order_by_datt("price", "integer") end it "should default to asc" do @page.add_dynamic_attribute(:price, "integer") @page.write_dynamic_attribute :price, 2 @page.save @page_2 = Page.create @page_2.add_dynamic_attribute(:price, "integer") @page_2.write_dynamic_attribute :price, 1 @page_2.save @page_3 = Page.create @page_3.add_dynamic_attribute(:price, "float") @page_3.write_dynamic_attribute :price, 3.0 @page_3.save @page_4 = Page.create @page_4.add_dynamic_attribute(:price, "float") @page_4.write_dynamic_attribute :price, 3.5 @page_4.save # What if different records have price but they are of different object_type? # page_1.read_dynamic_attribute(:price) is an "integer" and it's saved in "integer_value" column # page_2.read_dynamic_attribute(:price) is a "float" and it's saved in "float_value" # # How would you work on Page.order_by_dynamic_attribute("price")? # # Answer 1: we could require passing of the object_type in the order method # This way, we know which column to look at => order_by_dynamic_attribute("price", "integer") Page.order_by_dynamic_attribute("price", "integer").should == [@page_2, @page] Page.order_by_dynamic_attribute("price DESC", "float").should == [@page_4, @page_3] end end describe ".write_dynamic_attribute" do it "should be aliased by write_datt" do Page.instance_method(:write_dynamic_attribute).should == Page.instance_method(:write_datt) end end describe ".with_dynamic_attribute_key" do it "should be aliased by .with_datt_key" do @page.add_dynamic_attribute(:price, "float") Page.with_dynamic_attribute_key(:price).should == Page.with_datt_key(:price) end end describe ".with_dynamic_attribute_type" do it "should be aliased by .with_datt_type" do @page.add_dynamic_attribute(:price, "float") Page.with_dynamic_attribute_type("float").should == Page.with_datt_type("float") end end describe "where_dynamic_attribute" do it "should be aliased by where_datt" do @page.add_dynamic_attribute(:price, "float") @page.write_dynamic_attribute :price, 200.0 Page.where_dynamic_attribute(:price => 200.0).should == Page.where_datt(:price => 200.0) end it "should automatically join in the dynamic_attributes table" do @page.add_dynamic_attribute(:price, "float") @page.write_dynamic_attribute :price, 200.0 @page.add_dynamic_attribute(:farce, "boolean") @page.write_dynamic_attribute :farce, true @page.save @page_2 = Page.create @page_2.add_dynamic_attribute(:price, "integer") @page_2.add_dynamic_attribute(:farce, "boolean") @page_2.write_dynamic_attribute :farce, true @page_2.write_dynamic_attribute :price, 1 @page_2.save @result = Page.where_dynamic_attribute(:price => 200.0, :farce => true) @result.should include(@page) @result.should_not include(@page_2) #@result = Page.where_dynamic_attribute("price < :price", :price => 199.0) #@result.should_not include(@page) #@result.should include(@page_2) end it "should allow chaining with where scopes" do @page.add_dynamic_attribute(:price, "integer") @page.name = "aardvark" @page.write_dynamic_attribute :price, 200 @page.save @page_2 = Page.create @page_2.add_dynamic_attribute(:price, "integer") @page_2.write_dynamic_attribute :price, 200 @page_2.save @results = Page.where_dynamic_attribute(:price => 200).where(:name => "aardvark") @results.should include(@page) @results.should_not include(@page_2) end it "should return an empty array if we're looking for a field that doesn't exist" do Page.where_dynamic_attribute(:bogus_field => "none").should be_empty end end describe ".dynamic_attribute_details(key)" do it "should be aliased by datt_details" do Page.instance_method(:dynamic_attribute_details).should == Page.instance_method(:datt_details) end it "should allow access to the dynamic_attribute" do @page.add_dynamic_attribute(:price, "integer") @page.dynamic_attribute_details(:price).class.should == DynamicAttribute end end describe "when loading from database" do it "should load the dynamic attributes for use" do @page.add_datt(:body, "text") @page.write_datt(:body, "t"*300) @page.save @page = Page.last @page.read_datt(:body).should == "t"*300 @page.dynamic_attribute_details(:body).value.should == @page.read_datt(:body) end end it "should be able to write short strings into text variables" do @page.add_datt(:body, "text") @page.write_datt(:body, "short") @page.save @page.read_datt(:body).should == "short" end it "should write like a normal attribute" do @page.add_datt(:body, "text") @page.body = "dude" @page.save @page.read_datt(:body).should == "dude" end it "should read like a normal attribute" do @page.add_datt(:body, "text") @page.body = "dude" @page.save @page.body.should == "dude" end describe ".attributes=" do it "should accept a mix of normal and dynamic attributes" do @page.add_dynamic_attribute(:price, "integer", 200) @page.attributes = {:name => "fark", :price => 300} @page.name.should == "fark" @page.read_datt(:price).should == 300 end it "should raise UnknownAttributeError if the attribute doesn't exist" do lambda { @page.attributes = {:name => "fark", :body => "lark"} }.should raise_error(ActiveRecord::UnknownAttributeError, "unknown attribute: body") end end describe "on the definition" do it "should not be created if it is not defined" do Page.create.dynamic_attribute_definition.should be_nil end end end