Skip to content

Commit

Permalink
Add global callbacks
Browse files Browse the repository at this point in the history
This allows callbacks (after :build, :create, etc.) to be defined at the
FactoryGirl level; this means that the callback will be invoked for all
factories. This is primarily to maintain consistency and follow the
principle of least surprise.

As usual, callbacks are applied from the lowest component to the
highest, meaning that global callbacks will be run after factory and
trait callbacks are run.

    FactoryGirl.define do
      after(:build) {|object| puts "Built #{object}" }

      factory :user
      # ...
    end

Closes #481
Closes #486
  • Loading branch information
joshuaclayton committed Feb 8, 2013
1 parent 9bc0e39 commit 36cb43e
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 19 deletions.
14 changes: 14 additions & 0 deletions GETTING_STARTED.md
Expand Up @@ -748,6 +748,20 @@ factory :user do
end
```

To override callbacks for all factories, define them within the
`FactoryGirl.define` block:

```ruby
FactoryGirl.define do
after(:build) {|object| puts "Built #{object}" }
after(:create) {|object| AuditLog.create(attrs: object.attributes) }

factory :user do
name "John Doe"
end
end
```

Modifying factories
-------------------

Expand Down
2 changes: 1 addition & 1 deletion gemfiles/3.0.gemfile.lock
@@ -1,5 +1,5 @@
PATH
remote: /Users/mjankowski/Development/OpenSource/factory_girl
remote: /Users/joshuaclayton/dev/gems/factory_girl
specs:
factory_girl (4.2.0)
activesupport (>= 3.0.0)
Expand Down
2 changes: 1 addition & 1 deletion lib/factory_girl.rb
Expand Up @@ -55,7 +55,7 @@ def self.reset_configuration
end

class << self
delegate :factories, :sequences, :traits, :strategies, :callback_names,
delegate :factories, :sequences, :traits, :callbacks, :strategies, :callback_names,
:to_create, :skip_create, :initialize_with, :constructor, :duplicate_attribute_assignment_from_initialize_with,
:duplicate_attribute_assignment_from_initialize_with=, to: :configuration
end
Expand Down
3 changes: 2 additions & 1 deletion lib/factory_girl/configuration.rb
Expand Up @@ -15,7 +15,8 @@ def initialize
initialize_with { new }
end

delegate :to_create, :skip_create, :constructor, to: :@definition
delegate :to_create, :skip_create, :constructor, :before, :after,
:callback, :callbacks, to: :@definition

def initialize_with(&block)
@definition.define_constructor(&block)
Expand Down
15 changes: 15 additions & 0 deletions lib/factory_girl/definition.rb
Expand Up @@ -84,6 +84,21 @@ def define_constructor(&block)
@constructor = block
end

def before(*names, &block)
callback(*names.map {|name| "before_#{name}" }, &block)
end

def after(*names, &block)
callback(*names.map {|name| "after_#{name}" }, &block)
end

def callback(*names, &block)
names.each do |name|
FactoryGirl.register_callback(name)
add_callback(Callback.new(name, block))
end
end

private

def base_traits
Expand Down
2 changes: 1 addition & 1 deletion lib/factory_girl/definition_hierarchy.rb
@@ -1,7 +1,7 @@
module FactoryGirl
class DefinitionHierarchy
def callbacks
[]
FactoryGirl.callbacks
end

def constructor
Expand Down
17 changes: 2 additions & 15 deletions lib/factory_girl/definition_proxy.rb
Expand Up @@ -6,6 +6,8 @@ class DefinitionProxy
undef_method(method) unless UNPROXIED_METHODS.include?(method.to_s)
end

delegate :before, :after, :callback, to: :@definition

attr_reader :child_factories

def initialize(definition, ignore = false)
Expand Down Expand Up @@ -157,20 +159,5 @@ def trait(name, &block)
def initialize_with(&block)
@definition.define_constructor(&block)
end

def before(*names, &block)
callback(*names.map {|name| "before_#{name}" }, &block)
end

def after(*names, &block)
callback(*names.map {|name| "after_#{name}" }, &block)
end

def callback(*names, &block)
names.each do |name|
FactoryGirl.register_callback(name)
@definition.add_callback(Callback.new(name, block))
end
end
end
end
8 changes: 8 additions & 0 deletions lib/factory_girl/syntax/default.rb
Expand Up @@ -48,6 +48,14 @@ def initialize_with(&block)
def self.run(block)
new.instance_eval(&block)
end

delegate :before, :after, :callback, to: :configuration

private

def configuration
FactoryGirl.configuration
end
end

class ModifyDSL
Expand Down
51 changes: 51 additions & 0 deletions spec/acceptance/callbacks_spec.rb
Expand Up @@ -175,3 +175,54 @@ def name
expect(FactoryGirl.build_stubbed(:user, name: 'John Doe').name).to eq 'JOHN DOE'
end
end

describe 'global callbacks' do
include FactoryGirl::Syntax::Methods

before do
define_model('User', name: :string)
define_model('Company', name: :string)

FactoryGirl.define do
after :build do |object|
object.name = case object.class.to_s
when 'User' then 'John Doe'
when 'Company' then 'Acme Suppliers'
end
end

after :create do |object|
object.name = "#{object.name}!!!"
end

trait :awesome do
after :build do |object|
object.name = "___#{object.name}___"
end

after :create do |object|
object.name = "A#{object.name}Z"
end
end

factory :user do
after :build do |user|
user.name = user.name.downcase
end
end

factory :company do
after :build do |company|
company.name = company.name.upcase
end
end
end
end

it 'triggers after build callbacks for all factories' do
expect(build(:user).name).to eq 'john doe'
expect(create(:user).name).to eq 'john doe!!!'
expect(create(:user, :awesome).name).to eq 'A___john doe___!!!Z'
expect(build(:company).name).to eq 'ACME SUPPLIERS'
end
end

0 comments on commit 36cb43e

Please sign in to comment.