require "spec_helper" describe Mongoid::Fields do before(:all) do Mongoid.use_activesupport_time_zone = false end describe "#\{field}_translations" do let(:product) do Product.new end context "when the field is localized" do context "when translations exist" do before do product.description = "test" ::I18n.locale = :de product.description = "The best" end after do ::I18n.locale = :en end let(:translations) do product.description_translations end it "returns all the translations" do expect(translations).to eq( { "en" => "test", "de" => "The best" } ) end it "returns translations as a HashWithIndifferentAccess" do expect(translations[:en]).to eq("test") end end context "when translations do not exist" do context "when no default is provided" do it "returns an empty hash" do expect(product.description_translations).to be_empty end end context "when a default is provided" do it "returns the translations with the default" do expect(product.name_translations).to eq( { "en" => "no translation" } ) end end end it "should have alias method #\{field}_t" do product.method(:name_t).should eq product.method(:name_translations) end end context "when the field is not localized" do it "does not respond to the method" do expect(product).to_not respond_to(:price_translations) end it "does not respond to the alias method" do product.should_not respond_to(:price_t) end end end describe "#\{field}_translations=" do let(:product) do Product.new end let(:dictionary) do Dictionary.new end context "when the field is localized" do context "when the field does not require mongoizations" do let(:translations) do { "en" => "test", "de" => "testing" } end before do product.description_translations = translations end it "sets the raw values of the translations" do expect(product.description_translations).to eq(translations) end context "when saving the new translations" do before do product.save end it "persists the changes" do expect(product.reload.description_translations).to eq(translations) end context "when updating the translations" do before do product.description_translations = { "en" => "overwritten" } product.save end it "persists the changes" do expect(product.reload.description_translations).to eq( { "en" => "overwritten" } ) end end end end context "when the field requires mongoization" do let(:translations) do { "en" => 1, "de" => 2 } end before do dictionary.description_translations = translations end it "sets the mongoized values of the translations" do expect(dictionary.description_translations).to eq( { "en" => "1", "de" => "2" } ) end context "when saving the new translations" do before do dictionary.save end it "persists the changes" do expect(dictionary.reload.description_translations).to eq( { "en" => "1", "de" => "2" } ) end context "when updating the translations" do before do dictionary.description_translations = { "en" => "overwritten" } dictionary.save end it "persists the changes" do expect(dictionary.reload.description_translations).to eq( { "en" => "overwritten" } ) end end end end it "should have alias method #\{field}_t=" do product.method(:name_t=).should eq product.method(:name_translations=) end end context "when the field is not localized" do it "does not respond to the method" do expect(product).to_not respond_to(:price_translations=) end it "does not respond to the alias method" do product.should_not respond_to(:price_t=) end end end describe "#aliased_fields" do let(:person) do Person.new end context "when the document is subclassed" do it "does not include the child aliases" do expect(person.aliased_fields.keys).to_not include("spec") end end end describe "#attribute_names" do context "when the document is a parent class" do let(:shape) do Shape.new end it "includes the _id field" do expect(shape.attribute_names).to include("_id") end it "includes the _type field" do expect(shape.attribute_names).to include("_type") end it "includes its own fields" do expect(shape.attribute_names).to include("x") end it "does not return subclass fields" do expect(shape.attribute_names).to_not include("radius") end end context "when the document is a subclass" do let(:circle) do Circle.new end it "includes the _id field" do expect(circle.attribute_names).to include("_id") end it "includes the _type field" do expect(circle.attribute_names).to include("_type") end it "includes the first parent field" do expect(circle.attribute_names).to include("x") end it "includes the second parent field" do expect(circle.attribute_names).to include("y") end it "includes the child fields" do expect(circle.attribute_names).to include("radius") end end end describe ".attribute_names" do context "when the class is a parent" do it "includes the _id field" do expect(Shape.attribute_names).to include("_id") end it "includes the _type field" do expect(Shape.attribute_names).to include("_type") end it "includes its own fields" do expect(Shape.attribute_names).to include("x") end it "does not return subclass fields" do expect(Shape.attribute_names).to_not include("radius") end end context "when the class is a subclass" do it "includes the _id field" do expect(Circle.attribute_names).to include("_id") end it "includes the _type field" do expect(Circle.attribute_names).to include("_type") end it "includes the first parent field" do expect(Circle.attribute_names).to include("x") end it "includes the second parent field" do expect(Circle.attribute_names).to include("y") end it "includes the child fields" do expect(Circle.attribute_names).to include("radius") end end end describe "#field" do before(:all) do Mongoid::Fields.option :custom do |model, field, value| end end context "when the options are valid" do context "when the options are all standard" do before do Band.field :acceptable, type: Boolean end after do Band.fields.delete("acceptable") end it "adds the field to the model" do expect(Band.fields["acceptable"]).to_not be_nil end end context "when a custom option is provided" do before do Band.field :acceptable, type: Boolean, custom: true end it "adds the field to the model" do expect(Band.fields["acceptable"]).to_not be_nil end end end context "when the options are not valid" do it "raises an error" do expect { Band.field :unacceptable, bad: true }.to raise_error(Mongoid::Errors::InvalidFieldOption) end end end describe "#getter" do context "when the field is binary" do let(:binary) do BSON::Binary.new(:md5, "testing") end let(:registry) do Registry.new(data: binary) end it "returns the binary data intact" do expect(registry.data).to eq(binary) end end context "when a field is localized" do let(:product) do Product.new end context "when no locale is set" do before do product.description = "The best" end let(:description) do product.description end it "returns the default locale value" do expect(description).to eq("The best") end end context "when a single locale is set" do before do ::I18n.locale = :de product.description = "The best" end after do ::I18n.locale = :en end let(:description) do product.description end it "returns the set locale value" do expect(description).to eq("The best") end end context "when multiple locales are set" do before do product.description = "Cheap drinks" ::I18n.locale = :de product.description = "Cheaper drinks" end after do ::I18n.locale = :en end let(:description) do product.description end it "returns the current locale value" do expect(description).to eq("Cheaper drinks") end end end end describe "#getter_before_type_cast" do let(:person) do Person.new end context "when the attribute has not been assigned" do it "delgates to the getter" do expect(person.age_before_type_cast).to eq(person.age) end end context "when the attribute has been assigned" do it "returns the attribute before type cast" do person.age = "old" expect(person.age_before_type_cast).to eq("old") end end end describe "#setter=" do let(:product) do Product.new end context "when setting via the setter" do it "returns the set value" do expect(product.price = 10).to eq(10) end end context "when setting via send" do it "returns the set value" do expect(product.send(:price=, 10)).to eq(10) end end context "when the field is binary" do let(:binary) do BSON::Binary.new(:md5, "testing") end let(:registry) do Registry.new end before do registry.data = binary end it "returns the binary data intact" do expect(registry.data).to eq(binary) end end context "when the field is an array" do before do product.stores = [ "kadewe", "karstadt" ] product.save end context "when setting the value to nil" do before do product.stores = nil product.save end it "allows the set" do expect(product.stores).to be_nil end end context "when setting any of the values to nil" do before do product.stores = [ "kadewe", nil ] product.save end it "allows the set of nil values" do expect(product.stores).to eq([ "kadewe", nil ]) end it "persists the nil values" do expect(product.reload.stores).to eq([ "kadewe", nil ]) end end context "when reversing the array values" do before do product.stores = [ "karstadt", "kadewe" ] product.save end it "reverses the values" do expect(product.stores).to eq([ "karstadt", "kadewe" ]) end it "persists the changes" do expect(product.reload.stores).to eq([ "karstadt", "kadewe" ]) end end end context "when a field is localized" do context "when no locale is set" do before do product.description = "Cheap drinks" end let(:description) do product.attributes["description"] end it "sets the value in the default locale" do expect(description).to eq({ "en" => "Cheap drinks" }) end end context "when a locale is set" do before do ::I18n.locale = :de product.description = "Cheaper drinks" end after do ::I18n.locale = :en end let(:description) do product.attributes["description"] end it "sets the value in the default locale" do expect(description).to eq({ "de" => "Cheaper drinks" }) end end context "when having multiple locales" do before do product.description = "Cheap drinks" ::I18n.locale = :de product.description = "Cheaper drinks" end after do ::I18n.locale = :en end let(:description) do product.attributes["description"] end it "sets the value in both locales" do expect(description).to eq( { "de" => "Cheaper drinks", "en" => "Cheap drinks" } ) end end end end describe "#defaults" do context "with defaults specified as a non-primitive" do let(:person_one) do Person.new end let(:person_two) do Person.new end context "when provided a default array" do before do Person.field(:array_testing, type: Array, default: []) end after do Person.fields.delete("array_testing") Person.pre_processed_defaults.delete_one("array_testing") end it "returns an equal object of a different instance" do expect(person_one.array_testing.object_id).to_not eq( person_two.array_testing.object_id ) end end context "when provided a default hash" do before do Person.field(:hash_testing, type: Hash, default: {}) end after do Person.fields.delete("hash_testing") end it "returns an equal object of a different instance" do expect(person_one.hash_testing.object_id).to_not eq( person_two.hash_testing.object_id ) end end context "when provided a default proc" do context "when the proc has no argument" do before do Person.field( :generated_testing, type: Float, default: ->{ Time.now.to_f } ) end after do Person.fields.delete("generated_testing") Person.pre_processed_defaults.delete_one("generated_testing") end it "returns an equal object of a different instance" do expect(person_one.generated_testing.object_id).to_not eq( person_two.generated_testing.object_id ) end end context "when the proc has to be evaluated on the document" do before do Person.field( :rank, type: Integer, default: ->{ title? ? 1 : 2 } ) end after do Person.fields.delete("rank") Person.post_processed_defaults.delete_one("rank") end it "yields the document to the proc" do expect(Person.new.rank).to eq(2) end end end end context "on parent classes" do let(:shape) do Shape.new end it "does not return subclass defaults" do expect(shape.pre_processed_defaults).to eq([ "_id", "x", "y", "_type" ]) end end context "on subclasses" do let(:circle) do Circle.new end it "has the parent and child defaults" do expect(circle.pre_processed_defaults).to eq([ "_id", "x", "y", "_type", "radius" ]) end end end describe ".field" do it "returns the generated field" do expect(Person.field(:testing)).to eq(Person.fields["testing"]) end context "when the field name conflicts with mongoid's internals" do context "when the field is named metadata" do it "raises an error" do expect { Person.field(:metadata) }.to raise_error(Mongoid::Errors::InvalidField) end end end context "when field already exist and validate_duplicate is enable" do before do Mongoid.duplicate_fields_exception = true end after do Mongoid.duplicate_fields_exception = false end it "raises an error" do expect { Person.field(:title) }.to raise_error(Mongoid::Errors::InvalidField) end it "doesn't raise an error" do expect { Class.new(Person) }.to_not raise_error end end context "when the field is a time" do let!(:time) do Time.now end let!(:person) do Person.new(lunch_time: time.utc) end context "when reading the field" do before do Time.zone = "Berlin" end after do Time.zone = nil end it "performs the necessary time conversions" do expect(person.lunch_time.to_s).to eq(time.getlocal.to_s) end end end context "when providing no options" do before do Person.field(:testing) end let(:person) do Person.new(testing: "Test") end it "adds a reader for the fields defined" do expect(person.testing).to eq("Test") end it "adds a writer for the fields defined" do (person.testing = expect("Testy")).to eq("Testy") end it "adds an existance method" do expect(Person.new.testing?).to be_false end context "when overwriting an existing field" do before do Person.class_eval do attr_reader :testing_override_called def testing=(value) @testing_override_called = true super end end person.testing = 'Test' end it "properly overwrites the method" do expect(person.testing_override_called).to be_true end end end context "when the type is an object" do let(:bob) do Person.new(reading: 10.023) end it "returns the given value" do expect(bob.reading).to eq(10.023) end end context "when type is a boolean" do let(:person) do Person.new(terms: true) end it "adds an accessor method with a question mark" do expect(person.terms?).to be_true end end context "when as is specified" do let(:person) do Person.new(alias: true) end before do Person.field :aliased, as: :alias, type: Boolean end it "uses the alias to write the attribute" do (person.alias = expect(true)).to be_true end it "uses the alias to read the attribute" do expect(person.alias).to be_true end it "uses the alias for the query method" do expect(person).to be_alias end it "uses the name to write the attribute" do (person.aliased = expect(true)).to be_true end it "uses the name to read the attribute" do expect(person.aliased).to be_true end it "uses the name for the query method" do expect(person).to be_aliased end it "creates dirty methods for the name" do expect(person).to respond_to(:aliased_changed?) end it "creates dirty methods for the alias" do expect(person).to respond_to(:alias_changed?) end context "when changing the name" do before do person.aliased = true end it "sets name_changed?" do expect(person.aliased_changed?).to be_true end it "sets alias_changed?" do expect(person.alias_changed?).to be_true end end context "when changing the alias" do before do person.alias = true end it "sets name_changed?" do expect(person.aliased_changed?).to be_true end it "sets alias_changed?" do expect(person.alias_changed?).to be_true end end context "when defining a criteria" do let(:criteria) do Person.where(alias: "true") end it "properly serializes the aliased field" do expect(criteria.selector).to eq({ "aliased" => true }) end end end context "custom options" do let(:handler) do proc {} end before do Mongoid::Fields.option :option, &handler end context "when option is provided" do it "calls the handler with the model" do handler.should_receive(:call).with do |model,_,_| expect(model).to eql User end User.field :custom, option: true end it "calls the handler with the field" do handler.should_receive(:call).with do |_,field,_| expect(field).to eql User.fields["custom"] end User.field :custom, option: true end it "calls the handler with the option value" do handler.should_receive(:call).with do |_,_,value| expect(value).to eql true end User.field :custom, option: true end end context "when option is nil" do it "calls the handler" do handler.should_receive(:call) User.field :custom, option: nil end end context "when option is not provided" do it "does not call the handler" do handler.should_receive(:call).never User.field :custom end end end end describe "#fields" do context "on parent classes" do let(:shape) do Shape.new end it "includes its own fields" do expect(shape.fields.keys).to include("x") end it "does not return subclass fields" do expect(shape.fields.keys).to_not include("radius") end end context "on subclasses" do let(:circle) do Circle.new end it "includes the first parent field" do expect(circle.fields.keys).to include("x") end it "includes the second parent field" do expect(circle.fields.keys).to include("y") end it "includes the child fields" do expect(circle.fields.keys).to include("radius") end end end describe ".replace_field" do let!(:original) do Person.field(:id_test, type: BSON::ObjectId, label: "id") end let!(:altered) do Person.replace_field("id_test", String) end after do Person.fields.delete("id_test") end let(:new_field) do Person.fields["id_test"] end it "sets the new type on the field" do expect(new_field.type).to eq(String) end it "keeps the options from the old field" do expect(new_field.options[:label]).to eq("id") end end context "when sending an include of another module at runtime" do before do Basic.send(:include, Ownable) end context "when the class is a parent" do let(:fields) do Basic.fields end it "resets the fields" do expect(fields.keys).to include("user_id") end end context "when the class is a subclass" do let(:fields) do SubBasic.fields end it "resets the fields" do expect(fields.keys).to include("user_id") end end end context "when a setter accesses a field with a default" do let(:person) do Person.new(set_on_map_with_default: "testing") end it "sets the default value pre process" do expect(person.map_with_default).to eq({ "key" => "testing" }) end end context "when a field is defined as a big decimal" do let(:band) do Band.new(name: "Tool") end let(:decimal) do BigDecimal.new("1000000.00") end context "when setting to a big decimal" do before do band.sales = decimal end it "properly persists as a string" do expect(band.attributes["sales"]).to eq(decimal.to_s) end it "returns the proper big decimal" do expect(band.sales).to eq(decimal) end end context "when setting to a string" do before do band.sales = decimal.to_s end it "properly persists as a string" do expect(band.attributes["sales"]).to eq(decimal.to_s) end it "returns the proper big decimal" do expect(band.sales).to eq(decimal) end end context "when setting to an integer" do before do band.sales = decimal.to_i end it "properly persists as a string" do expect(band.attributes["sales"]).to eq("1000000") end it "returns the proper big decimal" do expect(band.sales).to eq(decimal) end end context "when setting to a float" do before do band.sales = decimal.to_f end it "properly persists as a string" do expect(band.attributes["sales"]).to eq(decimal.to_s) end it "returns the proper big decimal" do expect(band.sales).to eq(decimal) end end end context "when the field is a hash of arrays" do let(:person) do Person.create end let(:map) do { "stack1" => [ 1, 2, 3, 4 ], "stack2" => [ 1, 2, 3, 4 ], "stack3" => [ 1, 2, 3, 4 ] } end before do person.map = map person.map["stack1"].reverse! person.save end it "properly updates the hash" do expect(person.map).to eq( { "stack1" => [ 4, 3, 2, 1 ], "stack2" => [ 1, 2, 3, 4 ], "stack3" => [ 1, 2, 3, 4 ] } ) end it "persists the changes" do expect(person.reload.map).to eq( { "stack1" => [ 4, 3, 2, 1 ], "stack2" => [ 1, 2, 3, 4 ], "stack3" => [ 1, 2, 3, 4 ] } ) end end context "when overriding a parent class field" do context "when the field has a default value" do let!(:canvas) do Canvas.new end let!(:test) do Canvas::Test.new end it "does not override the parent" do expect(canvas.foo).to eq("original") end it "overrides the default" do expect(test.foo).to eq("overridden") end end end end