factory_bot 5.0.2 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +58 -13
  3. data/GETTING_STARTED.md +745 -149
  4. data/NEWS.md +53 -0
  5. data/README.md +19 -18
  6. data/lib/factory_bot/aliases.rb +4 -4
  7. data/lib/factory_bot/attribute/association.rb +2 -2
  8. data/lib/factory_bot/attribute/dynamic.rb +3 -2
  9. data/lib/factory_bot/attribute_assigner.rb +10 -10
  10. data/lib/factory_bot/attribute_list.rb +1 -1
  11. data/lib/factory_bot/callback.rb +3 -11
  12. data/lib/factory_bot/configuration.rb +6 -6
  13. data/lib/factory_bot/declaration/association.rb +30 -2
  14. data/lib/factory_bot/declaration/implicit.rb +4 -1
  15. data/lib/factory_bot/declaration.rb +1 -1
  16. data/lib/factory_bot/declaration_list.rb +2 -2
  17. data/lib/factory_bot/decorator/invocation_tracker.rb +2 -1
  18. data/lib/factory_bot/decorator.rb +18 -6
  19. data/lib/factory_bot/definition.rb +66 -19
  20. data/lib/factory_bot/definition_hierarchy.rb +1 -11
  21. data/lib/factory_bot/definition_proxy.rb +77 -12
  22. data/lib/factory_bot/enum.rb +27 -0
  23. data/lib/factory_bot/errors.rb +3 -0
  24. data/lib/factory_bot/evaluator.rb +8 -9
  25. data/lib/factory_bot/evaluator_class_definer.rb +1 -1
  26. data/lib/factory_bot/factory.rb +13 -13
  27. data/lib/factory_bot/factory_runner.rb +4 -4
  28. data/lib/factory_bot/find_definitions.rb +1 -1
  29. data/lib/factory_bot/internal.rb +68 -1
  30. data/lib/factory_bot/linter.rb +9 -13
  31. data/lib/factory_bot/null_factory.rb +10 -4
  32. data/lib/factory_bot/null_object.rb +2 -6
  33. data/lib/factory_bot/registry.rb +4 -4
  34. data/lib/factory_bot/reload.rb +1 -2
  35. data/lib/factory_bot/sequence.rb +5 -5
  36. data/lib/factory_bot/strategy/attributes_for.rb +4 -0
  37. data/lib/factory_bot/strategy/build.rb +4 -0
  38. data/lib/factory_bot/strategy/create.rb +4 -0
  39. data/lib/factory_bot/strategy/null.rb +8 -2
  40. data/lib/factory_bot/strategy/stub.rb +20 -5
  41. data/lib/factory_bot/strategy_calculator.rb +1 -1
  42. data/lib/factory_bot/strategy_syntax_method_registrar.rb +12 -1
  43. data/lib/factory_bot/syntax/default.rb +12 -24
  44. data/lib/factory_bot/syntax/methods.rb +3 -3
  45. data/lib/factory_bot/trait.rb +5 -3
  46. data/lib/factory_bot/version.rb +1 -1
  47. data/lib/factory_bot.rb +21 -93
  48. metadata +12 -39
data/NEWS.md CHANGED
@@ -1,5 +1,58 @@
1
1
  # News
2
2
 
