Skip to content

Commit f14a63e

Browse files
Add support for before build callback (#1760)
* add_before_build_callback * 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 * fix rubocop offenses * Move `before_build` callback inside the strategies * Update documentation to include `before(:build)` --------- Co-authored-by: Mohammed Nasser <[email protected]> Co-authored-by: mohammednasser-32 <[email protected]>
1 parent 6b67f18 commit f14a63e

File tree

6 files changed

+110
-39
lines changed

6 files changed

+110
-39
lines changed

docs/src/callbacks/callback_order.md

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,35 @@ FactoryBot.define do
1717
factory :user do
1818
before(:all) { puts "User before(:all)" }
1919
after(:all) { puts "User after(:all)" }
20-
after(:build) { puts "User after(:build)"
21-
20+
before(:build) { puts "User before(:build)" }
21+
after(:build) { puts "User after(:build)" }
22+
2223
trait :trait_a do
23-
after(:build) { puts "Trait-A after(:build)"
24+
before(:build) { puts "Trait-A before(:build)" }
25+
after(:build) { puts "Trait-A after(:build)" }
2426
end
25-
26-
trait :trait_b do
27+
28+
trait :trait_b do
29+
before(:build) { puts "Trait-B before(:build)" }
2730
after(:build) { puts "Trait-B after(:build)" }
2831
end
2932
end
3033
end
3134

32-
build(:user, :trait_b, :trait_a)
35+
build(:user, :trait_b, :trait_a)
3336

3437
# Result:
3538
#
3639
# 1. "Global before(:all)"
3740
# 2. "User before(:all)"
38-
# 3. "User after(:build)"
39-
# 4. "Trait-B after(:build)"
40-
# 5. "Trait-A after(:build)"
41-
# 6. "Global after(:all)"
42-
# 7. "User after(:all)"
41+
# 3. "User before(:build)
42+
# 4. "Trait-B before(:build)"
43+
# 5. "Trait-A before(:build)"
44+
# 6. "User after(:build)"
45+
# 7. "Trait-B after(:build)"
46+
# 8. "Trait-A after(:build)"
47+
# 9. "Global after(:all)"
48+
# 10. "User after(:all)"
4349

4450
```
4551

@@ -49,53 +55,64 @@ build(:user, :trait_b, :trait_a)
4955
```ruby
5056
FactoryBot.define do
5157
before(:all) { puts "Global before(:all)" }
58+
before(:build) { puts "Global before(:build)" }
5259
after(:build) { puts "Global after(:build)" }
5360
after(:all) { puts "Global after(:all)" }
5461

5562
factory :parent do
5663
before(:all) { puts "Parent before(:all)" }
64+
before(:build) { puts "Parent before(:build)" }
5765
after(:all) { puts "Parent after(:all)" }
5866
after(:build) { puts "Parent after(:build)" }
59-
60-
trait :trait_a do
67+
68+
trait :trait_a do
69+
before(:build) { puts "Trait-A before(:build)" }
6170
after(:build) { puts "Trait-A after(:build)" }
6271
end
6372

6473
factory :child do
6574
before(:all) { puts "Child before(:all)" }
75+
before(:build) { puts "Child before(:build)" }
6676
after(:build) { puts "Child after(:build)" }
6777
after(:all) { puts "Child after(:all)" }
6878

6979
trait :trait_b do
80+
before(:build) { puts "Trait-B before(:build)" }
7081
after(:build) { puts "Trait-B after(:build)" }
7182
after(:all) { puts "Trait-B after(:all)" }
7283
end
7384

7485
trait :trait_c do
86+
before(:build) { puts "Trait-C before(:build)" }
7587
after(:build) { puts "Trait-C after(:build)" }
7688
before(:all) { puts "Trait-C before(:all)" }
7789
end
7890
end
7991
end
8092
end
8193

82-
build(:child, :trait_c, :trait_a, :trait_b)
94+
build(:child, :trait_c, :trait_a, :trait_b)
8395

8496
# Result:
8597
#
8698
# 1. "Global before(:all)"
8799
# 2. "Parent before(:all)"
88100
# 3. "Child before(:all)"
89101
# 4. "Trait-C before(:all)"
90-
# 5. "Global after(:build)"
91-
# 6. "Parent after(:build)"
92-
# 7. "Child after(:build)"
93-
# 8. "Trait-C after(:build)"
94-
# 9. "Trait-A after(:build)"
95-
# 10. "Trait-B after(:build)"
96-
# 11. "Global after(:all)"
97-
# 12. "Parent after(:all)"
98-
# 13. "Child after(:all)"
99-
# 14. "Trait-B after(:all)"
100-
102+
# 5. "Global before(:build)"
103+
# 6. "Parent before(:build)"
104+
# 7. "Child before(:build)"
105+
# 8. "Trait-C before(:build)"
106+
# 9. "Trait-A before(:build)"
107+
# 10. "Trait-B before(:build)"
108+
# 11. "Global after(:build)"
109+
# 12. "Parent after(:build)"
110+
# 13. "Child after(:build)"
111+
# 14. "Trait-C after(:build)"
112+
# 15. "Trait-A after(:build)"
113+
# 16. "Trait-B after(:build)"
114+
# 17. "Global after(:all)"
115+
# 18. "Parent after(:all)"
116+
# 19. "Child after(:all)"
117+
# 20. "Trait-B after(:all)"
101118
```

docs/src/callbacks/default-callbacks.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
factory\_bot makes available four callbacks for injecting some code:
44

5-
* after(:build) - called after a factory is built (via `FactoryBot.build`, `FactoryBot.create`)
6-
* before(:create) - called before a factory is saved (via `FactoryBot.create`)
7-
* after(:create) - called after a factory is saved (via `FactoryBot.create`)
8-
* after(:stub) - called after a factory is stubbed (via `FactoryBot.build_stubbed`)
5+
* before(:all) - called before any strategy is used (e.g., `FactoryBot.build`, `FactoryBot.create`, `FactoryBot.build_stubbed`)
6+
* before(:build) - called before a factory is built (via `FactoryBot.build`, `FactoryBot.create`)
7+
* after(:build) - called after a factory is built (via `FactoryBot.build`, `FactoryBot.create`)
8+
* before(:create) - called before a factory is saved (via `FactoryBot.create`)
9+
* after(:create) - called after a factory is saved (via `FactoryBot.create`)
10+
* after(:stub) - called after a factory is stubbed (via `FactoryBot.build_stubbed`)
11+
* after(:all) - called after any strategy is used (e.g., `FactoryBot.build`, `FactoryBot.create`, `FactoryBot.build_stubbed`)
912

1013
Examples:
1114

docs/src/callbacks/summary.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@ factory\_bot makes six callbacks available:
44

55
| Callback | Timing |
66
| --------------- | ------------------------------------------------------------------------------------------------------------------------- |
7-
| before(:all) | called before a factory constructs an object (via `FactoryBot.build`, `FactoryBot.create`, or `FactoryBot.build_stubbed`) |
7+
| before(:all) | called before any strategy is used to construct an object, including custom strategies |
8+
| before(:build) | called before a factory builds an object (via `FactoryBot.build` or `FactoryBot.create`) |
89
| after(:build) | called after a factory builds an object (via `FactoryBot.build` or `FactoryBot.create`) |
910
| before(:create) | called before a factory saves an object (via `FactoryBot.create`) |
1011
| after(:create) | called after a factory saves an object (via `FactoryBot.create`) |
1112
| after(:stub) | called after a factory stubs an object (via `FactoryBot.build_stubbed`) |
12-
| after(:all) | called after a factory constructs an object (via `FactoryBot.build`, `FactoryBot.create`, or `FactoryBot.build_stubbed`) |
13+
| after(:all) | called after any strategy has completed, including custom strategies |
1314

1415

1516
## Examples
1617

1718
### Calling an object's own method after building
1819

1920
```ruby
20-
##
21+
##
2122
# Define a factory that calls the generate_hashed_password method
2223
# after the user factory is built.
2324
#
@@ -32,11 +33,11 @@ end
3233

3334
```ruby
3435
##
35-
# Disable a model's own :after_create callback that sends an email
36+
# Disable a model's own :after_create callback that sends an email
3637
# on creation, then re-enable it afterwards
3738
#
3839
factory :user do
3940
before(:all){ User.skip_callback(:create, :after, :send_welcome_email) }
4041
after(:all){ User.set_callback(:create, :after, :send_welcome_email) }
4142
end
42-
```
43+
```

lib/factory_bot/strategy/build.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ def association(runner)
66
end
77

88
def result(evaluation)
9+
evaluation.notify(:before_build, nil)
10+
911
evaluation.object.tap do |instance|
1012
evaluation.notify(:after_build, instance)
1113
end

lib/factory_bot/strategy/create.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ def association(runner)
66
end
77

88
def result(evaluation)
9+
evaluation.notify(:before_build, nil)
10+
911
evaluation.object.tap do |instance|
1012
evaluation.notify(:after_build, instance)
1113
evaluation.notify(:before_create, instance)

spec/acceptance/callbacks_spec.rb

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,23 @@
7171
FactoryBot.define do
7272
before(:all) { TestLog << "global before-all called" }
7373
after(:all) { TestLog << "global after-all called" }
74+
before(:build) { TestLog << "global before-build called" }
7475
after(:build) { TestLog << "global after-build called" }
7576
before(:create) { TestLog << "global before-create called" }
7677
after(:create) { TestLog << "global after-create called" }
7778

7879
factory :parent, class: :user do
7980
before(:all) { TestLog << "parent before-all called" }
8081
after(:all) { TestLog << "parent after-all called" }
82+
before(:build) { TestLog << "parent before-build called" }
8183
after(:build) { TestLog << "parent after-build called" }
8284
before(:create) { TestLog << "parent before-create called" }
8385
after(:create) { TestLog << "parent after-create called" }
8486

8587
trait :parent_trait_1 do
8688
before(:all) { TestLog << "parent-trait-1 before-all called" }
8789
after(:all) { TestLog << "parent-trait-1 after-all called" }
90+
before(:build) { TestLog << "parent-trait-1 before-build called" }
8891
after(:build) { TestLog << "parent-trait-1 after-build called" }
8992
before(:create) { TestLog << "parent-trait-1 before-create called" }
9093
after(:create) { TestLog << "parent-trait-1 after-create called" }
@@ -93,6 +96,7 @@
9396
trait :parent_trait_2 do
9497
before(:all) { TestLog << "parent-trait-2 before-all called" }
9598
after(:all) { TestLog << "parent-trait-2 after-all called" }
99+
before(:build) { TestLog << "parent-trait-2 before-build called" }
96100
after(:build) { TestLog << "parent-trait-2 after-build called" }
97101
before(:create) { TestLog << "parent-trait-2 before-create called" }
98102
after(:create) { TestLog << "parent-trait-2 after-create called" }
@@ -103,12 +107,14 @@
103107
before(:all) { TestLog << "child before-all called" }
104108
after(:create) { TestLog << "child after-create called" }
105109
after(:build) { TestLog << "child after-build called" }
110+
before(:build) { TestLog << "child before-build called" }
106111
before(:create) { TestLog << "child before-create called" }
107112
after(:all) { TestLog << "child after-all called" }
108113

109114
trait :child_trait do
110115
before(:all) { TestLog << "child-trait before-all called" }
111116
after(:all) { TestLog << "child-trait after-all called" }
117+
before(:build) { TestLog << "child-trait before-build called" }
112118
after(:build) { TestLog << "child-trait after-build called" }
113119
before(:create) { TestLog << "child-trait before-create called" }
114120
after(:create) { TestLog << "child-trait after-create called" }
@@ -125,7 +131,7 @@
125131
#
126132
FactoryBot.create(:child, :parent_trait_2, :child_trait, :parent_trait_1)
127133

128-
expect(TestLog.size).to eq 30
134+
expect(TestLog.size).to eq 36
129135

130136
# before(:all)
131137
expect(TestLog[0..5]).to eq [
@@ -137,8 +143,18 @@
137143
"parent-trait-1 before-all called"
138144
]
139145

140-
# after(:build)
146+
# before(:build)
141147
expect(TestLog[6..11]).to eq [
148+
"global before-build called",
149+
"parent before-build called",
150+
"child before-build called",
151+
"parent-trait-2 before-build called",
152+
"child-trait before-build called",
153+
"parent-trait-1 before-build called"
154+
]
155+
156+
# after(:build)
157+
expect(TestLog[12..17]).to eq [
142158
"global after-build called",
143159
"parent after-build called",
144160
"child after-build called",
@@ -148,7 +164,7 @@
148164
]
149165

150166
# before(:create)
151-
expect(TestLog[12..17]).to eq [
167+
expect(TestLog[18..23]).to eq [
152168
"global before-create called",
153169
"parent before-create called",
154170
"child before-create called",
@@ -158,7 +174,7 @@
158174
]
159175

160176
# after(:create)
161-
expect(TestLog[18..23]).to eq [
177+
expect(TestLog[24..29]).to eq [
162178
"global after-create called",
163179
"parent after-create called",
164180
"child after-create called",
@@ -168,7 +184,7 @@
168184
]
169185

170186
# after(:all)
171-
expect(TestLog[24..29]).to eq [
187+
expect(TestLog[30..35]).to eq [
172188
"global after-all called",
173189
"parent after-all called",
174190
"child after-all called",
@@ -463,3 +479,33 @@ def name
463479
expect(build(:company).name).to eq "ACME SUPPLIERS"
464480
end
465481
end
482+
483+
describe "before build callback" do
484+
before do
485+
define_class("TitleSetter") do
486+
def self.title=(new_title)
487+
class_variable_set(:@@title, new_title)
488+
end
489+
490+
def self.title
491+
class_variable_get(:@@title)
492+
end
493+
end
494+
495+
define_model("Article", title: :string)
496+
497+
FactoryBot.define do
498+
factory :article_with_before_callbacks, class: :article do
499+
before(:build) { TitleSetter.title = "title from before build" }
500+
after(:build) { TitleSetter.title = "title from after build" }
501+
502+
title { TitleSetter.title }
503+
end
504+
end
505+
end
506+
507+
it "runs the before callback" do
508+
article = FactoryBot.build(:article_with_before_callbacks)
509+
expect(article.title).to eq("title from before build")
510+
end
511+
end

0 commit comments

Comments
 (0)