Skip to content

Commit e477fcd

Browse files
Add per model automatically_define_enum_traits option
Current you can only specify whether to automatically define enum traits at a global level, through `FactoryBot.automatically_define_enum_traits`. This means that an entire codebase has to either opt-in, or opt-out from automatically defining enum traits. If you are in a large, established codebase with lots of enum's, this is quite hard to change globally when you find that automatically defining them doesn't fit for your new use case. If we could override this at a per-factory level, we could allow individual factories to override the global setting where appropriate, in order to do customise them where necessary. Given `FactoryBot.automatically_define_enum_traits` being set to `true`, and a model called `Task` with the following enum definition: ``` class Task enum :confirmed_by, [:user, :organisation], prefix: true end ``` You would be able to override disable this on a per-factory basis like so: ``` FactoryBot.define do factory :task, automatically_define_enum_traits: false do confirmed_by { :user } trait :org_confirmed do confirmed_by { :organisation } end end end ``` If `FactoryBot.automatically_define_enum_traits` was instead set to `false`, then the same model with a factory override set to `false` you would end up with the following automatic traits: ``` FactoryBot.define do factory :task, automatically_define_enum_traits: true do # The :user and :organisation traits below would be automatically # defined in the following way: # # trait :user do # confirmed_by { :user } # end # trait :organisation do # confirmed_by { :organisation } # end end end ``` Fixes: thoughtbot#1597 Co-Authored-By: Julia Chan <[email protected]>
1 parent 9b9b24f commit e477fcd

File tree

4 files changed

+94
-7
lines changed

4 files changed

+94
-7
lines changed

docs/src/traits/enum.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,25 @@ FactoryBot.define do
5959
end
6060
```
6161

62+
If you want to override `FactoryBot.automatically_define_enum_traits` on a
63+
per-model basis, you can use an additional attribute on your factory:
64+
65+
```rb
66+
FactoryBot.define do
67+
factory :task, automatically_define_enum_traits: false do
68+
status { :queued }
69+
70+
trait :in_progress do
71+
status { :started }
72+
end
73+
74+
trait :complete do
75+
status {:finished }
76+
end
77+
end
78+
end
79+
```
80+
6281
It is also possible to use this feature for other enumerable values, not
6382
specifically tied to Active Record enum attributes.
6483

lib/factory_bot/definition.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module FactoryBot
33
class Definition
44
attr_reader :defined_traits, :declarations, :name, :registered_enums
55

6-
def initialize(name, base_traits = [])
6+
def initialize(name, base_traits = [], automatically_define_enum_traits = nil)
77
@name = name
88
@declarations = DeclarationList.new(name)
99
@callbacks = []
@@ -15,6 +15,7 @@ def initialize(name, base_traits = [])
1515
@constructor = nil
1616
@attributes = nil
1717
@compiled = false
18+
@automatically_define_enum_traits = automatically_define_enum_traits
1819
@expanded_enum_traits = false
1920
end
2021

@@ -195,8 +196,13 @@ def automatically_register_defined_enums(klass)
195196
end
196197

197198
def automatically_register_defined_enums?(klass)
198-
FactoryBot.automatically_define_enum_traits &&
199-
klass.respond_to?(:defined_enums)
199+
automatically_define_enum_traits = if @automatically_define_enum_traits.nil?
200+
FactoryBot.automatically_define_enum_traits
201+
else
202+
@automatically_define_enum_traits
203+
end
204+
205+
automatically_define_enum_traits && klass.respond_to?(:defined_enums)
200206
end
201207
end
202208
end

lib/factory_bot/factory.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def initialize(name, options = {})
1212
@parent = options[:parent]
1313
@aliases = options[:aliases] || []
1414
@class_name = options[:class]
15-
@definition = Definition.new(@name, options[:traits] || [])
15+
@definition = Definition.new(@name, options[:traits] || [], options[:automatically_define_enum_traits])
1616
@compiled = false
1717
end
1818

@@ -140,7 +140,7 @@ def compiled_constructor
140140
private
141141

142142
def assert_valid_options(options)
143-
options.assert_valid_keys(:class, :parent, :aliases, :traits)
143+
options.assert_valid_keys(:class, :parent, :aliases, :traits, :automatically_define_enum_traits)
144144
end
145145

146146
def parent

spec/acceptance/enum_traits_spec.rb

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
describe "enum traits" do
2-
context "when automatically_define_enum_traits is true" do
2+
context "when FactoryBot.automatically_define_enum_traits is true" do
33
it "builds traits automatically for model enum field" do
44
define_model("Task", status: :integer) do
55
enum status: {queued: 0, started: 1, finished: 2}
@@ -113,9 +113,49 @@ def each(&block)
113113
expect(task.status).to eq(trait_name)
114114
end
115115
end
116+
117+
context "when the factory specifies automatically_define_enum_traits as false" do
118+
it "raises an error for undefined traits" do
119+
define_model("Task", status: :integer) do
120+
enum status: {queued: 0, started: 1, finished: 2}
121+
end
122+
123+
FactoryBot.define do
124+
factory :task, automatically_define_enum_traits: false
125+
end
126+
127+
Task.statuses.each_key do |trait_name|
128+
expect { FactoryBot.build(:task, trait_name) }.to raise_error(
129+
KeyError, "Trait not registered: \"#{trait_name}\""
130+
)
131+
end
132+
133+
Task.reset_column_information
134+
end
135+
136+
it "builds traits for each enumerated value when traits_for_enum are specified" do
137+
define_model("Task", status: :integer) do
138+
enum status: {queued: 0, started: 1, finished: 2}
139+
end
140+
141+
FactoryBot.define do
142+
factory :task, automatically_define_enum_traits: false do
143+
traits_for_enum(:status)
144+
end
145+
end
146+
147+
Task.statuses.each_key do |trait_name|
148+
task = FactoryBot.build(:task, trait_name)
149+
150+
expect(task.status).to eq(trait_name)
151+
end
152+
153+
Task.reset_column_information
154+
end
155+
end
116156
end
117157

118-
context "when automatically_define_enum_traits is false" do
158+
context "when FactoryBot.automatically_define_enum_traits is false" do
119159
it "raises an error for undefined traits" do
120160
with_temporary_assignment(FactoryBot, :automatically_define_enum_traits, false) do
121161
define_model("Task", status: :integer) do
@@ -157,5 +197,27 @@ def each(&block)
157197
Task.reset_column_information
158198
end
159199
end
200+
201+
context "when the factory specifies automatically_define_enum_traits as true" do
202+
it "builds traits automatically for model enum field" do
203+
with_temporary_assignment(FactoryBot, :automatically_define_enum_traits, false) do
204+
define_model("Task", status: :integer) do
205+
enum status: {queued: 0, started: 1, finished: 2}
206+
end
207+
208+
FactoryBot.define do
209+
factory :task, automatically_define_enum_traits: true
210+
end
211+
212+
Task.statuses.each_key do |trait_name|
213+
task = FactoryBot.build(:task, trait_name)
214+
215+
expect(task.status).to eq(trait_name)
216+
end
217+
218+
Task.reset_column_information
219+
end
220+
end
221+
end
160222
end
161223
end

0 commit comments

Comments
 (0)