Skip to content

Commit

Permalink
Now able to specify :method => :build in a factory's association.
Browse files Browse the repository at this point in the history
See issue #64.
  • Loading branch information
Jim Kingdon committed Aug 25, 2011
1 parent d22e0d8 commit 9037481
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 8 deletions.
20 changes: 16 additions & 4 deletions GETTING_STARTED.md
Expand Up @@ -194,13 +194,25 @@ The behavior of the association method varies depending on the build strategy us

# Builds and saves a User and a Post
post = FactoryGirl.create(:post)
post.new_record? # => false
post.author.new_record # => false
post.new_record? # => false
post.author.new_record? # => false

# Builds and saves a User, and then builds but does not save a Post
post = FactoryGirl.build(:post)
post.new_record? # => true
post.author.new_record # => false
post.new_record? # => true
post.author.new_record? # => false

To not save the associated object, specify :method => :build in the factory:

factory :post do
# ...
association :author, :factory => :user, :method => :build
end

# Builds a User, and then builds a Post, but does not save either
post = FactoryGirl.build(:post)
post.new_record? # => true
post.author.new_record? # => true

Inheritance
-----------
Expand Down
23 changes: 21 additions & 2 deletions lib/factory_girl/proxy/build.rb
Expand Up @@ -23,19 +23,38 @@ def set(attribute, value, ignored = false)
end

def associate(name, factory_name, overrides)
method = get_method(overrides[:method])
factory = FactoryGirl.factory_by_name(factory_name)
set(name, factory.run(Proxy::Create, overrides))
set(name, factory.run(method, remove_method(overrides)))
end

def association(factory_name, overrides = {})
method = get_method(overrides[:method])
factory = FactoryGirl.factory_by_name(factory_name)
factory.run(Proxy::Create, overrides)
factory.run(method, remove_method(overrides))
end

def remove_method(overrides)
overrides.dup.delete_if {|key, value| key == :method}
end

def result(to_create)
run_callbacks(:after_build)
@instance
end

def parse_method(method)
method ||= :create
if :build == method
return Proxy::Build
elsif :create == method
return Proxy::Create
else
raise "unrecognized method #{method}"
end
end

alias_method :get_method, :parse_method
end
end
end
6 changes: 6 additions & 0 deletions lib/factory_girl/proxy/create.rb
Expand Up @@ -11,6 +11,12 @@ def result(to_create)
run_callbacks(:after_create)
@instance
end

def get_method(method_string)
# Leaving this as Proxy::Build in the :method => :build case
# is a bit strange, but does it have any user-visible behaviors?
parse_method(method_string)
end
end
end
end
8 changes: 6 additions & 2 deletions lib/factory_girl/proxy/stub.rb
Expand Up @@ -60,12 +60,16 @@ def set(attribute, value, ignored = false)

def associate(name, factory_name, overrides)
factory = FactoryGirl.factory_by_name(factory_name)
set(name, factory.run(Proxy::Stub, overrides))
set(name, factory.run(Proxy::Stub, remove_method(overrides)))
end

def association(factory_name, overrides = {})
factory = FactoryGirl.factory_by_name(factory_name)
factory.run(Proxy::Stub, overrides)
factory.run(Proxy::Stub, remove_method(overrides))
end

def remove_method(overrides)
overrides.dup.delete_if {|key, value| key == :method}
end

def result(to_create)
Expand Down
31 changes: 31 additions & 0 deletions spec/acceptance/build_spec.rb
Expand Up @@ -31,3 +31,34 @@
end
end

describe "a built instance with :method => :build" do
include FactoryGirl::Syntax::Methods

before do
define_model('User')

define_model('Post', :user_id => :integer) do
belongs_to :user
end

FactoryGirl.define do
factory :user

factory :post do
association(:user, :method => :build)
end
end
end

subject { build(:post) }

it "isn't saved" do
should be_new_record
end

it "assigns but does not save associations" do
subject.user.should be_kind_of(User)
subject.user.should be_new_record
end

end
27 changes: 27 additions & 0 deletions spec/acceptance/create_spec.rb
Expand Up @@ -31,6 +31,33 @@
end
end

describe "a created instance, specifying :method => build" do
include FactoryGirl::Syntax::Methods

before do
define_model('User')

define_model('Post', :user_id => :integer) do
belongs_to :user
end

FactoryGirl.define do
factory :user

factory :post do
association(:user, :method => :build)
end
end
end

subject { create('post') }

it "still saves associations (:method => :build only affects build, not create)" do
subject.user.should be_kind_of(User)
subject.user.should_not be_new_record
end
end

describe "a custom create" do
include FactoryGirl::Syntax::Methods

Expand Down
64 changes: 64 additions & 0 deletions spec/acceptance/stub_spec.rb
@@ -0,0 +1,64 @@
require 'spec_helper'

describe "a stubbed instance" do
include FactoryGirl::Syntax::Methods

before do
define_model('User')