3
+ ## 6.2.1 (March 8, 2022)
4
+ * Added: CI testing against truffleruby
5
+ * Changed: Documentation improvements for sequences and traits
6
+ * Fixed: ActiveSupport::Notifications reporting strategy through associations now report as symbols
7
+ * Fixed: `add_attribute` with reserved keywords assigns values correctly
8
+
9
+ ## 6.2.0 (May 7, 2021)
10
+ * Added: support for Ruby 3.0
11
+ * Changed: Include factory or trait name in error messages for missing traits. d05a9a3c
12
+ * Changed: Switched from Travis CI to GitHub Actions
13
+ * Fixed: More Ruby 2.7 kwarg deprecation warnings
14
+
15
+ ## 6.1.0 (July 8, 2020)
16
+ * Added: public reader for the evaluation instance, helpful for building interrelated associations
17
+ * Changed: raise a more helpful error when passing an invalid argument to an association
18
+ * Fixed: Ruby 2.7 kwarg deprecation warnings
19
+
20
+ ## 6.0.2 (June 19, 2020)
21
+ * Fixed: bug causing traits to consume more memory each time they were used
22
+
23
+ ## 6.0.1 (June 19, 2020)
24
+ * Fixed: bug with constant resolution causing unexpected uninitialized constant errors
25
+
26
+ ## 6.0.0 (June 18, 2020)
27
+ * Added: automatic definition of traits for Active Record enum attributes, enabled by default
28
+ (Note that this required changing where factory_bot constantizes the build
29
+ class, which may affect applications that were using abstract factories for
30
+ inheritance. See issue #1409.)
31
+ * Added: `traits_for_enum` method to define traits for non-Active Record enums
32
+ * Added: `build_stubbed_starting_id=` option to define the starting id for `build_stubbed`
33
+ * Removed: deprecated methods on the top-level `FactoryBot` module meant only for internal use
34
+ * Removed: support for EOL versions of Ruby (2.3, 2.4) and Rails (4.2)
35
+ * Removed: support for "abstract" factories with no associated class; use traits instead.
36
+
37
+ ## 5.2.0 (April 24, 2020)
38
+ * Added: Pass index to block for `*_list` methods
39
+ * Deprecated: methods on the top-level `FactoryBot` module meant only for internal use: `callbacks`, `configuration`, `constructor`, `initialize_with`, `register_sequence`, `resent_configuration`, `skip_create`, `to_create`
40
+
41
+ ## 5.1.2 (March 25, 2020)
42
+ * Fixed: Ruby 2.7 keyword deprecation warning in FactoryBot.lint
43
+
44
+ ## 5.1.1 (October 2, 2019)
45
+ * Improved: performance of traits
46
+ * Fixed: registering strategies on JRuby
47
+
48
+ ## 5.1.0 (September 21, 2019)
49
+ * Added: "Did you mean?" style error message to help with typos in association declarations
50
+ * Changed: `NoMethodError` for static attributes now offers a "Did you mean?" style message
51
+ * Fixed: avoid undefining inherited evaluator methods
52
+ * Fixed: avoid stubbing id for records without a primary key
53
+ * Fixed: raise a helpful error for self-referencing traits to avoid a `SystemStackError`
54
+ * Deprecated: methods on the top-level `FactoryBot` module meant only for internal use: `allow_class_lookup`, `allow_class_lookup`=, `register_trait`, `trait_by_name`, `traits`, `sequence_by_name`, `sequences`, `factory_by_name`, `register_factory`, `callback_names`, `register_callback`, `register_default_callbacks`, `register_default_strategies`, `strategies`
55
+
3
56
  ## 5.0.2 (February 22, 2019)
4
57
  * Bugfix: raise "Trait not registered" error when passing invalid trait arguments
5
58
 
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # factory_bot [![Build Status][ci-image]][ci] [![Code Climate][grade-image]][grade] [![Gem Version][version-image]][version] [![Reviewed by Hound][hound-badge-image]][hound]
1
+ # factory_bot [![Build Status][ci-image]][ci] [![Code Climate][grade-image]][grade] [![Gem Version][version-image]][version]
2
2
 
3
3
  factory_bot is a fixtures replacement with a straightforward definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute hashes, and stubbed objects), and support for multiple factories for the same class (user, admin_user, and so on), including factory inheritance.
4
4
 
5
5
  If you want to use factory_bot with Rails, see
