Introduce new callback syntax

Instead of calling before_create, after_build, after_create, or
after_stub, you can now call:

    before(:create) {|instance| instance.name = "overridden!" }
    after(:create)  {|instance| instance.name = "overridden!" }
    after(:build)   {|instance| instance.name = "overridden!" }
    after(:stub)    {|instance| instance.name = "overridden!" }

Additionally, you can declare callbacks longhand:

    callback(:after_stub) {|instance| instance.name = "overridden!" }

This allows for custom callbacks to be defined:

    callback(:custom_callback) {|instance| instance.name = "overridden!" }

Which can then be used from a custom strategy:

    class CustomStrategy
      def association(runner); end

      def result(evaluation)
        evaluation.object.tap do |instance|
          evaluation.notify(:custom_callback, instance)
        end
      end
    end

    FactoryGirl.register_strategy(:custom, CustomStrategy)

This would allow for calling:

    FactoryGirl.custom(:user)

Which would return the user instance but execute the :custom_callback callback
on the user instance first.
This commit is contained in:
Josh Clayton and Jason Draper 2012-05-04 16:48:46 -04:00 committed by Joshua Clayton
parent 95a4626daa
commit bef5a01b31
11 changed files with 182 additions and 42 deletions

View File

@ -207,7 +207,7 @@ factory :user do
name { "John Doe#{" - Rockstar" if rockstar}" }
email { "#{name.downcase}@example.com" }
after_create do |user, evaluator|
after(:create) do |user, evaluator|
user.name.upcase! if evaluator.upcased
end
end
@ -299,11 +299,11 @@ FactoryGirl.define do
posts_count 5
end
# the after_create yields two values; the user instance itself and the
# the after(:create) yields two values; the user instance itself and the
# evaluator, which stores all values from the factory, including ignored
# attributes; `create_list`'s second argument is the number of records
# to create and we make sure the user is associated properly to the post
after_create do |user, evaluator|
after(:create) do |user, evaluator|
FactoryGirl.create_list(:post, evaluator.posts_count, user: user)
end
end
@ -589,18 +589,19 @@ FactoryGirl.create_list(:user, 3, :admin, :male, name: "Jon Snow")
Callbacks
---------
factory\_girl makes available three callbacks for injecting some code:
factory\_girl makes available four callbacks for injecting some code:
* after_build - called after a factory is built (via FactoryGirl.build)
* after_create - called after a factory is saved (via FactoryGirl.create)
* after_stub - called after a factory is stubbed (via FactoryGirl.build_stubbed)
* after(:build) - called after a factory is built (via `FactoryGirl.build`, `FactoryGirl.create`)
* before(:create) - called before a factory is saved (via `FactoryGirl.create`)
* after(:create) - called after a factory is saved (via `FactoryGirl.create`)
* after(:stub) - called after a factory is stubbed (via `FactoryGirl.build_stubbed`)
Examples:
```ruby
# Define a factory that calls the generate_hashed_password method after it is built
factory :user do
after_build { |user| generate_hashed_password(user) }
after(:build) { |user| generate_hashed_password(user) }
end
```
@ -610,8 +611,8 @@ You can also define multiple types of callbacks on the same factory:
```ruby
factory :user do
after_build { |user| do_something_to(user) }
after_create { |user| do_something_else_to(user) }
after(:build) { |user| do_something_to(user) }
after(:create) { |user| do_something_else_to(user) }
end
```
@ -619,12 +620,12 @@ Factories can also define any number of the same kind of callback. These callba
```ruby
factory :user do
after_create { this_runs_first }
after_create { then_this }
after(:create) { this_runs_first }
after(:create) { then_this }
end
```
Calling FactoryGirl.create will invoke both after\_build and after\_create callbacks.
Calling FactoryGirl.create will invoke both `after_build` and `after_create` callbacks.
Also, like standard attributes, child factories will inherit (and can also define) callbacks from their parent factory.
@ -677,7 +678,7 @@ When modifying a factory, you can change any of the attributes you want (aside f
`FactoryGirl.modify` must be called outside of a `FactoryGirl.define` block as it operates on factories differently.
A caveat: you can only modify factories (not sequences or traits) and callbacks *still compound as they normally would*. So, if
the factory you're modifying defines an `after_create` callback, you defining an `after_create` won't override it, it'll just get run after the first callback.
the factory you're modifying defines an `after(:create)` callback, you defining an `after(:create)` won't override it, it'll just get run after the first callback.
Building or Creating Multiple Records
-------------------------------------
@ -810,6 +811,41 @@ FactoryGirl.json(:user)
Finally, you can override factory\_girl's own strategies if you'd like by
registering a new object in place of the strategies.
Custom Callbacks
----------------
Custom callbacks can be defined if you're using custom strategies:
```ruby
class JsonStrategy
def initialize
@strategy = FactoryGirl.strategy_by_name(:create).new
end
delegate :association, to: :@strategy
def result(evaluation)
result = @strategy.result(evaluation)
evaluation.notify(:before_json, result)
result.to_json.tap do |json|
evaluation.notify(:after_json, json)
evaluation.notify(:make_json_awesome, json)
end
end
end
FactoryGirl.register_strategy(:json, JsonStrategy)
FactoryGirl.define do
factory :user do
before(:json) {|user| do_something_to(user) }
after(:json) {|user_json| do_something_to(user_json) }
callback(:make_json_awesome) {|user_json| do_something_to(user_json) }
end
end
```
Custom Methods to Persist Objects
---------------------------------

View File

@ -1,3 +1,4 @@
require "set"
require "active_support/core_ext/module/delegation"
require "active_support/notifications"
@ -103,9 +104,26 @@ module FactoryGirl
FactoryGirl.register_strategy(:null, FactoryGirl::Strategy::Null)
end
def self.callbacks
@callbacks ||= Set.new
end
def self.register_default_callbacks
register_callback(:after_build)
register_callback(:after_create)
register_callback(:after_stub)
register_callback(:before_create)
end
def self.callback_names
[:after_build, :after_create, :after_stub, :before_create].freeze
callbacks
end
def self.register_callback(name)
name = name.to_sym
callbacks << name
end
end
FactoryGirl.register_default_strategies
FactoryGirl.register_default_callbacks

View File

@ -159,5 +159,18 @@ module FactoryGirl
def initialize_with(&block)
@definition.define_constructor(&block)
end
def before(name, &block)
callback("before_#{name}", &block)
end
def after(name, &block)
callback("after_#{name}", &block)
end
def callback(name, &block)
FactoryGirl.register_callback(name)
@definition.add_callback(Callback.new(name, block))
end
end
end

View File

@ -21,7 +21,7 @@ describe "using ActiveSupport::Instrumentation to track factory interaction" do
email "john@example.com"
factory :slow_user do
after_build { Kernel.sleep(0.1) }
after(:build) { Kernel.sleep(0.1) }
end
end

View File

@ -6,35 +6,35 @@ describe "callbacks" do
FactoryGirl.define do
factory :user_with_callbacks, class: :user do
after_stub { |user| user.first_name = 'Stubby' }
after_build { |user| user.first_name = 'Buildy' }
after_create { |user| user.last_name = 'Createy' }
after(:stub) { |user| user.first_name = 'Stubby' }
after(:build) { |user| user.first_name = 'Buildy' }
after(:create) { |user| user.last_name = 'Createy' }
end
factory :user_with_inherited_callbacks, parent: :user_with_callbacks do
after_stub { |user| user.last_name = 'Double-Stubby' }
after_build { |user| user.first_name = 'Child-Buildy' }
after(:stub) { |user| user.last_name = 'Double-Stubby' }
after(:build) { |user| user.first_name = 'Child-Buildy' }
end
end
end
it "runs the after_stub callback when stubbing" do
it "runs the after(:stub) callback when stubbing" do
user = FactoryGirl.build_stubbed(:user_with_callbacks)
user.first_name.should == 'Stubby'
end
it "runs the after_build callback when building" do
it "runs the after(:build) callback when building" do
user = FactoryGirl.build(:user_with_callbacks)
user.first_name.should == 'Buildy'
end
it "runs both the after_build and after_create callbacks when creating" do
it "runs both the after(:build) and after(:create) callbacks when creating" do
user = FactoryGirl.create(:user_with_callbacks)
user.first_name.should == 'Buildy'
user.last_name.should == 'Createy'
end
it "runs both the after_stub callback on the factory and the inherited after_stub callback" do
it "runs both the after(:stub) callback on the factory and the inherited after(:stub) callback" do
user = FactoryGirl.build_stubbed(:user_with_inherited_callbacks)
user.first_name.should == 'Stubby'
user.last_name.should == 'Double-Stubby'
@ -56,9 +56,9 @@ describe "callbacks using syntax methods without referencing FactoryGirl explici
sequence(:sequence_3)
factory :user do
after_stub { generate(:sequence_3) }
after_build {|user| user.first_name = generate(:sequence_1) }
after_create {|user, evaluator| user.last_name = generate(:sequence_2) }
after(:stub) { generate(:sequence_3) }
after(:build) {|user| user.first_name = generate(:sequence_1) }
after(:create) {|user, evaluator| user.last_name = generate(:sequence_2) }
end
end
end
@ -76,3 +76,76 @@ describe "callbacks using syntax methods without referencing FactoryGirl explici
FactoryGirl.create(:user).last_name.should == 1
end
end
describe "custom callbacks" do
let(:custom_before) do
Class.new do
def result(evaluation)
evaluation.object.tap do |instance|
evaluation.notify(:before_custom, instance)
end
end
end
end
let(:custom_after) do
Class.new do
def result(evaluation)
evaluation.object.tap do |instance|
evaluation.notify(:after_custom, instance)
end
end
end
end
let(:totally_custom) do
Class.new do
def result(evaluation)
evaluation.object.tap do |instance|
evaluation.notify(:totally_custom, instance)
end
end
end
end
before do
define_model("User", first_name: :string, last_name: :string) do
def name
[first_name, last_name].join(" ")
end
end
FactoryGirl.register_strategy(:custom_before, custom_before)
FactoryGirl.register_strategy(:custom_after, custom_after)
FactoryGirl.register_strategy(:totally_custom, totally_custom)
FactoryGirl.define do
factory :user do
first_name "John"
last_name "Doe"
before(:custom) {|instance| instance.first_name = "Overridden First" }
after(:custom) {|instance| instance.last_name = "Overridden Last" }
callback(:totally_custom) do |instance|
instance.first_name = "Totally"
instance.last_name = "Custom"
end
end
end
end
it "runs a custom before callback when the proper strategy executes" do
FactoryGirl.build(:user).name.should == "John Doe"
FactoryGirl.custom_before(:user).name.should == "Overridden First Doe"
end
it "runs a custom after callback when the proper strategy executes" do
FactoryGirl.build(:user).name.should == "John Doe"
FactoryGirl.custom_after(:user).name.should == "John Overridden Last"
end
it "runs a custom callback without prepending before or after when the proper strategy executes" do
FactoryGirl.build(:user).name.should == "John Doe"
FactoryGirl.totally_custom(:user).name.should == "Totally Custom"
end
end

View File

@ -64,7 +64,7 @@ describe "multiple creates and ignored attributes to dynamically build attribute
posts_count 5
end
after_create do |user, evaluator|
after(:create) do |user, evaluator|
FactoryGirl.create_list(:post, evaluator.posts_count, user: user)
end
end

View File

@ -12,7 +12,7 @@ describe "modifying factories" do
factory :user do
email
after_create do |user|
after(:create) do |user|
user.login = user.name.upcase if user.name
end
@ -58,7 +58,7 @@ describe "modifying factories" do
FactoryGirl.modify do
factory :user do
name "Great User"
after_create do |user|
after(:create) do |user|
user.name = user.name.downcase
user.login = nil
end

View File

@ -78,7 +78,7 @@ describe "nested factories with different parents" do
end
factory :uppercase_male_user, parent: :male_user do
after_build {|user| user.name = user.name.upcase }
after(:build) {|user| user.name = user.name.upcase }
end
end
end

View File

@ -187,11 +187,11 @@ describe "traits with callbacks" do
name "John"
trait :great do
after_create {|user| user.name.upcase! }
after(:create) {|user| user.name.upcase! }
end
trait :awesome do
after_create {|user| user.name = "awesome" }
after(:create) {|user| user.name = "awesome" }
end
factory :caps_user, traits: [:great]
@ -232,7 +232,7 @@ describe "traits added via strategy" do
end
trait :great do
after_create {|user| user.name.upcase! }
after(:create) {|user| user.name.upcase! }
end
end
end

View File

@ -17,7 +17,7 @@ describe "transient attributes" do
name { "#{FactoryGirl.generate(:name)}#{" - Rockstar" if rockstar}" }
email { "#{name.downcase}#{four}@example.com" }
after_create do |user, evaluator|
after(:create) do |user, evaluator|
user.name.upcase! if evaluator.upcased
end
end

View File

@ -126,18 +126,18 @@ describe FactoryGirl::DefinitionProxy, "adding callbacks" do
let(:proxy) { FactoryGirl::DefinitionProxy.new(subject) }
let(:callback) { -> { "my awesome callback!" } }
context "#after_build" do
before { proxy.after_build(&callback) }
context "#after(:build)" do
before { proxy.after(:build, &callback) }
it { should have_callback(:after_build).with_block(callback) }
end
context "#after_create" do
before { proxy.after_create(&callback) }
context "#after(:create)" do
before { proxy.after(:create, &callback) }
it { should have_callback(:after_create).with_block(callback) }
end
context "#after_stub" do
before { proxy.after_stub(&callback) }
context "#after(:stub)" do
before { proxy.after(:stub, &callback) }
it { should have_callback(:after_stub).with_block(callback) }
end
end