# frozen_string_literal: true
# encoding: utf-8

require "spec_helper"

describe Mongoid::Attributes do

  describe "\#{attribute}" do

    context "when setting the value in the getter" do

      let(:account) do
        Account.new
      end

      it "does not cause an infinite loop" do
        expect(account.overridden).to eq("not recommended")
      end
    end

    context "when the attribute was excluded in a criteria" do

      let!(:person) do
        Person.create(title: "sir")
      end

      context "when the attribute is localized" do

        before do
          person.update_attribute(:desc, "test")
        end

        context "when the context includes" do

          context "when the attribute exists" do

            let(:from_db) do
              Person.only(:desc).first
            end

            it "does not raise an error" do
              expect(from_db.desc).to eq("test")
            end

            context "accessing via []" do

              it "does not raise an error" do
                expect(from_db["desc"]).to eq("test")
              end
            end

            context "when calling only on a sub-document" do

              let(:title) {"Executive"}
              let(:city) {"NYC"}
              let!(:agent) do
                agent = Agent.new(:title => title)
                agent.build_address(:city => city)
                agent.save()
                agent
              end
              let(:from_db) do
                Agent.only(:title, "address.city").first
              end

              context "when the field is in the only" do

                it "does not raise an error" do
                  expect(from_db.address.city).to eq(city)
                end
              end

              context "accessing via []" do

                it "does not raise an error" do
                  expect(from_db["address.city"]).to eq(city)
                end
              end
            end
          end

          context 'when the attribute is a hash field' do

            before do
              person.update_attribute(:map, map)
            end

            let(:map) do
              { 'dates' => { 'y' => { '2016' => 'Berlin' } } }
            end

            let(:from_db) do
              Person.only('map.dates.y.2016').first
            end

            it "does not raise an error" do
              expect(from_db.map).to eq(map)
            end
          end
        end

        context "when the context excludes" do

          context "when the attribute exists" do

            let(:from_db) do
              Person.without(:pets).first
            end

            it "does not raise an error" do
              expect(from_db.desc).to eq("test")
            end
          end
        end
      end

      context "when excluding with only" do

        let(:from_db) do
          Person.only(:_id).first
        end

        it "raises an error" do
          expect {
            from_db.title
          }.to raise_error(ActiveModel::MissingAttributeError)
        end

        context "accessing via []" do

          it "raises an error" do
            expect {
              from_db["title"]
            }.to raise_error(ActiveModel::MissingAttributeError)
          end
        end
      end

      context "when excluding with without" do

        let(:from_db) do
          Person.without(:title).first
        end

        it "raises an error" do
          expect {
            from_db.title
          }.to raise_error(ActiveModel::MissingAttributeError)
        end
      end
    end
  end

  describe "#[]" do

    context 'when the document has a custom attribute type' do

      let(:bar) do
        Bar.create(lat_lng: LatLng.new(52.30, 13.25))
      end

      it 'returns the demongoized version of the attribute' do
        expect(bar.reload[:lat_lng]).to be_a(LatLng)
      end
    end

    context "when the document is a new record" do

      let(:person) do
        Person.new
      end

      context "when accessing a localized field" do

        before do
          person.desc = "testing"
        end

        context "when passing just the name" do

          it "returns the full value" do
            expect(person[:desc]).to eq("testing")
          end
        end

        context "when passing the name with locale" do

          it "returns the value for the locale" do
            expect(person["desc.en"]).to eq("testing")
          end
        end
      end

      context "when attribute does not exist" do

        it "returns the default value" do
          expect(person[:age]).to eq(100)
        end
      end

      context "when attribute is not accessible" do

        before do
          person.owner_id = 5
        end

        it "returns the value" do
          expect(person[:owner_id]).to eq(5)
        end
      end
    end

    context "when the document is an existing record" do

      let!(:person) do
        Person.create(title: "sir")
      end

      context "when the attribute was excluded in a criteria" do

        context "when excluding with only" do

          let(:from_db) do
            Person.only(:_id).first
          end

          it "raises an error" do
            expect {
              from_db[:title]
            }.to raise_error(ActiveModel::MissingAttributeError)
          end
        end

        context "when excluding with without" do

          let(:from_db) do
            Person.without(:title).first
          end

          it "raises an error" do
            expect {
              from_db[:title]
            }.to raise_error(ActiveModel::MissingAttributeError)
          end
        end
      end

      context "when the attribute does not exist" do

        before do
          person.collection
            .find({ _id: person.id })
            .update_one({ "$unset" => { age: 1 }})
        end

        context "when found" do

          let(:found) do
            Person.find(person.id)
          end

          it "returns the default value" do
            expect(found[:age]).to eq(100)
          end
        end

        context "when reloaded" do

          before do
            Mongoid.raise_not_found_error = false
            person.reload
            Mongoid.raise_not_found_error = true
          end

          it "returns the default value" do
            expect(person[:age]).to eq(100)
          end
        end
      end
    end
  end

  describe "#[]=" do

    context 'when the document has a custom attribute type' do

      let(:bar) do
        Bar.new.tap do |b|
          b[:lat_lng] = LatLng.new(52.30, 13.25)
          b.save
        end
      end

      it 'sets the demongoized version of the attribute' do
        expect(bar.reload.lat_lng).to be_a(LatLng)
      end
    end

    let(:person) do
      Person.new
    end

    context "when setting the attribute to nil" do

      let!(:age) do
        person[:age] = nil
      end

      it "does not use the default value" do
        expect(person.age).to be_nil
      end

      it "returns the set value" do
        expect(age).to be_nil
      end
    end

    context "when field has a default value" do

      let!(:terms) do
        person[:terms] = true
      end

      it "allows overwriting of the default value" do
        expect(person.terms).to be true
      end

      it "returns the set value" do
        expect(terms).to eq(true)
      end
    end
  end

  describe "#_id" do

    let(:person) do
      Person.new
    end

    it "delegates to #id" do
      expect(person._id).to eq(person.id)
    end

    context "when #id alias is overridden" do

      let(:object) do
        IdKey.new(key: 'foo')
      end

      it "delegates to another method" do
        expect(object.id).to eq(object.key)
      end
    end
  end

  describe "#_id=" do

    after(:all) do
      Person.field(
        :_id,
        type: BSON::ObjectId,
        pre_processed: true,
        default: ->{ BSON::ObjectId.new },
        overwrite: true
      )
    end

    context "when using object ids" do

      before(:all) do
        Person.field(
          :_id,
          type: BSON::ObjectId,
          pre_processed: true,
          default: ->{ BSON::ObjectId.new },
          overwrite: true
        )
      end

      let(:person) do
        Person.new
      end

      let(:bson_id) do
        BSON::ObjectId.new
      end

      context "when providing an object id" do

        before do
          person._id = bson_id
        end

        it "sets the id as the object id" do
          expect(person.id).to eq(bson_id)
        end
      end

      context "when providing a string" do

        before do
          person._id = bson_id.to_s
        end

        it "sets the id as the object id" do
          expect(person.id).to eq(bson_id)
        end
      end

      context "when providing an integer" do

        before do
          person._id = 2
        end

        it "sets the id as the supplied value to_s" do
          expect(person.id).to eq(2)
        end
      end

      context "when #id= alias is overridden" do

        let(:object) do
          IdKey.new(key: 'foo')
        end

        it "delegates to another method" do
          object.id = 'bar'
          expect(object.id).to eq('bar')
        end
      end

    end

    context "when using string ids" do

      before(:all) do
        Person.field(
          :_id,
          type: String,
          pre_processed: true,
          default: ->{ BSON::ObjectId.new.to_s },
          overwrite: true
        )
      end

      let(:person) do
        Person.new
      end

      let(:bson_id) do
        BSON::ObjectId.new
      end

      context "when providing an object id" do

        before do
          person._id = bson_id
        end

        it "sets the id as the string of the object id" do
          expect(person.id).to eq(bson_id.to_s)
        end
      end

      context "when providing a string" do

        before do
          person._id = bson_id.to_s
        end

        it "sets the id as the string" do
          expect(person.id).to eq(bson_id.to_s)
        end
      end

      context "when providing an integer" do

        before do
          person._id = 2
        end

        it "sets the id as the supplied value to_s" do
          expect(person.id).to eq("2")
        end
      end
    end

    context "when using integer ids" do

      before(:all) do
        Person.field(:_id, type: Integer, overwrite: true)
      end

      let(:person) do
        Person.new
      end

      context "when providing a string" do

        before do
          person._id = 1.to_s
        end

        it "sets the id as the integer" do
          expect(person.id).to eq(1)
        end
      end

      context "when providing an integer" do

        before do
          person._id = 2
        end

        it "sets the id as the supplied value" do
          expect(person.id).to eq(2)
        end
      end
    end
  end

  describe "#method_missing" do

    let(:attributes) do
      { testing: "Testing" }
    end

    let(:person) do
      Person.new(attributes)
    end

    context "when an attribute exists" do

      it "allows the getter" do
        expect(person.testing).to eq("Testing")
      end

      it "allows the setter" do
        person.testing = "Test"
        expect(person.testing).to eq("Test")
      end

      it "allows the getter before_type_cast" do
        expect(person.testing_before_type_cast).to eq("Testing")
      end

      it "returns true for respond_to?" do
        expect(person.respond_to?(:testing)).to be true
      end
    end

    context "when the provided value needs mongoization" do

      let(:new_years) do
        DateTime.new(2013, 1, 1, 0, 0, 0)
      end

      before do
        person[:new_years] = new_years
      end

      it "mongoizes the dynamic field" do
        expect(person.new_years).to be_a(Time)
      end

      it "keeps the same value" do
        expect(person.new_years).to eq(new_years)
      end
    end
  end

  describe "#process" do

    context "when attributes dont have fields defined" do

      let(:attributes) do
        {
          nofieldstring: "Testing",
          nofieldint: 5,
          employer: Employer.new
        }
      end

      context "when allowing dynamic fields" do

        let!(:person) do
          Person.new(attributes)
        end

        context "when attribute is a string" do

          it "adds the string to the attributes" do
            expect(person.attributes["nofieldstring"]).to eq("Testing")
          end
        end

        context "when attribute is not a string" do

          it "adds a cast value to the attributes" do
            expect(person.attributes["nofieldint"]).to eq(5)
          end
        end
      end

      context "when not allowing dynamic fields" do

        it "raises an unknown attribute error on instantiation" do
          expect {
            Account.new({ anothernew: "Test" })
          }.to raise_error(Mongoid::Errors::UnknownAttribute)
        end
      end
    end

    context "when supplied hash has string values" do

      let(:bson_id) do
        BSON::ObjectId.new
      end

      let!(:attributes) do
        {
          title: "value",
          age: "30",
          terms: "true",
          score: "",
          name: {
            _id: "2", first_name: "Test", last_name: "User"
          },
          addresses: [
            { _id: "3", street: "First Street" },
            { _id: "4", street: "Second Street" }
          ]
        }
      end

      let!(:person) do
        Person.new(attributes)
      end

      it "casts integers" do
        expect(person[:age]).to eq(30)
      end

      it "casts booleans" do
        expect(person[:terms]).to be true
      end

      it "sets empty strings to nil" do
        expect(person[:score]).to be_nil
      end
    end

    context "when associations provided in the attributes" do

      context "when association is a has_one" do

        let(:name) do
          Name.new(first_name: "Testy")
        end

        let(:attributes) do
          { name: name }
        end

        let(:person) do
          Person.new(attributes)
        end

        it "sets the associations" do
          expect(person.name).to eq(name)
        end
      end

      context "when association is a references_one" do

        let(:game) do
          Game.new(score: 100)
        end

        let(:attributes) do
          { game: game }
        end

        let!(:person) do
          Person.new(attributes)
        end

        it "sets the parent association" do
          expect(person.game).to eq(game)
        end

        it "sets the inverse association" do
          expect(game.person).to eq(person)
        end
      end

      context "when association is a embedded_in" do

        let(:person) do
          Person.new
        end

        let(:name) do
          Name.new(first_name: "Tyler", person: person)
        end

        it "sets the association" do
          expect(name.person).to eq(person)
        end
      end
    end

    context "when non-associations provided in the attributes" do

      let(:employer) do
        Employer.new
      end

      let(:attributes) do
        { employer_id: employer.id, title: "Sir" }
      end

      let(:person) do
        Person.new(attributes)
      end

      it "calls the setter for the association" do
        expect(person.employer_id).to eq("1")
      end
    end

    context "when an empty array is provided in the attributes" do

      let(:attributes) do
        { aliases: [] }
      end

      let(:person) do
        Person.new(attributes)
      end

      it "sets the empty array" do
        expect(person.aliases).to be_empty
      end
    end

    context "when an empty hash is provided in the attributes" do

      let(:attributes) do
        { map: {} }
      end

      let(:person) do
        Person.new(attributes)
      end

      it "sets the empty hash" do
        expect(person.map).to eq({})
      end
    end

    context "when providing tainted parameters" do

      let(:params) do
        ActionController::Parameters.new(title: "sir")
      end

      it "raises an error" do
        expect {
          Person.new(params)
        }.to raise_error(ActiveModel::ForbiddenAttributesError)
      end
    end
  end

  context "updating when attributes already exist" do

    let(:person) do
      Person.new(title: "Sir")
    end

    let(:attributes) do
      { dob: "2000-01-01" }
    end

    before do
      person.process_attributes(attributes)
    end

    it "only overwrites supplied attributes" do
      expect(person.title).to eq("Sir")
    end
  end

  describe "#read_attribute" do

    context 'when the document has a custom attribute type' do

      let(:bar) do
        Bar.create(lat_lng: LatLng.new(52.30, 13.25))
      end

      it 'returns the demongoized version of the attribute' do
        expect(bar.reload.read_attribute(:lat_lng)).to be_a(LatLng)
      end
    end

    context "when the document is a new record" do

      let(:person) do
        Person.new
      end

      context "when attribute does not exist" do

        it "returns the default value" do
          expect(person.age).to eq(100)
          expect(person.pets).to be false
        end

      end

      context "when attribute is not accessible" do

        before do
          person.owner_id = 5
        end

        it "returns the value" do
          expect(person.read_attribute(:owner_id)).to eq(5)
        end
      end
    end

    context "when the document is an existing record" do

      let(:person) do
        Person.create
      end

      context "when the attribute does not exist" do

        before do
          person.collection
            .find({ _id: person.id })
            .update_one({ "$unset" => { age: 1 }})
          Mongoid.raise_not_found_error = false
          person.reload
          Mongoid.raise_not_found_error = true
        end

        it "returns the default value" do
          expect(person.age).to eq(100)
        end
      end
    end

    context "when attribute has an aliased name" do

      let(:person) do
        Person.new
      end

      before(:each) do
        person.write_attribute(:t, "aliased field to test")
      end

      it "returns the value of the aliased field" do
        expect(person.read_attribute(:test)).to eq("aliased field to test")
      end
    end
  end

  describe "#read_attribute_before_type_cast" do

    let(:person) do
      Person.create
    end

    context "when the attribute has not yet been assigned" do

      it "returns the default value" do
        expect(person.age_before_type_cast).to eq(100)
      end
    end

    context "after the attribute has been assigned" do

      it "returns the default value" do
        person.age = "old"
        expect(person.age_before_type_cast).to eq("old")
      end
    end
  end

  describe "#attribute_present?" do

    context "when document is a new record" do

      let(:person) do
        Person.new
      end

      context "when attribute does not exist" do

        it "returns false" do
          expect(person.attribute_present?(:owner_id)).to be false
        end
      end

      context "when attribute does exist" do
        before do
          person.owner_id = 5
        end

        it "returns true" do
          expect(person.attribute_present?(:owner_id)).to be true
        end
      end
    end

    context "when the document is an existing record" do

      let(:person) do
        Person.create
      end

      context "when the attribute does not exist" do

        before do
          person.collection
            .find({ _id: person.id })
            .update_one({ "$unset" => { age: 1 }})
          Mongoid.raise_not_found_error = false
          person.reload
          Mongoid.raise_not_found_error = true
        end

        it "returns true" do
          expect(person.attribute_present?(:age)).to be true
        end
      end
    end

    context "when the value is boolean" do

      let(:person) do
        Person.new
      end

      context "when attribute does not exist" do

        context "when the value is true" do

          it "return true"  do
            person.terms = false
            expect(person.attribute_present?(:terms)).to be true
          end
        end

        context "when the value is false" do

          it "return true"  do
            person.terms = false
            expect(person.attribute_present?(:terms)).to be true
          end
        end
      end
    end

    context "when the value is blank string" do

      let(:person) do
        Person.new(title: '')
      end

      it "return false" do
        expect(person.attribute_present?(:title)).to be false
      end
    end

    context "when the attribute is not on only list" do

      before { Person.create }
      let(:person) do
        Person.only(:id).first
      end

      it "return false" do
        expect(person.attribute_present?(:foobar)).to be false
      end
    end
  end

  describe "#has_attribute?" do

    let(:person) do
      Person.new(title: "sir")
    end

    context "when the key is in the attributes" do

      context "when provided a symbol" do

        it "returns true" do
          expect(person.has_attribute?(:title)).to be true
        end
      end

      context "when provided a string" do

        it "returns true" do
          expect(person.has_attribute?("title")).to be true
        end
      end
    end

    context "when the key is not in the attributes" do

      it "returns false" do
        expect(person.has_attribute?(:employer_id)).to be false
      end
    end
  end

  describe '#has_attribute_before_type_cast?' do

    let(:person) do
      Person.new
    end

    context "before the attribute has been assigned" do

      it "returns false" do
        expect(person.has_attribute_before_type_cast?(:age)).to be false
      end
    end

    context "after the attribute has been assigned" do

      it "returns true" do
        person.age = 'old'
        expect(person.has_attribute_before_type_cast?(:age)).to be true
      end
    end
  end

  describe "#remove_attribute" do

    context "when the attribute exists" do

      let(:person) do
        Person.create(title: "Sir")
      end

      before do
        person.remove_attribute(:title)
      end

      it "removes the attribute" do
        expect(person.title).to be_nil
      end

      it "removes the key from the attributes hash" do
        expect(person.has_attribute?(:title)).to be false
      end

      context "when saving after the removal" do

        before do
          person.save
        end

        it "persists the removal" do
          expect(person.reload.has_attribute?(:title)).to be false
        end
      end
    end

    context "when the attribute exists in embedded document" do

     let(:pet) do
       Animal.new(name: "Cat")
     end

     let(:person) do
       Person.new(pet: pet)
     end

     before do
       person.save
       person.pet.remove_attribute(:name)
     end

     it "removes the attribute" do
       expect(person.pet.name).to be_nil
     end

     it "removes the key from the attributes hash" do
       expect(person.pet.has_attribute?(:name)).to be false
     end

     context "when saving after the removal" do

       before do
         person.save
       end

       it "persists the removal" do
         expect(person.reload.pet.has_attribute?(:name)).to be false
       end
     end

    end

    context "when the attribute does not exist" do

      let(:person) do
        Person.new
      end

      before do
        person.remove_attribute(:title)
      end

      it "does not fail" do
        expect(person.title).to be_nil
      end
    end

    context "when the document is new" do

      let(:person) do
        Person.new(title: "sir")
      end

      before do
        person.remove_attribute(:title)
      end

      it "does not add a delayed unset operation" do
        expect(person.delayed_atomic_unsets).to be_empty
      end
    end

    context "when the attribute is aliased" do

      context 'when the database name is used' do

        let(:person) do
          Person.create(at: Time.now)
        end

        before do
          person.remove_attribute(:at)
        end

        it "removes the attribute" do
          expect(person.at).to be_nil
        end

        it "removes the key from the attributes hash" do
          expect(person.has_attribute?(:at)).to be false
        end

        context "when saving after the removal" do

          before do
            person.save
          end

          it "persists the removal" do
            expect(person.reload.has_attribute?(:at)).to be false
          end
        end
      end

      context 'when the alias is used' do

        let(:person) do
          Person.create(aliased_timestamp: Time.now)
        end

        before do
          person.remove_attribute(:aliased_timestamp)
        end

        it "removes the attribute" do
          expect(person.aliased_timestamp).to be_nil
        end

        it "removes the key from the attributes hash" do
          expect(person.has_attribute?(:aliased_timestamp)).to be false
        end

        context "when saving after the removal" do

          before do
            person.save
          end

          it "persists the removal" do
            expect(person.reload.has_attribute?(:aliased_timestamp)).to be false
          end
        end
      end
    end
  end

  describe "#respond_to?" do

    context "when allowing dynamic fields" do

      let(:person) do
        Person.new
      end

      context "when asking for the getter" do

        context "when the attribute exists" do

          before do
            person[:attr] = "test"
          end

          it "returns true" do
            expect(person).to respond_to(:attr)
          end
        end

        context "when the attribute does not exist" do

          it "returns false" do
            expect(person).to_not respond_to(:attr)
          end
        end
      end

      context "when asking for the setter" do

        context "when the attribute exists" do

          before do
            person[:attr] = "test"
          end

          it "returns true" do
            expect(person).to respond_to(:attr=)
          end
        end

        context "when the attribute does not exist" do

          it "returns false" do
            expect(person).to_not respond_to(:attr=)
          end
        end
      end
    end

    context "when not allowing dynamic fields" do

      let(:bar) do
        Bar.new
      end

      context "when asking for the getter" do

        it "returns false" do
          expect(bar).to_not respond_to(:attr)
        end
      end

      context "when asking for the setter" do

        it "returns false" do
          expect(bar).to_not respond_to(:attr=)
        end
      end
    end
  end

  describe "#write_attribute" do

    context "when attribute does not exist" do

      let(:person) do
        Person.new
      end

      it "returns the default value" do
        expect(person.age).to eq(100)
      end
    end

    context "when setting an attribute that needs type casting" do
      let(:person) do
        Person.new(age: "old")
      end

      it "should store the attribute before type cast" do
        expect(person.age_before_type_cast).to eq("old")
      end
    end

    context "when setting the attribute to nil" do

      let(:person) do
        Person.new(age: nil)
      end

      it "does not use the default value" do
        expect(person.age).to be_nil
      end
    end

    context "when field has a default value" do

      let(:person) do
        Person.new
      end

      before do
        person.terms = true
      end

      it "allows overwriting of the default value" do
        expect(person.terms).to be true
      end
    end

    context "when attribute has an aliased name" do

      let(:person) do
        Person.new
      end

      before(:each) do
        person.write_attribute(:test, "aliased field to test")
      end

      it "allows the field name to be udpated" do
        expect(person.t).to eq("aliased field to test")
      end
    end

    context "when attribute is a Hash" do
      let(:person) { Person.new map: { somekey: "somevalue" } }

      it "raises an error when trying to set a value of invalid type - array" do
        expect do
          person.map = []
        end.to raise_error(Mongoid::Errors::InvalidValue, /Value of type Array cannot be written to a field of type Hash/)
      end

      it "raises an error when trying to set a value of invalid type - boolean" do
        expect do
          person.map = false
        end.to raise_error(Mongoid::Errors::InvalidValue, /Value of type FalseClass cannot be written to a field of type Hash/)
      end

      it "can set a Hash value" do
        expect(person.map).to eq( { somekey: "somevalue" } )
      end
    end

    context "when attribute is an Array" do
      let(:person) { Person.new aliases: [ :alias_1 ] }

      it "can set an Array Value" do
        expect(person.aliases).to eq([ :alias_1 ])
      end

      it "raises an error when trying to set a value of invalid type - hash" do
        expect do
          person.aliases = {}
        end.to raise_error(Mongoid::Errors::InvalidValue, /Value of type Hash cannot be written to a field of type Array/)
      end

      it "raises an error when trying to set a value of invalid type - boolean" do
        expect do
          person.aliases = false
        end.to raise_error(Mongoid::Errors::InvalidValue, /Value of type FalseClass cannot be written to a field of type Array/)
      end
    end

    context "when attribute is localized and #attributes is a BSON::Document" do
      let(:dictionary) { Dictionary.new }

      before do
        allow(dictionary).to receive(:attributes).and_return(BSON::Document.new)
      end

      it "sets the value for the current locale" do
        dictionary.write_attribute(:description, 'foo')
        expect(dictionary.description).to eq('foo')
      end
    end
  end

  describe "#typed_value_for" do

    let(:person) do
      Person.new
    end

    context "when the key has been specified as a field" do

      it "retuns the typed value" do
        person.send(:typed_value_for, "age", "51")
      end
    end

    context "when the key has not been specified as a field" do

      before do
        allow(person).to receive(:fields).and_return({})
      end

      it "returns the value" do
        person.send(:typed_value_for, "age", expect("51")).to eq("51")
      end
    end
  end

  describe "#apply_default_attributes" do

    let(:person) do
      Person.new
    end

    it "typecasts proc values" do
      expect(person.age).to eq(100)
    end
  end

  describe "#typed_attributes"  do

    let(:date_time) do
      DateTime.current
    end

    let(:user) do
      User.new(last_login: date_time)
    end

    it 'returns typecasted attributes' do
      expect(user.typed_attributes).to include("last_login" => date_time)
    end
  end

  [:attributes=, :write_attributes].each do |method|

    describe "##{method}" do

      context "when nested" do

        let(:person) do
          Person.new
        end

        before do
          person.send(method, { videos: [{title: "Fight Club"}] })
        end

        it "sets nested documents" do
          expect(person.videos.first.title).to eq("Fight Club")
        end
      end

      context "typecasting" do

        let(:person) do
          Person.new
        end

        let(:attributes) do
          { age: "50" }
        end

        context "when passing a hash" do

          before do
            person.send(method, attributes)
          end

          it "properly casts values" do
            expect(person.age).to eq(50)
          end
        end

        context "when passing nil" do

          before do
            person.send(method, nil)
          end

          it "does not set anything" do
            expect(person.age).to eq(100)
          end
        end
      end

      context "copying from instance" do

        let(:person) do
          Person.new
        end

        let(:instance) do
          Person.new(attributes)
        end

        let(:attributes) do
          { age: 50, range: 1..100 }
        end

        before do
          person.send(method, instance.attributes)
        end

        it "properly copies values" do
          expect(person.age).to eq(50)
        end

        it "properly copies ranges" do
          expect(person.range).to eq(1..100)
        end
      end

      context "on a parent document" do

        context "when the parent has a has many through a has one" do

          let(:owner) do
            PetOwner.new(title: "Mr")
          end

          let(:pet) do
            Pet.new(name: "Fido")
          end

          let(:vet_visit) do
            VetVisit.new(date: Date.today)
          end

          before do
            owner.pet = pet
            pet.vet_visits = [ vet_visit ]
            owner.send(method, { pet: { name: "Bingo" } })
          end

          it "does not overwrite child attributes if not in the hash" do
            expect(owner.pet.name).to eq("Bingo")
            expect(owner.pet.vet_visits.size).to eq(1)
          end
        end

        context "when parent destroy all child on setter" do

          let(:owner) do
            PetOwner.create!(title: "Mr")
          end

          let(:pet) do
            Pet.create!(name: "Kika", pet_owner: owner)
          end

          let!(:vet_visit) do
            pet.vet_visits.create!(date: Date.today)
          end

          before do
            pet.send(method, { visits_count: 3 })
            pet.save!
          end

          it "has 3 new entries" do
            expect(pet.vet_visits.count).to eq(3)
          end

          it "persists the changes" do
            expect(pet.reload.vet_visits.count).to eq(3)
          end
        end

        context "when the parent has an empty embeds_many" do

          let(:person) do
            Person.new
          end

          let(:attributes) do
            { services: [] }
          end

          it "does not raise an error" do
            person.send(method, attributes)
          end
        end
      end

      context "on a child document" do

        context "when child is part of a has one" do

          let(:person) do
            Person.new(title: "Sir", age: 30)
          end

          let(:name) do
            Name.new(first_name: "Test", last_name: "User")
          end

          before do
            person.name = name
            name.send(method, first_name: "Test2", last_name: "User2")
          end

          it "sets the child attributes on the parent" do
            expect(name.attributes).to eq(
              { "_id" => "Test-User", "first_name" => "Test2", "last_name" => "User2" }
            )
          end
        end

        context "when child is part of a has many" do

          let(:person) do
            Person.new(title: "Sir")
          end

          let(:address) do
            Address.new(street: "Test")
          end

          before do
            person.addresses << address
            address.send(method, "street" => "Test2")
          end

          it "updates the child attributes on the parent" do
            expect(address.attributes).to eq(
              { "_id" => "test", "street" => "Test2" }
            )
          end
        end
      end
    end
  end

  describe "#alias_attribute" do

    let(:product) do
      Product.new
    end

    context "when checking against the alias" do

      before do
        product.cost = 500
      end

      it "adds the alias for criteria" do
        expect(Product.where(cost: 500).selector).to eq("price" => 500)
      end

      it "aliases the getter" do
        expect(product.cost).to eq(500)
      end

      it "aliases the existence check" do
        expect(product.cost?).to be true
      end

      it "aliases *_changed?" do
        expect(product.cost_changed?).to be true
      end

      it "aliases *_change" do
        expect(product.cost_change).to eq([ nil, 500 ])
      end

      it "aliases *_will_change!" do
        expect(product).to respond_to(:cost_will_change!)
      end

      it "aliases *_was" do
        expect(product.cost_was).to be_nil
      end

      it "aliases reset_*!" do
        product.reset_cost!
        expect(product.cost).to be_nil
      end

      it "aliases *_before_type_cast" do
        product.cost = "expensive"
        expect(product.cost_before_type_cast).to eq("expensive")
      end
    end

    context "when checking against the original" do

      before do
        product.price = 500
      end

      it "aliases the getter" do
        expect(product.price).to eq(500)
      end

      it "aliases the existence check" do
        expect(product.price?).to be true
      end

      it "aliases *_changed?" do
        expect(product.price_changed?).to be true
      end

      it "aliases *_change" do
        expect(product.price_change).to eq([ nil, 500 ])
      end

      it "aliases *_will_change!" do
        expect(product).to respond_to(:price_will_change!)
      end

      it "aliases *_was" do
        expect(product.price_was).to be_nil
      end

      it "aliases reset_*!" do
        product.reset_price!
        expect(product.price).to be_nil
      end
    end
  end

  context "when persisting nil attributes" do

    let!(:person) do
      Person.create(score: nil)
    end

    it "has an entry in the attributes" do
      expect(person.reload.attributes).to have_key("score")
    end
  end

  context "with a default last_drink_taken_at" do

    let(:person) do
      Person.new
    end

    it "saves the default" do
      expect { person.save }.to_not raise_error
      expect(person.last_drink_taken_at).to eq(1.day.ago.in_time_zone("Alaska").to_date)
    end
  end

  context "when default values are defined" do

    context "when no value exists in the database" do

      let(:person) do
        Person.create
      end

      it "applies the default value" do
        expect(person.last_drink_taken_at).to eq(1.day.ago.in_time_zone("Alaska").to_date)
      end
    end

    context "when a value exists in the database" do

      context "when the value is not nil" do

        let!(:person) do
          Person.create(age: 50)
        end

        let(:from_db) do
          Person.find(person.id)
        end

        it "does not set the default" do
          expect(from_db.age).to eq(50)
        end
      end

      context "when the value is explicitly nil" do

        let!(:person) do
          Person.create(age: nil)
        end

        let(:from_db) do
          Person.find(person.id)
        end

        it "does not set the default" do
          expect(from_db.age).to be_nil
        end
      end

      context "when the default is a proc" do

        let!(:account) do
          Account.create(name: "savings", balance: "100")
        end

        let(:from_db) do
          Account.find(account.id)
        end

        it "applies the defaults after all attributes are set" do
          expect(from_db).to be_balanced
        end
      end
    end
  end

  context 'when calling the attribute check method' do

    context 'when the attribute is blank' do
      let(:person) do
        Person.create(title: '')
      end

      it 'returns false' do
        expect(person.title?).to be(false)
      end
    end

    context 'when the attribute is localized' do
      let(:person) do
        Person.create
      end

      context 'after initialization when the field is nil' do

        it 'returns false' do
          expect(person.desc?).to be(false)
        end
      end

      context 'when setting the field to nil' do

        it 'applies the localization when checking the attribute' do
          person.desc = nil
          expect(person.desc?).to be(false)
        end
      end

      context 'when the field is a boolean' do

        before do
          person.desc = false
        end

        it 'applies the localization when checking the attribute' do
          expect(person.desc?).to be(false)
        end
      end
    end

    context 'when the attribute is not localized' do
      let(:person) do
        Person.create(username: 'localized')
      end

      before do
        person.username = nil
      end

      it 'does not apply localization when checking the attribute' do
        expect(person.username?).to be(false)
      end
    end
  end
end
