require 'helper'

describe Toy::Attributes do
  uses_objects('User', 'Game')

  describe ".attributes" do
    it "defaults to empty hash" do
      User.attributes.should eq({})
    end
  end

  describe ".defaulted_attributes" do
    before do
      @name = User.attribute(:name, String)
      @age  = User.attribute(:age, Integer, :default => 10)
    end

    it "includes attributes with a default" do
      User.defaulted_attributes.should include(@age)
    end

    it "excludes attributes without a default" do
      User.defaulted_attributes.should_not include(@name)
    end

    it "memoizes after first call" do
      User.should_receive(:attributes).once.and_return({
        'name' => @name,
        'age' => @age,
      })
      User.defaulted_attributes
      User.defaulted_attributes
      User.defaulted_attributes
    end

    it "is unmemoized when declaring a new attribute" do
      User.defaulted_attributes
      age = User.attribute :location, String, :default => 'IN'
      User.defaulted_attributes.map(&:name).sort.should eq(%w[age location])
    end
  end

  describe ".attribute?" do
    before do
      User.attribute :age, Integer
    end

    it "returns true if attribute (symbol)" do
      User.attribute?(:age).should be_true
    end

    it "returns true if attribute (string)" do
      User.attribute?('age').should be_true
    end

    it "returns false if not attribute" do
      User.attribute?(:foobar).should be_false
    end
  end

  describe "#initialize" do
    before do
      User.attribute :name, String
      User.attribute :age,  Integer
    end

    it "sets attributes" do
      instance = User.new(:name => 'John', :age => 28)
      instance.name.should == 'John'
      instance.age.should  == 28
    end

    it "sets defaults" do
      User.attribute(:awesome, Boolean, :default => true)
      User.new.awesome.should be_true
    end

    it "does not fail with nil" do
      User.new(nil).should be_instance_of(User)
    end
  end

  describe "#attributes" do
    it "defaults to empty hash" do
      attrs = ToyObject().new.attributes
      attrs.should eq({})
    end

    it "includes all attributes that are not nil" do
      User.attribute(:name, String)
      User.attribute(:active, Boolean, :default => true)
      user = User.new
      user.attributes.should == {
        'active' => true,
      }
    end
  end

  describe "#attributes=" do
    it "sets attributes if present" do
      User.attribute :age, Integer
      record = User.new
      record.attributes = {:age => 20}
      record.age.should == 20
    end

    it "does nothing if nil" do
      record = User.new
      lambda { record.attributes = nil }.should_not raise_error
    end

    it "works with accessors that are not keys" do
      User.class_eval { attr_accessor :foo }
      record = User.new(:foo => 'oof')
      record.foo.should == 'oof'
    end

    it "ignores keys that are not attributes and do not have accessors defined" do
      lambda { User.new(:taco => 'bell') }.should_not raise_error
    end

    it "uses accessor over writing attribute" do
      User.attribute :age, Integer

      User.class_eval do
        def age=(value)
          write_attribute :age, value + 10
        end
      end

      record = User.new
      record.attributes = {:age => 15}
      record.age.should be(25)
    end

    it "uses write_attribute if accessor not present" do
      User.attribute :age, Integer
      record = User.new
      record.should_receive(:respond_to?).with("age=") { false }
      record.attributes = {:age => 10}
      record.age.should be(10)
    end
  end

  describe "reading an attribute" do
    before do
      User.attribute(:info, Hash)
      @user = User.new(:info => {'name' => 'John'})
    end

    it "returns the same instance" do
      @user.info.should equal(@user.info)
    end
  end

  describe "writing an attribute" do
    before do
      User.attribute :name, String

      User.class_eval do
        def alternate_name=(value)
          write_attribute :name, value
        end
      end
    end

    it "assigns attribute value" do
      user = User.new
      user.alternate_name = 'Joe'
      user.name.should eq('Joe')
    end

    context "when attribute not defined" do
      before do
        User.class_eval do
          def pirate=(value)
            write_attribute :pirate, value
          end
        end

        @user = User.new
      end

      it "raises error" do
        expect {
          @user.pirate = 'arrrrrr'
        }.to raise_error(Toy::AttributeNotDefined, "User does not have attribute pirate")
      end
    end
  end

  describe "declaring an attribute" do
    before do
      User.attribute :name, String
      User.attribute :age, Integer
    end

    it "adds attribute to attributes" do
      User.attributes['name'].should == Toy::Attribute.new(User, :name, String)
      User.attributes[:name].should be_nil
      User.attributes['age'].should  == Toy::Attribute.new(User, :age, Integer)
      User.attributes[:age].should be_nil
    end

    it "adds accessors" do
      record = User.new
      record.name = 'John'
      record.name.should == 'John'
    end

    it "converts to attribute type" do
      record = User.new
      record.age = '12'
      record.age.should == 12
    end

    it "adds query-ers" do
      record = User.new
      record.name?.should be_false
      record.name = 'John'
      record.name?.should be_true
    end

    it "knows if it responds to attribute method" do
      record = User.new
      record.should respond_to(:name)
      record.should respond_to(:name=)
      record.should respond_to(:name?)
    end

    it "know if it does not respond to method" do
      record = User.new
      record.should_not respond_to(:foobar)
    end

    it "aliases [] to read_attribute" do
      record = User.new(:name => 'John')
      record[:name].should == 'John'
    end

    it "aliases []= to write_attribute" do
      record = User.new
      record[:name] = 'John'
      record.name.should == 'John'
    end
  end

  describe "declaring an attribute with a default" do
    before do
      User.attribute :active, Boolean, :default => true
    end

    it "adds attribute to attributes" do
      attribute = Toy::Attribute.new(User, :active, Boolean, {:default => true})
      User.attributes['active'].should == attribute
    end

    it "defaults value when initialized" do
      User.new.active.should be(true)
    end

    it "overrides default if present" do
      User.new(:active => false).active.should be(false)
    end
  end

  describe "declaring an attribute with an abbreviation" do
    before do
      User.attribute(:twitter_access_token, String, :abbr => 'tat')
    end

    it "aliases reading to abbreviation" do
      user = User.new
      user.twitter_access_token = '1234'
      user.tat.should == '1234'
    end

    it "aliases writing to abbreviation" do
      user = User.new
      user.tat = '1234'
      user.twitter_access_token.should == '1234'
    end
  end

  describe "Initialization of array attributes" do
    before do
      User.attribute(:skills, Array)
    end

    it "initializes to empty array" do
      User.new.skills.should == []
    end
  end
end