define_model('Post', :user_id => :integer) do
belongs_to :user
end

FactoryGirl.define do
factory :user

factory :post do
user
end
end
end

subject { build_stubbed(:post) }

it "acts as if it came from the database" do
should_not be_new_record
end

it "assigns associations and acts as if it is saved" do
subject.user.should be_kind_of(User)
subject.user.should_not be_new_record
end
end

describe "a stubbed instance with :method => :build" do
include FactoryGirl::Syntax::Methods

before do
define_model('User')

define_model('Post', :user_id => :integer) do
belongs_to :user
end

FactoryGirl.define do
factory :user

factory :post do
association(:user, :method => :build)
end
end
end

subject { build_stubbed(:post) }

it "acts as if it is saved in the database" do
should_not be_new_record
end

it "assigns associations and acts as if it is saved" do
subject.user.should be_kind_of(User)
subject.user.should_not be_new_record
end

end
24 changes: 24 additions & 0 deletions spec/factory_girl/proxy/build_spec.rb
Expand Up @@ -9,4 +9,28 @@
it_should_behave_like "proxy with association support", FactoryGirl::Proxy::Create
it_should_behave_like "proxy with standard getters and setters", :attribute_name, "attribute value!"
it_should_behave_like "proxy with callbacks", :after_build
it_should_behave_like "proxy with :method => :build",
FactoryGirl::Proxy::Build

describe "specifying method" do
it "defaults to create" do
subject.send(:get_method, nil).should == FactoryGirl::Proxy::Create
end

it "can specify create explicitly" do
subject.send(:get_method, :create).should ==
FactoryGirl::Proxy::Create
end

it "can specify build explicitly" do
subject.send(:get_method, :build).should ==
FactoryGirl::Proxy::Build
end

it "complains if method is unrecognized" do
lambda { subject.send(:get_method, :froboznicate) }.
should raise_error("unrecognized method froboznicate")
end
end

end
2 changes: 2 additions & 0 deletions spec/factory_girl/proxy/stub_spec.rb
Expand Up @@ -9,6 +9,8 @@
it_should_behave_like "proxy with association support", FactoryGirl::Proxy::Stub
it_should_behave_like "proxy with standard getters and setters", :attribute_name, "attribute value!"
it_should_behave_like "proxy with callbacks", :after_stub
it_should_behave_like "proxy with :method => :build",
FactoryGirl::Proxy::Stub

context "asking for a result" do
it { subject.result(nil).should_not be_new_record }
Expand Down
29 changes: 29 additions & 0 deletions spec/support/shared_examples/proxy.rb
Expand Up @@ -49,6 +49,35 @@
end
end

shared_examples_for "proxy with :method => :build" do |factory_girl_proxy_class|
let(:factory_name) { :user }
let(:association_name) { :owner }
let(:factory) { stub("associate_factory") }
let(:overrides) { { :method => :build } }

before do
FactoryGirl.stubs(:factory_by_name => factory)
instance.stubs(association_name => factory_name)
factory.stubs(:run => factory_name)
subject.stubs(:set)
end

it "sets a value for the association" do
subject.associate(association_name, factory_name, overrides)
subject.result(nil).send(association_name).should == factory_name
end

it "sets the association attribute as the factory" do
subject.associate(association_name, factory_name, overrides)
subject.should have_received(:set).with(association_name, factory_name)
end

it "runs the factory with the correct proxy class" do
subject.associate(association_name, factory_name, overrides)
factory.should have_received(:run).with(factory_girl_proxy_class, {})
end
end

shared_examples_for "proxy with standard getters and setters" do |attribute, value|
before do
instance.stubs(:"#{attribute}=" => value, :"#{attribute}" => value)
Expand Down

5 comments on commit 9037481

@aaronjensen
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious why not just use the method used when the Factory was invoked? If I say build, build it and all associations, if I say create, create the entire hierarchy. As it is now I'd have to have separately factories if I want to be able to create and build.

@jcf
Copy link

@jcf jcf commented on 9037481 Aug 27, 2011

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build not Create

@joshuaclayton
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this got merged without me taking a look over it. @NZKoz had suggested :method => :build but I think it should be inferred from the way the factory is called (create, build, or build_stubbed). I'm not going to revert this commit, since it's a step in the right direction, but I'll talk to @jferris and try to get a better grasp why it can't be inferred, if that's actually the case.

@cgriego
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joshuaclayton Did you find out anything on if the method can be inferred? The comment added to Proxy::Create in this commit suggests that not overriding the method has no impact, and that may or may not be true in ActiveRecord, but it certainly has an impact in other ORMs like Mongoid.

@gamov
Copy link

@gamov gamov commented on 9037481 Nov 4, 2011

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes indeed, this doesn't really help because now you have to create two different factories (one for where the association is 'normal' and another one where you state :method => :build).

Still, the foreign key validation problem when you want to save the build model is not solved (validates_presence_of :user_id fails because the associated model user doesn't have an id yet).

Please sign in to comment.