6
6
  [factory_bot_rails](https://github.com/thoughtbot/factory_bot_rails).
7
7
 
8
- _[Interested in the history of the project name?](NAME.md)_
8
+ _[Interested in the history of the project name?][NAME]_
9
9
 
10
10
 
11
11
  ### Transitioning from factory\_girl?
@@ -43,13 +43,7 @@ gem install factory_bot
43
43
  Supported Ruby versions
44
44
  -----------------------
45
45
 
46
- The factory_bot 5.x series supports MRI Ruby 2.3+.
47
-
48
- The factory_bot 3.x+ series supports MRI Ruby 1.9. Additionally, factory_bot
49
- 3.6+ supports JRuby 1.6.7.2+ while running in 1.9 mode. See [GETTING_STARTED]
50
- for more information on configuring the JRuby environment.
51
-
52
- For versions of Ruby prior to 1.9, please use factory_bot 2.x.
46
+ Supported Ruby versions are listed in [`.github/workflows/build.yml`](https://github.com/thoughtbot/factory_bot/blob/master/.github/workflows/build.yml)
53
47
 
54
48
  More Information
55
49
  ----------------
@@ -59,30 +53,37 @@ More Information
59
53
  * [Issues](https://github.com/thoughtbot/factory_bot/issues)
60
54
  * [GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS](https://robots.thoughtbot.com/)
61
55
 
62
- You may also find useful information under the [factory_girl tag on Stack Overflow](https://stackoverflow.com/questions/tagged/factory-girl).
56
+ [GETTING_STARTED]: https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md
57
+ [NAME]: https://github.com/thoughtbot/factory_bot/blob/master/NAME.md
58
+
59
+ Useful Tools
60
+ ------------
63
61
 
64
- [GETTING_STARTED]: https://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md
62
+ * [FactoryTrace](https://github.com/djezzzl/factory_trace) - helps to find unused factories and traits.
65
63
 
66
64
  Contributing
67
65
  ------------
68
66
 
69
67
  Please see [CONTRIBUTING.md](https://github.com/thoughtbot/factory_bot/blob/master/CONTRIBUTING.md).
70
68
 
71
- factory_bot was originally written by Joe Ferris and is now maintained by Josh
72
- Clayton. Many improvements and bugfixes were contributed by the [open source
69
+ factory_bot was originally written by Joe Ferris and is maintained by thoughtbot.
70
+ Many improvements and bugfixes were contributed by the [open source
73
71
  community](https://github.com/thoughtbot/factory_bot/graphs/contributors).
74
72
 
75
73
  License
76
74
  -------
77
75
 
78
- factory_bot is Copyright © 2008-2019 Joe Ferris and thoughtbot. It is free
76
+ factory_bot is Copyright © 2008-2020 Joe Ferris and thoughtbot. It is free
79
77
  software, and may be redistributed under the terms specified in the
80
- [LICENSE](/LICENSE) file.
78
+ [LICENSE] file.
79
+
80
+ [LICENSE]: https://github.com/thoughtbot/factory_bot/blob/master/LICENSE
81
+
81
82
 
82
83
  About thoughtbot
83
84
  ----------------
84
85
 
85
- ![thoughtbot](https://presskit.thoughtbot.com/images/thoughtbot-logo-for-readmes.svg)
86
+ ![thoughtbot](https://thoughtbot.com/brand_assets/93:44.svg)
86
87
 
87
88
  factory_bot is maintained and funded by thoughtbot, inc.
88
89
  The names and logos for thoughtbot are trademarks of thoughtbot, inc.
@@ -93,8 +94,8 @@ See [our other projects][community] or
93
94
 
94
95
  [community]: https://thoughtbot.com/community?utm_source=github
95
96
  [hire]: https://thoughtbot.com/hire-us?utm_source=github
96
- [ci-image]: https://travis-ci.org/thoughtbot/factory_bot.svg
97
- [ci]: https://travis-ci.org/thoughtbot/factory_bot?branch=master
97
+ [ci-image]: https://github.com/thoughtbot/factory_bot/actions/workflows/build.yml/badge.svg
98
+ [ci]: https://github.com/thoughtbot/factory_bot/actions?query=workflow%3A.github%2Fworkflows%2Fbuild.yml+branch%3Amaster++
98
99
  [grade-image]: https://codeclimate.com/github/thoughtbot/factory_bot/badges/gpa.svg
99
100
  [grade]: https://codeclimate.com/github/thoughtbot/factory_bot
100
101
  [version-image]: https://badge.fury.io/rb/factory_bot.svg
@@ -5,14 +5,14 @@ module FactoryBot
5
5
 
6
6
  self.aliases = [
7
7
  [/(.+)_id/, '\1'],
8
- [/(.*)/, '\1_id'],
8
+ [/(.*)/, '\1_id']
9
9
  ]
10
10
 
11
11
  def self.aliases_for(attribute)
12
- aliases.map do |(pattern, replace)|
13
- if pattern.match(attribute.to_s)
12
+ aliases.map { |(pattern, replace)|
13
+ if pattern.match?(attribute)
14
14
  attribute.to_s.sub(pattern, replace).to_sym
15
15
  end
16
- end.compact << attribute
16
+ }.compact << attribute
17
17
  end
18
18
  end
@@ -6,12 +6,12 @@ module FactoryBot
6
6
 
7
7
  def initialize(name, factory, overrides)
8
8
  super(name, false)
9
- @factory = factory
9
+ @factory = factory
10
10
  @overrides = overrides
11
11
  end
12
12
 
13
13
  def to_proc
14
- factory = @factory
14
+ factory = @factory
15
15
  overrides = @overrides
16
16
  traits_and_overrides = [factory, overrides].flatten
17
17
  factory_name = traits_and_overrides.shift
@@ -12,10 +12,11 @@ module FactoryBot
12
12
 
13
13
  -> {
14
14
  value = case block.arity
15
- when 1, -1 then instance_exec(self, &block)
15
+ when 1, -1, -2 then instance_exec(self, &block)
16
16
  else instance_exec(&block)
17
- end
17
+ end
18
18
  raise SequenceAbuseError if FactoryBot::Sequence === value
19
+
19
20
  value
20
21
  }
21
22
  end
@@ -3,9 +3,9 @@ module FactoryBot
3
3
  class AttributeAssigner
4
4
  def initialize(evaluator, build_class, &instance_builder)
5
5
  @build_class = build_class
6
- @instance_builder = instance_builder
7
- @evaluator = evaluator
8
- @attribute_list = evaluator.class.attribute_list
6
+ @instance_builder = instance_builder
7
+ @evaluator = evaluator
8
+ @attribute_list = evaluator.class.attribute_list
9
9
  @attribute_names_assigned = []
10
10
  end
11
11
 
@@ -22,9 +22,8 @@ module FactoryBot
22
22
  def hash
23
23
  @evaluator.instance = build_hash
24
24
 
25
- attributes_to_set_on_hash.reduce({}) do |result, attribute|
25
+ attributes_to_set_on_hash.each_with_object({}) do |attribute, result|
26
26
  result[attribute] = get(attribute)
27
- result
28
27
  end
29
28
  end
30
29
 
@@ -33,13 +32,14 @@ module FactoryBot
33
32
  def method_tracking_evaluator
34
33
  @method_tracking_evaluator ||= Decorator::AttributeHash.new(
35
34
  decorated_evaluator,
36
- attribute_names_to_assign,
35
+ attribute_names_to_assign
37
36
  )
38
37
  end
39
38
 
40
39
  def decorated_evaluator
41
- Decorator::InvocationTracker.new(
42
- Decorator::NewConstructor.new(@evaluator, @build_class),
40
+ Decorator::NewConstructor.new(
41
+ Decorator::InvocationTracker.new(@evaluator),
42
+ @build_class
43
43
  )
44
44
  end
45
45
 
@@ -96,11 +96,11 @@ module FactoryBot
96
96
  end
97
97
 
98
98
  def alias_names_to_ignore
99
- @attribute_list.non_ignored.flat_map do |attribute|
99
+ @attribute_list.non_ignored.flat_map { |attribute|
100
100
  override_names.map do |override|
101
101
  attribute.name if ignorable_alias?(attribute, override)
102
102
  end
103
- end.compact
103
+ }.compact
104
104
  end
105
105
 
106
106
  def ignorable_alias?(attribute, override)
@@ -4,7 +4,7 @@ module FactoryBot
4
4
  include Enumerable
5
5
 
6
6
  def initialize(name = nil, attributes = [])
7
- @name = name
7
+ @name = name
8
8
  @attributes = attributes
9
9
  end
10
10
 
@@ -3,16 +3,15 @@ module FactoryBot
3
3
  attr_reader :name
4
4
 
5
5
  def initialize(name, block)
6
- @name = name.to_sym
6
+ @name = name.to_sym
7
7
  @block = block
8
- ensure_valid_callback_name!
9
8
  end
10
9
 
11
10
  def run(instance, evaluator)
12
11
  case block.arity
13
- when 1, -1 then syntax_runner.instance_exec(instance, &block)
12
+ when 1, -1, -2 then syntax_runner.instance_exec(instance, &block)
14
13
  when 2 then syntax_runner.instance_exec(instance, evaluator, &block)
15
- else syntax_runner.instance_exec(&block)
14
+ else syntax_runner.instance_exec(&block)
16
15
  end
17
16
  end
18
17
 
@@ -27,13 +26,6 @@ module FactoryBot
27
26
 
28
27
  private
29
28
 
30
- def ensure_valid_callback_name!
31
- unless FactoryBot.callback_names.include?(name)
32
- raise InvalidCallbackNameError, "#{name} is not a valid callback name. " +
33
- "Valid callback names are #{FactoryBot.callback_names.inspect}"
34
- end
35
- end
36
-
37
29
  def syntax_runner
38
30
  @syntax_runner ||= SyntaxRunner.new
39
31
  end
@@ -7,16 +7,16 @@ module FactoryBot
7
7
  :inline_sequences,
8
8
  :sequences,
9
9
  :strategies,
10
- :traits,
10
+ :traits
11
11
  )
12
12
 
13
13
  def initialize
14
- @factories = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Factory"))
15
- @sequences = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Sequence"))
16
- @traits = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Trait"))
17
- @strategies = Registry.new("Strategy")
14
+ @factories = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Factory"))
15
+ @sequences = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Sequence"))
16
+ @traits = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Trait"))
17
+ @strategies = Registry.new("Strategy")
18
18
  @callback_names = Set.new
19
- @definition = Definition.new(:configuration)
19
+ @definition = Definition.new(:configuration)
20
20
  @inline_sequences = []
21
21
 
22
22
  to_create(&:save!)
@@ -6,6 +6,7 @@ module FactoryBot
6
6
  super(name, false)
7
7
  @options = options.dup
8
8
  @overrides = options.extract_options!
9
+ @factory_name = @overrides.delete(:factory) || name
9
10
  @traits = options
10
11
  end
11
12
 
@@ -21,9 +22,36 @@ module FactoryBot
21
22
 
22
23
  private
23
24
 
25
+ attr_reader :factory_name, :overrides, :traits
26
+
24
27
  def build
25
- factory_name = @overrides[:factory] || name
26
- [Attribute::Association.new(name, factory_name, [@traits, @overrides.except(:factory)].flatten)]
28
+ raise_if_arguments_are_declarations!
29
+
30
+ [
31
+ Attribute::Association.new(
32
+ name,
33
+ factory_name,
34
+ [traits, overrides].flatten
35
+ )
36
+ ]
37
+ end
38
+
39
+ def raise_if_arguments_are_declarations!
40
+ if factory_name.is_a?(Declaration)
41
+ raise ArgumentError.new(<<~MSG)
42
+ Association '#{name}' received an invalid factory argument.
43
+ Did you mean? 'factory: :#{factory_name.name}'
44
+ MSG
45
+ end
46
+
47
+ overrides.each do |attribute, value|
48
+ if value.is_a?(Declaration)
49
+ raise ArgumentError.new(<<~MSG)
50
+ Association '#{name}' received an invalid attribute override.
51
+ Did you mean? '#{attribute}: :#{value.name}'
52
+ MSG
53
+ end
54
+ end
27
55
  end
28
56
  end
29
57
  end
@@ -23,8 +23,11 @@ module FactoryBot
23
23
  def build
24
24
  if FactoryBot.factories.registered?(name)
25
25
  [Attribute::Association.new(name, name, {})]
26
- elsif FactoryBot.sequences.registered?(name)
26
+ elsif FactoryBot::Internal.sequences.registered?(name)
27
27
  [Attribute::Sequence.new(name, name, @ignored)]
28
+ elsif @factory.name.to_s == name.to_s
29
+ message = "Self-referencing trait '#{@name}'"
30
+ raise TraitDefinitionError, message
28
31
  else
29
32
  @factory.inherit_traits([name])
30
33
  []
@@ -8,7 +8,7 @@ module FactoryBot
8
8
  attr_reader :name
9
9
 
10
10
  def initialize(name, ignored = false)
11
- @name = name
11
+ @name = name
12
12
  @ignored = ignored
13
13
  end
14
14
 
@@ -5,8 +5,8 @@ module FactoryBot
5
5
 
6
6
  def initialize(name = nil)
7
7
  @declarations = []
8
- @name = name
9
- @overridable = false
8
+ @name = name
9
+ @overridable = false
10
10
  end
11
11
 
12
12
  def declare_attribute(declaration)
@@ -6,10 +6,11 @@ module FactoryBot
6
6
  @invoked_methods = []
7
7
  end
8
8
 
9
- def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissing
9
+ def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing
10
10
  @invoked_methods << name
11
11
  super
12
12
  end
13
+ ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
13
14
 
14
15
  def __invoked_methods__
15
16
  @invoked_methods.uniq
@@ -6,18 +6,30 @@ module FactoryBot
6
6
  @component = component
7
7
  end
8
8
 
9
- def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissing
10
- @component.send(name, *args, &block)
9
+ if ::Gem::Version.new(::RUBY_VERSION) >= ::Gem::Version.new("2.7")
10
+ class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
11
+ def method_missing(...) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
12
+ @component.send(...)
13
+ end
14
+
15
+ def send(...)
16
+ __send__(...)
17
+ end
18
+ RUBY
19
+ else
20
+ def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing
21
+ @component.send(name, *args, &block)
22
+ end
23
+
24
+ def send(symbol, *args, &block)
25
+ __send__(symbol, *args, &block)
26
+ end
11
27
  end
12
28
 
13
29
  def respond_to_missing?(name, include_private = false)
14
30
  @component.respond_to?(name, true) || super
15
31
  end
16
32
 
17
- def send(symbol, *args, &block)
18
- __send__(symbol, *args, &block)
19
- end
20
-
21
33
  def self.const_missing(name)
22
34
  ::Object.const_get(name)
23
35
  end
@@ -1,19 +1,21 @@
1
1
  module FactoryBot
2
2
  # @api private
3
3
  class Definition
4
- attr_reader :defined_traits, :declarations, :name
4
+ attr_reader :defined_traits, :declarations, :name, :registered_enums
5
5
 
6
6
  def initialize(name, base_traits = [])
7
- @name = name
8
- @declarations = DeclarationList.new(name)
9
- @callbacks = []
10
- @defined_traits = Set.new
11
- @to_create = nil
12
- @base_traits = base_traits
7
+ @name = name
8
+ @declarations = DeclarationList.new(name)
9
+ @callbacks = []
10
+ @defined_traits = Set.new
11
+ @registered_enums = []
12
+ @to_create = nil
13
+ @base_traits = base_traits
13
14
  @additional_traits = []
14
- @constructor = nil
15
- @attributes = nil
16
- @compiled = false
15
+ @constructor = nil
16
+ @attributes = nil
17
+ @compiled = false
18
+ @expanded_enum_traits = false
17
19
  end
18
20
 
19
21
  delegate :declare_attribute, to: :declarations
@@ -28,7 +30,7 @@ module FactoryBot
28
30
  end
29
31
 
30
32
  def to_create(&block)
31
- if block_given?
33
+ if block
32
34
  @to_create = block
33
35
  else
34
36
  aggregate_from_traits_and_self(:to_create) { @to_create }.last
@@ -43,13 +45,15 @@ module FactoryBot
43
45
  aggregate_from_traits_and_self(:callbacks) { @callbacks }
44
46
  end
45
47
 
46
- def compile
48
+ def compile(klass = nil)
47
49
  unless @compiled
50
+ expand_enum_traits(klass) unless klass.nil?
51
+
48
52
  declarations.attributes
49
53
 
50
54
  defined_traits.each do |defined_trait|
51
- base_traits.each { |bt| bt.define_trait defined_trait }
52
- additional_traits.each { |bt| bt.define_trait defined_trait }
55
+ base_traits.each { |bt| bt.define_trait defined_trait }
56
+ additional_traits.each { |at| at.define_trait defined_trait }
53
57
  end
54
58
 
55
59
  @compiled = true
@@ -81,6 +85,10 @@ module FactoryBot
81
85
  @defined_traits.add(trait)
82
86
  end
83
87
 
88
+ def register_enum(enum)
89
+ @registered_enums << enum
90
+ end
91
+
84
92
  def define_constructor(&block)
85
93
  @constructor = block
86
94
  end
@@ -95,7 +103,6 @@ module FactoryBot
95
103
 
96
104
  def callback(*names, &block)
97
105
  names.each do |name|
98
- FactoryBot.register_callback(name)
99
106
  add_callback(Callback.new(name, block))
100
107
  end
101
108
  end
@@ -104,6 +111,20 @@ module FactoryBot
104
111
 
105
112
  def base_traits
106
113
  @base_traits.map { |name| trait_by_name(name) }
114
+ rescue KeyError => error
115
+ raise error_with_definition_name(error)
116
+ end
117
+
118
+ def error_with_definition_name(error)
119
+ message = error.message
120
+ message.insert(
121
+ message.index("\nDid you mean?") || message.length,
122
+ " referenced within \"#{name}\" definition"
123
+ )
124
+
125
+ error.class.new(message).tap do |new_error|
126
+ new_error.set_backtrace(error.backtrace)
127
+ end
107
128
  end
108
129
 
109
130
  def additional_traits
@@ -111,17 +132,19 @@ module FactoryBot
111
132
  end
112
133
 
113
134
  def trait_by_name(name)
114
- trait_for(name) || FactoryBot.trait_by_name(name)
135
+ trait_for(name) || Internal.trait_by_name(name)
115
136
  end
116
137
 
117
138
  def trait_for(name)
118
- defined_traits.detect { |trait| trait.name == name.to_s }
139
+ @defined_traits_by_name ||= defined_traits.each_with_object({}) { |t, memo| memo[t.name] ||= t }
140
+ @defined_traits_by_name[name.to_s]
119
141
  end
120
142
 
121
143
  def initialize_copy(source)
122
144
  super
123
145
  @attributes = nil
124
- @compiled = false
146
+ @compiled = false
147
+ @defined_traits_by_name = nil
125
148
  end
126
149
 
127
150
  def aggregate_from_traits_and_self(method_name, &block)
@@ -130,8 +153,32 @@ module FactoryBot
130
153
  [
131
154
  base_traits.map(&method_name),
132
155
  instance_exec(&block),
133
- additional_traits.map(&method_name),
156
+ additional_traits.map(&method_name)
134
157
  ].flatten.compact
135
158
  end
159
+
160
+ def expand_enum_traits(klass)
161
+ return if @expanded_enum_traits
162
+
163
+ if automatically_register_defined_enums?(klass)
164
+ automatically_register_defined_enums(klass)
165
+ end
166
+
167
+ registered_enums.each do |enum|
168
+ traits = enum.build_traits(klass)
169
+ traits.each { |trait| define_trait(trait) }
170
+ end
171
+
172
+ @expanded_enum_traits = true
173
+ end
174
+
175
+ def automatically_register_defined_enums(klass)
176
+ klass.defined_enums.each_key { |name| register_enum(Enum.new(name)) }
177
+ end
178
+
179
+ def automatically_register_defined_enums?(klass)
180
+ FactoryBot.automatically_define_enum_traits &&
181
+ klass.respond_to?(:defined_enums)
182
+ end
136
183
  end
137
184
  end
@@ -1,16 +1,6 @@
1
1
  module FactoryBot
2
2
  class DefinitionHierarchy
3
- def callbacks
4
- FactoryBot.callbacks
5
- end
6
-
7
- def constructor
8
- FactoryBot.constructor
9
- end
10
-
11
- def to_create
12
- FactoryBot.to_create
13
- end
3
+ delegate :callbacks, :constructor, :to_create, to: Internal
14
4
 
15
5
  def self.build_from_definition(definition)
16
6
  build_to_create(&definition.to_create)