require "spec_helper"

describe "initialize_with with non-FG attributes" do
  include FactoryGirl::Syntax::Methods

  before do
    define_model("User", name: :string, age: :integer) do
      def self.construct(name, age)
        new(name: name, age: age)
      end
    end

    FactoryGirl.define do
      factory :user do
        initialize_with { User.construct("John Doe", 21) }
      end
    end
  end

  subject    { build(:user) }
  its(:name) { should eq "John Doe" }
  its(:age)  { should eq 21 }
end

describe "initialize_with with FG attributes that are transient" do
  include FactoryGirl::Syntax::Methods

  before do
    define_model("User", name: :string) do
      def self.construct(name)
        new(name: "#{name} from .construct")
      end
    end

    FactoryGirl.define do
      factory :user do
        transient do
          name { "Handsome Chap" }
        end

        initialize_with { User.construct(name) }
      end
    end
  end

  subject    { build(:user) }
  its(:name) { should eq "Handsome Chap from .construct" }
end

describe "initialize_with non-ORM-backed objects" do
  include FactoryGirl::Syntax::Methods

  before do
    define_class("ReportGenerator") do
      attr_reader :name, :data

      def initialize(name, data)
        @name = name
        @data = data
      end
    end

    FactoryGirl.define do
      sequence(:random_data) { 5.times.map { Kernel.rand(200) } }

      factory :report_generator do
        transient do
          name "My Awesome Report"
        end

        initialize_with { ReportGenerator.new(name, FactoryGirl.generate(:random_data)) }
      end
    end
  end

  it "allows for overrides" do
    expect(build(:report_generator, name: "Overridden").name).to eq "Overridden"
  end

  it "generates random data" do
    expect(build(:report_generator).data.length).to eq 5
  end
end

describe "initialize_with parent and child factories" do
  before do
    define_class("Awesome") do
      attr_reader :name

      def initialize(name)
        @name = name
      end
    end

    FactoryGirl.define do
      factory :awesome do
        transient do
          name "Great"
        end

        initialize_with { Awesome.new(name) }

        factory :sub_awesome do
          transient do
            name "Sub"
          end
        end

        factory :super_awesome do
          initialize_with { Awesome.new("Super") }
        end
      end
    end
  end

  it "uses the parent's constructor when the child factory doesn't assign it" do
    expect(FactoryGirl.build(:sub_awesome).name).to eq "Sub"
  end

  it "allows child factories to override initialize_with" do
    expect(FactoryGirl.build(:super_awesome).name).to eq "Super"
  end
end

describe "initialize_with implicit constructor" do
  before do
    define_class("Awesome") do
      attr_reader :name

      def initialize(name)
        @name = name
      end
    end

    FactoryGirl.define do
      factory :awesome do
        transient do
          name "Great"
        end

        initialize_with { new(name) }
      end
    end
  end

  it "instantiates the correct object" do
    expect(FactoryGirl.build(:awesome, name: "Awesome name").name).to eq "Awesome name"
  end
end

describe "initialize_with doesn't duplicate assignment on attributes accessed from initialize_with" do
  before do
    define_class("User") do
      attr_reader :name
      attr_accessor :email

      def initialize(name)
        @name = name
      end
    end

    FactoryGirl.define do
      sequence(:email) { |n| "person#{n}@example.com" }

      factory :user do
        email

        name { email.gsub(/\@.+/, "") }

        initialize_with { new(name) }
      end
    end
  end

  it "instantiates the correct object" do
    built_user = FactoryGirl.build(:user)
    expect(built_user.name).to eq "person1"
    expect(built_user.email).to eq "person1@example.com"
  end
end

describe "initialize_with has access to all attributes for construction" do
  before do
    define_class("User") do
      attr_reader :name, :email, :ignored

      def initialize(attributes = {})
        @name = attributes[:name]
        @email = attributes[:email]
        @ignored = attributes[:ignored]
      end
    end

    FactoryGirl.define do
      sequence(:email) { |n| "person#{n}@example.com" }

      factory :user do
        transient do
          ignored "of course!"
        end

        email

        name { email.gsub(/\@.+/, "") }

        initialize_with { new(attributes) }
      end
    end
  end

  it "assigns attributes correctly" do
    user_with_attributes = FactoryGirl.build(:user)
    expect(user_with_attributes.email).to eq "person1@example.com"
    expect(user_with_attributes.name).to eq "person1"
    expect(user_with_attributes.ignored).to be_nil
  end
end

describe "initialize_with for a constructor that requires a block" do
  it "executes the block correctly" do
    define_class("Awesome") do
      attr_reader :output

      def initialize(&block)
        @output = instance_exec(&block)
      end
    end

    FactoryGirl.define do
      factory :awesome do
        initialize_with { new { "Output" } }
      end
    end

    expect(FactoryGirl.build(:awesome).output).to eq "Output"
  end
end
