From 6cf129e9fbc70c8cd3a21898f419cae8fdf25011 Mon Sep 17 00:00:00 2001 From: Mohammed Nasser <135416851+mohammednasser-32@users.noreply.github.com> Date: Sun, 28 Apr 2024 22:44:04 +0300 Subject: [PATCH 1/5] add_before_build_callbcak --- lib/factory_bot/strategy/build.rb | 1 + lib/factory_bot/strategy/create.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/factory_bot/strategy/build.rb b/lib/factory_bot/strategy/build.rb index d18bb8ff4..45720acb9 100644 --- a/lib/factory_bot/strategy/build.rb +++ b/lib/factory_bot/strategy/build.rb @@ -7,6 +7,7 @@ def association(runner) def result(evaluation) evaluation.object.tap do |instance| + evaluation.notify(:before_build, instance) evaluation.notify(:after_build, instance) end end diff --git a/lib/factory_bot/strategy/create.rb b/lib/factory_bot/strategy/create.rb index c9371f42d..a0ea8586f 100644 --- a/lib/factory_bot/strategy/create.rb +++ b/lib/factory_bot/strategy/create.rb @@ -7,6 +7,7 @@ def association(runner) def result(evaluation) evaluation.object.tap do |instance| + evaluation.notify(:before_build, instance) evaluation.notify(:after_build, instance) evaluation.notify(:before_create, instance) evaluation.create(instance) From e405a258cc6a83201c9307c7ba0926671d2865e5 Mon Sep 17 00:00:00 2001 From: Mohammed Nasser <135416851+mohammednasser-32@users.noreply.github.com> Date: Tue, 30 Apr 2024 18:55:56 +0300 Subject: [PATCH 2/5] fix before_build_callback change the approach for calling the before_build_callback, now it is called before the object is built, and thus called without any attributes --- lib/factory_bot/callback.rb | 8 ++++++++ lib/factory_bot/factory.rb | 7 +++++++ lib/factory_bot/strategy/build.rb | 1 - lib/factory_bot/strategy/create.rb | 1 - spec/acceptance/callbacks_spec.rb | 30 ++++++++++++++++++++++++++++++ spec/factory_bot/callback_spec.rb | 11 +++++++++++ 6 files changed, 56 insertions(+), 2 deletions(-) diff --git a/lib/factory_bot/callback.rb b/lib/factory_bot/callback.rb index 1766cc808..c11f07168 100644 --- a/lib/factory_bot/callback.rb +++ b/lib/factory_bot/callback.rb @@ -7,6 +7,10 @@ def initialize(name, block) @block = block end + def run_before_build + syntax_runner.instance_exec(&block) + end + def run(instance, evaluator) case block.arity when 1, -1, -2 then syntax_runner.instance_exec(instance, &block) @@ -20,6 +24,10 @@ def ==(other) block == other.block end + def before_build? + name == :before_build + end + protected attr_reader :block diff --git a/lib/factory_bot/factory.rb b/lib/factory_bot/factory.rb index 08b9701d5..592bcb66e 100644 --- a/lib/factory_bot/factory.rb +++ b/lib/factory_bot/factory.rb @@ -35,6 +35,7 @@ def run(build_strategy, overrides, &block) block ||= ->(result) { result } compile + run_before_build_callbacks strategy = StrategyCalculator.new(build_strategy).strategy.new @@ -146,6 +147,12 @@ def compiled_constructor hierarchy_instance.constructor end + def run_before_build_callbacks + callbacks.each do |callback| + callback.run_before_build if callback.before_build? + end + end + private def assert_valid_options(options) diff --git a/lib/factory_bot/strategy/build.rb b/lib/factory_bot/strategy/build.rb index 45720acb9..d18bb8ff4 100644 --- a/lib/factory_bot/strategy/build.rb +++ b/lib/factory_bot/strategy/build.rb @@ -7,7 +7,6 @@ def association(runner) def result(evaluation) evaluation.object.tap do |instance| - evaluation.notify(:before_build, instance) evaluation.notify(:after_build, instance) end end diff --git a/lib/factory_bot/strategy/create.rb b/lib/factory_bot/strategy/create.rb index a0ea8586f..c9371f42d 100644 --- a/lib/factory_bot/strategy/create.rb +++ b/lib/factory_bot/strategy/create.rb @@ -7,7 +7,6 @@ def association(runner) def result(evaluation) evaluation.object.tap do |instance| - evaluation.notify(:before_build, instance) evaluation.notify(:after_build, instance) evaluation.notify(:before_create, instance) evaluation.create(instance) diff --git a/spec/acceptance/callbacks_spec.rb b/spec/acceptance/callbacks_spec.rb index 1891fe49e..5493abfce 100644 --- a/spec/acceptance/callbacks_spec.rb +++ b/spec/acceptance/callbacks_spec.rb @@ -463,3 +463,33 @@ def name expect(build(:company).name).to eq "ACME SUPPLIERS" end end + +describe "before build callback" do + class TitleSetter + def self.title=(new_title) + @@title = new_title + end + + def self.title + @@title + end + end + + before do + define_model("Article", title: :string) + + FactoryBot.define do + factory :article_with_before_callbacks, class: :article do + before(:build) { TitleSetter.title = "title from before build" } + after(:build) { TitleSetter.title = "title from after build" } + + title { TitleSetter.title } + end + end + end + + it "runs the before callback" do + article = FactoryBot.build(:article_with_before_callbacks) + expect(article.title).to eq("title from before build") + end +end diff --git a/spec/factory_bot/callback_spec.rb b/spec/factory_bot/callback_spec.rb index 238d7d8a5..2ccbb934b 100644 --- a/spec/factory_bot/callback_spec.rb +++ b/spec/factory_bot/callback_spec.rb @@ -24,4 +24,15 @@ FactoryBot::Callback.new(:after_create, ->(one, two) { ran_with = [one, two] }).run(:one, :two) expect(ran_with).to eq [:one, :two] end + + it "runs run_before_build callback without attributes" do + ran_with = nil + FactoryBot::Callback.new(:before_build, -> { ran_with = "before build" }).run_before_build + expect(ran_with).to eq "before build" + end + + it "#before_build?" do + expect(FactoryBot::Callback.new(:before_build, -> {}).before_build?).to be true + expect(FactoryBot::Callback.new(:after_create, -> {}).before_build?).to be false + end end From 29ac95727b737117c66b4f14e6c92b3e2c6c2275 Mon Sep 17 00:00:00 2001 From: mohammednasser-32 Date: Fri, 30 Aug 2024 23:28:05 +0300 Subject: [PATCH 3/5] fix rubocop offenses --- spec/acceptance/callbacks_spec.rb | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/spec/acceptance/callbacks_spec.rb b/spec/acceptance/callbacks_spec.rb index 5493abfce..68c4d7da9 100644 --- a/spec/acceptance/callbacks_spec.rb +++ b/spec/acceptance/callbacks_spec.rb @@ -465,29 +465,29 @@ def name end describe "before build callback" do - class TitleSetter - def self.title=(new_title) - @@title = new_title - end - - def self.title - @@title - end - end - before do + define_class("TitleSetter") do + def self.title=(new_title) + class_variable_set(:@@title, new_title) + end + + def self.title + class_variable_get(:@@title) + end + end + define_model("Article", title: :string) - + FactoryBot.define do factory :article_with_before_callbacks, class: :article do before(:build) { TitleSetter.title = "title from before build" } after(:build) { TitleSetter.title = "title from after build" } - + title { TitleSetter.title } end end end - + it "runs the before callback" do article = FactoryBot.build(:article_with_before_callbacks) expect(article.title).to eq("title from before build") From 452c2d287b5b03719582dfae33ba6d6953f7646d Mon Sep 17 00:00:00 2001 From: Neil Carvalho Date: Fri, 1 Aug 2025 17:12:54 -0300 Subject: [PATCH 4/5] Move `before_build` callback inside the strategies --- lib/factory_bot/callback.rb | 8 -------- lib/factory_bot/factory.rb | 7 ------- lib/factory_bot/strategy/build.rb | 2 ++ lib/factory_bot/strategy/create.rb | 2 ++ spec/acceptance/callbacks_spec.rb | 26 +++++++++++++++++++++----- spec/factory_bot/callback_spec.rb | 11 ----------- 6 files changed, 25 insertions(+), 31 deletions(-) diff --git a/lib/factory_bot/callback.rb b/lib/factory_bot/callback.rb index c11f07168..1766cc808 100644 --- a/lib/factory_bot/callback.rb +++ b/lib/factory_bot/callback.rb @@ -7,10 +7,6 @@ def initialize(name, block) @block = block end - def run_before_build - syntax_runner.instance_exec(&block) - end - def run(instance, evaluator) case block.arity when 1, -1, -2 then syntax_runner.instance_exec(instance, &block) @@ -24,10 +20,6 @@ def ==(other) block == other.block end - def before_build? - name == :before_build - end - protected attr_reader :block diff --git a/lib/factory_bot/factory.rb b/lib/factory_bot/factory.rb index 592bcb66e..08b9701d5 100644 --- a/lib/factory_bot/factory.rb +++ b/lib/factory_bot/factory.rb @@ -35,7 +35,6 @@ def run(build_strategy, overrides, &block) block ||= ->(result) { result } compile - run_before_build_callbacks strategy = StrategyCalculator.new(build_strategy).strategy.new @@ -147,12 +146,6 @@ def compiled_constructor hierarchy_instance.constructor end - def run_before_build_callbacks - callbacks.each do |callback| - callback.run_before_build if callback.before_build? - end - end - private def assert_valid_options(options) diff --git a/lib/factory_bot/strategy/build.rb b/lib/factory_bot/strategy/build.rb index d18bb8ff4..5716359d3 100644 --- a/lib/factory_bot/strategy/build.rb +++ b/lib/factory_bot/strategy/build.rb @@ -6,6 +6,8 @@ def association(runner) end def result(evaluation) + evaluation.notify(:before_build, nil) + evaluation.object.tap do |instance| evaluation.notify(:after_build, instance) end diff --git a/lib/factory_bot/strategy/create.rb b/lib/factory_bot/strategy/create.rb index c9371f42d..d292d6085 100644 --- a/lib/factory_bot/strategy/create.rb +++ b/lib/factory_bot/strategy/create.rb @@ -6,6 +6,8 @@ def association(runner) end def result(evaluation) + evaluation.notify(:before_build, nil) + evaluation.object.tap do |instance| evaluation.notify(:after_build, instance) evaluation.notify(:before_create, instance) diff --git a/spec/acceptance/callbacks_spec.rb b/spec/acceptance/callbacks_spec.rb index 68c4d7da9..16b970fda 100644 --- a/spec/acceptance/callbacks_spec.rb +++ b/spec/acceptance/callbacks_spec.rb @@ -71,6 +71,7 @@ FactoryBot.define do before(:all) { TestLog << "global before-all called" } after(:all) { TestLog << "global after-all called" } + before(:build) { TestLog << "global before-build called" } after(:build) { TestLog << "global after-build called" } before(:create) { TestLog << "global before-create called" } after(:create) { TestLog << "global after-create called" } @@ -78,6 +79,7 @@ factory :parent, class: :user do before(:all) { TestLog << "parent before-all called" } after(:all) { TestLog << "parent after-all called" } + before(:build) { TestLog << "parent before-build called" } after(:build) { TestLog << "parent after-build called" } before(:create) { TestLog << "parent before-create called" } after(:create) { TestLog << "parent after-create called" } @@ -85,6 +87,7 @@ trait :parent_trait_1 do before(:all) { TestLog << "parent-trait-1 before-all called" } after(:all) { TestLog << "parent-trait-1 after-all called" } + before(:build) { TestLog << "parent-trait-1 before-build called" } after(:build) { TestLog << "parent-trait-1 after-build called" } before(:create) { TestLog << "parent-trait-1 before-create called" } after(:create) { TestLog << "parent-trait-1 after-create called" } @@ -93,6 +96,7 @@ trait :parent_trait_2 do before(:all) { TestLog << "parent-trait-2 before-all called" } after(:all) { TestLog << "parent-trait-2 after-all called" } + before(:build) { TestLog << "parent-trait-2 before-build called" } after(:build) { TestLog << "parent-trait-2 after-build called" } before(:create) { TestLog << "parent-trait-2 before-create called" } after(:create) { TestLog << "parent-trait-2 after-create called" } @@ -103,12 +107,14 @@ before(:all) { TestLog << "child before-all called" } after(:create) { TestLog << "child after-create called" } after(:build) { TestLog << "child after-build called" } + before(:build) { TestLog << "child before-build called" } before(:create) { TestLog << "child before-create called" } after(:all) { TestLog << "child after-all called" } trait :child_trait do before(:all) { TestLog << "child-trait before-all called" } after(:all) { TestLog << "child-trait after-all called" } + before(:build) { TestLog << "child-trait before-build called" } after(:build) { TestLog << "child-trait after-build called" } before(:create) { TestLog << "child-trait before-create called" } after(:create) { TestLog << "child-trait after-create called" } @@ -125,7 +131,7 @@ # FactoryBot.create(:child, :parent_trait_2, :child_trait, :parent_trait_1) - expect(TestLog.size).to eq 30 + expect(TestLog.size).to eq 36 # before(:all) expect(TestLog[0..5]).to eq [ @@ -137,8 +143,18 @@ "parent-trait-1 before-all called" ] - # after(:build) + # before(:build) expect(TestLog[6..11]).to eq [ + "global before-build called", + "parent before-build called", + "child before-build called", + "parent-trait-2 before-build called", + "child-trait before-build called", + "parent-trait-1 before-build called" + ] + + # after(:build) + expect(TestLog[12..17]).to eq [ "global after-build called", "parent after-build called", "child after-build called", @@ -148,7 +164,7 @@ ] # before(:create) - expect(TestLog[12..17]).to eq [ + expect(TestLog[18..23]).to eq [ "global before-create called", "parent before-create called", "child before-create called", @@ -158,7 +174,7 @@ ] # after(:create) - expect(TestLog[18..23]).to eq [ + expect(TestLog[24..29]).to eq [ "global after-create called", "parent after-create called", "child after-create called", @@ -168,7 +184,7 @@ ] # after(:all) - expect(TestLog[24..29]).to eq [ + expect(TestLog[30..35]).to eq [ "global after-all called", "parent after-all called", "child after-all called", diff --git a/spec/factory_bot/callback_spec.rb b/spec/factory_bot/callback_spec.rb index 2ccbb934b..238d7d8a5 100644 --- a/spec/factory_bot/callback_spec.rb +++ b/spec/factory_bot/callback_spec.rb @@ -24,15 +24,4 @@ FactoryBot::Callback.new(:after_create, ->(one, two) { ran_with = [one, two] }).run(:one, :two) expect(ran_with).to eq [:one, :two] end - - it "runs run_before_build callback without attributes" do - ran_with = nil - FactoryBot::Callback.new(:before_build, -> { ran_with = "before build" }).run_before_build - expect(ran_with).to eq "before build" - end - - it "#before_build?" do - expect(FactoryBot::Callback.new(:before_build, -> {}).before_build?).to be true - expect(FactoryBot::Callback.new(:after_create, -> {}).before_build?).to be false - end end From 7629f1f0267f7affb589e96fb770a750f8a08f3e Mon Sep 17 00:00:00 2001 From: Neil Carvalho Date: Fri, 1 Aug 2025 17:13:52 -0300 Subject: [PATCH 5/5] Update documentation to include `before(:build)` --- docs/src/callbacks/callback_order.md | 67 ++++++++++++++++--------- docs/src/callbacks/default-callbacks.md | 11 ++-- docs/src/callbacks/summary.md | 11 ++-- 3 files changed, 55 insertions(+), 34 deletions(-) diff --git a/docs/src/callbacks/callback_order.md b/docs/src/callbacks/callback_order.md index 2090e00af..fbbd8feb6 100644 --- a/docs/src/callbacks/callback_order.md +++ b/docs/src/callbacks/callback_order.md @@ -17,29 +17,35 @@ FactoryBot.define do factory :user do before(:all) { puts "User before(:all)" } after(:all) { puts "User after(:all)" } - after(:build) { puts "User after(:build)" - + before(:build) { puts "User before(:build)" } + after(:build) { puts "User after(:build)" } + trait :trait_a do - after(:build) { puts "Trait-A after(:build)" + before(:build) { puts "Trait-A before(:build)" } + after(:build) { puts "Trait-A after(:build)" } end - - trait :trait_b do + + trait :trait_b do + before(:build) { puts "Trait-B before(:build)" } after(:build) { puts "Trait-B after(:build)" } end end end -build(:user, :trait_b, :trait_a) +build(:user, :trait_b, :trait_a) # Result: # # 1. "Global before(:all)" # 2. "User before(:all)" -# 3. "User after(:build)" -# 4. "Trait-B after(:build)" -# 5. "Trait-A after(:build)" -# 6. "Global after(:all)" -# 7. "User after(:all)" +# 3. "User before(:build) +# 4. "Trait-B before(:build)" +# 5. "Trait-A before(:build)" +# 6. "User after(:build)" +# 7. "Trait-B after(:build)" +# 8. "Trait-A after(:build)" +# 9. "Global after(:all)" +# 10. "User after(:all)" ``` @@ -49,29 +55,35 @@ build(:user, :trait_b, :trait_a) ```ruby FactoryBot.define do before(:all) { puts "Global before(:all)" } + before(:build) { puts "Global before(:build)" } after(:build) { puts "Global after(:build)" } after(:all) { puts "Global after(:all)" } factory :parent do before(:all) { puts "Parent before(:all)" } + before(:build) { puts "Parent before(:build)" } after(:all) { puts "Parent after(:all)" } after(:build) { puts "Parent after(:build)" } - - trait :trait_a do + + trait :trait_a do + before(:build) { puts "Trait-A before(:build)" } after(:build) { puts "Trait-A after(:build)" } end factory :child do before(:all) { puts "Child before(:all)" } + before(:build) { puts "Child before(:build)" } after(:build) { puts "Child after(:build)" } after(:all) { puts "Child after(:all)" } trait :trait_b do + before(:build) { puts "Trait-B before(:build)" } after(:build) { puts "Trait-B after(:build)" } after(:all) { puts "Trait-B after(:all)" } end trait :trait_c do + before(:build) { puts "Trait-C before(:build)" } after(:build) { puts "Trait-C after(:build)" } before(:all) { puts "Trait-C before(:all)" } end @@ -79,7 +91,7 @@ FactoryBot.define do end end -build(:child, :trait_c, :trait_a, :trait_b) +build(:child, :trait_c, :trait_a, :trait_b) # Result: # @@ -87,15 +99,20 @@ build(:child, :trait_c, :trait_a, :trait_b) # 2. "Parent before(:all)" # 3. "Child before(:all)" # 4. "Trait-C before(:all)" -# 5. "Global after(:build)" -# 6. "Parent after(:build)" -# 7. "Child after(:build)" -# 8. "Trait-C after(:build)" -# 9. "Trait-A after(:build)" -# 10. "Trait-B after(:build)" -# 11. "Global after(:all)" -# 12. "Parent after(:all)" -# 13. "Child after(:all)" -# 14. "Trait-B after(:all)" - +# 5. "Global before(:build)" +# 6. "Parent before(:build)" +# 7. "Child before(:build)" +# 8. "Trait-C before(:build)" +# 9. "Trait-A before(:build)" +# 10. "Trait-B before(:build)" +# 11. "Global after(:build)" +# 12. "Parent after(:build)" +# 13. "Child after(:build)" +# 14. "Trait-C after(:build)" +# 15. "Trait-A after(:build)" +# 16. "Trait-B after(:build)" +# 17. "Global after(:all)" +# 18. "Parent after(:all)" +# 19. "Child after(:all)" +# 20. "Trait-B after(:all)" ``` diff --git a/docs/src/callbacks/default-callbacks.md b/docs/src/callbacks/default-callbacks.md index f8e3a24d3..e5b14910c 100644 --- a/docs/src/callbacks/default-callbacks.md +++ b/docs/src/callbacks/default-callbacks.md @@ -2,10 +2,13 @@ factory\_bot makes available four callbacks for injecting some code: -* after(:build) - called after a factory is built (via `FactoryBot.build`, `FactoryBot.create`) -* before(:create) - called before a factory is saved (via `FactoryBot.create`) -* after(:create) - called after a factory is saved (via `FactoryBot.create`) -* after(:stub) - called after a factory is stubbed (via `FactoryBot.build_stubbed`) +* before(:all) - called before any strategy is used (e.g., `FactoryBot.build`, `FactoryBot.create`, `FactoryBot.build_stubbed`) +* before(:build) - called before a factory is built (via `FactoryBot.build`, `FactoryBot.create`) +* after(:build) - called after a factory is built (via `FactoryBot.build`, `FactoryBot.create`) +* before(:create) - called before a factory is saved (via `FactoryBot.create`) +* after(:create) - called after a factory is saved (via `FactoryBot.create`) +* after(:stub) - called after a factory is stubbed (via `FactoryBot.build_stubbed`) +* after(:all) - called after any strategy is used (e.g., `FactoryBot.build`, `FactoryBot.create`, `FactoryBot.build_stubbed`) Examples: diff --git a/docs/src/callbacks/summary.md b/docs/src/callbacks/summary.md index e49b26ba6..9f4f45fa7 100644 --- a/docs/src/callbacks/summary.md +++ b/docs/src/callbacks/summary.md @@ -4,12 +4,13 @@ factory\_bot makes six callbacks available: | Callback | Timing | | --------------- | ------------------------------------------------------------------------------------------------------------------------- | -| before(:all) | called before a factory constructs an object (via `FactoryBot.build`, `FactoryBot.create`, or `FactoryBot.build_stubbed`) | +| before(:all) | called before any strategy is used to construct an object, including custom strategies | +| before(:build) | called before a factory builds an object (via `FactoryBot.build` or `FactoryBot.create`) | | after(:build) | called after a factory builds an object (via `FactoryBot.build` or `FactoryBot.create`) | | before(:create) | called before a factory saves an object (via `FactoryBot.create`) | | after(:create) | called after a factory saves an object (via `FactoryBot.create`) | | after(:stub) | called after a factory stubs an object (via `FactoryBot.build_stubbed`) | -| after(:all) | called after a factory constructs an object (via `FactoryBot.build`, `FactoryBot.create`, or `FactoryBot.build_stubbed`) | +| after(:all) | called after any strategy has completed, including custom strategies | ## Examples @@ -17,7 +18,7 @@ factory\_bot makes six callbacks available: ### Calling an object's own method after building ```ruby -## +## # Define a factory that calls the generate_hashed_password method # after the user factory is built. # @@ -32,11 +33,11 @@ end ```ruby ## -# Disable a model's own :after_create callback that sends an email +# Disable a model's own :after_create callback that sends an email # on creation, then re-enable it afterwards # factory :user do before(:all){ User.skip_callback(:create, :after, :send_welcome_email) } after(:all){ User.set_callback(:create, :after, :send_welcome_email) } end -``` \ No newline at end of file +```