factory_bot 1.0.0.alpha

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 (86) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +9 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +2 -0
  5. data/.simplecov +4 -0
  6. data/.travis.yml +41 -0
  7. data/.yardopts +5 -0
  8. data/Appraisals +19 -0
  9. data/CONTRIBUTING.md +60 -0
  10. data/GETTING_STARTED.md +1387 -0
  11. data/Gemfile +8 -0
  12. data/Gemfile.lock +123 -0
  13. data/LICENSE +19 -0
  14. data/NAME.md +11 -0
  15. data/NEWS +285 -0
  16. data/README.md +102 -0
  17. data/Rakefile +36 -0
  18. data/cucumber.yml +1 -0
  19. data/factory_bot.gemspec +38 -0
  20. data/factory_girl.gemspec +41 -0
  21. data/gemfiles/3.2.gemfile +10 -0
  22. data/gemfiles/3.2.gemfile.lock +112 -0
  23. data/gemfiles/4.0.gemfile +10 -0
  24. data/gemfiles/4.0.gemfile.lock +112 -0
  25. data/gemfiles/4.1.gemfile +10 -0
  26. data/gemfiles/4.1.gemfile.lock +111 -0
  27. data/gemfiles/4.2.gemfile +10 -0
  28. data/gemfiles/4.2.gemfile.lock +111 -0
  29. data/gemfiles/5.0.gemfile +10 -0
  30. data/gemfiles/5.0.gemfile.lock +110 -0
  31. data/lib/factory_bot.rb +154 -0
  32. data/lib/factory_bot/aliases.rb +18 -0
  33. data/lib/factory_bot/attribute.rb +62 -0
  34. data/lib/factory_bot/attribute/association.rb +27 -0
  35. data/lib/factory_bot/attribute/dynamic.rb +24 -0
  36. data/lib/factory_bot/attribute/sequence.rb +16 -0
  37. data/lib/factory_bot/attribute/static.rb +16 -0
  38. data/lib/factory_bot/attribute_assigner.rb +97 -0
  39. data/lib/factory_bot/attribute_list.rb +67 -0
  40. data/lib/factory_bot/callback.rb +40 -0
  41. data/lib/factory_bot/callbacks_observer.rb +21 -0
  42. data/lib/factory_bot/configuration.rb +37 -0
  43. data/lib/factory_bot/declaration.rb +23 -0
  44. data/lib/factory_bot/declaration/association.rb +28 -0
  45. data/lib/factory_bot/declaration/dynamic.rb +26 -0
  46. data/lib/factory_bot/declaration/implicit.rb +33 -0
  47. data/lib/factory_bot/declaration/static.rb +26 -0
  48. data/lib/factory_bot/declaration_list.rb +49 -0
  49. data/lib/factory_bot/decorator.rb +21 -0
  50. data/lib/factory_bot/decorator/attribute_hash.rb +16 -0
  51. data/lib/factory_bot/decorator/class_key_hash.rb +28 -0
  52. data/lib/factory_bot/decorator/disallows_duplicates_registry.rb +13 -0
  53. data/lib/factory_bot/decorator/invocation_tracker.rb +19 -0
  54. data/lib/factory_bot/decorator/new_constructor.rb +12 -0
  55. data/lib/factory_bot/definition.rb +136 -0
  56. data/lib/factory_bot/definition_hierarchy.rb +48 -0
  57. data/lib/factory_bot/definition_proxy.rb +174 -0
  58. data/lib/factory_bot/errors.rb +25 -0
  59. data/lib/factory_bot/evaluation.rb +23 -0
  60. data/lib/factory_bot/evaluator.rb +82 -0
  61. data/lib/factory_bot/evaluator_class_definer.rb +20 -0
  62. data/lib/factory_bot/factory.rb +162 -0
  63. data/lib/factory_bot/factory_runner.rb +33 -0
  64. data/lib/factory_bot/find_definitions.rb +25 -0
  65. data/lib/factory_bot/linter.rb +97 -0
  66. data/lib/factory_bot/null_factory.rb +18 -0
  67. data/lib/factory_bot/null_object.rb +24 -0
  68. data/lib/factory_bot/registry.rb +38 -0
  69. data/lib/factory_bot/reload.rb +8 -0
  70. data/lib/factory_bot/sequence.rb +62 -0
  71. data/lib/factory_bot/strategy/attributes_for.rb +13 -0
  72. data/lib/factory_bot/strategy/build.rb +15 -0
  73. data/lib/factory_bot/strategy/create.rb +18 -0
  74. data/lib/factory_bot/strategy/null.rb +11 -0
  75. data/lib/factory_bot/strategy/stub.rb +108 -0
  76. data/lib/factory_bot/strategy_calculator.rb +26 -0
  77. data/lib/factory_bot/strategy_syntax_method_registrar.rb +54 -0
  78. data/lib/factory_bot/syntax.rb +7 -0
  79. data/lib/factory_bot/syntax/default.rb +76 -0
  80. data/lib/factory_bot/syntax/methods.rb +111 -0
  81. data/lib/factory_bot/syntax_runner.rb +6 -0
  82. data/lib/factory_bot/trait.rb +30 -0
  83. data/lib/factory_bot/version.rb +3 -0
  84. data/lib/factory_girl.rb +5 -0
  85. data/lib/factory_girl/version.rb +1 -0
  86. metadata +302 -0
@@ -0,0 +1,18 @@
1
+ module FactoryBot
2
+ class << self
3
+ attr_accessor :aliases
4
+ end
5
+
6
+ self.aliases = [
7
+ [/(.+)_id/, '\1'],
8
+ [/(.*)/, '\1_id']
9
+ ]
10
+
11
+ def self.aliases_for(attribute)
12
+ aliases.map do |(pattern, replace)|
13
+ if pattern.match(attribute.to_s)
14
+ attribute.to_s.sub(pattern, replace).to_sym
15
+ end
16
+ end.compact << attribute
17
+ end
18
+ end
@@ -0,0 +1,62 @@
1
+ require 'factory_bot/attribute/static'
2
+ require 'factory_bot/attribute/dynamic'
3
+ require 'factory_bot/attribute/association'
4
+ require 'factory_bot/attribute/sequence'
5
+
6
+ module FactoryBot
7
+ # @api private
8
+ class Attribute
9
+ attr_reader :name, :ignored
10
+
11
+ def initialize(name, ignored)
12
+ @name = name.to_sym
13
+ @ignored = ignored
14
+ ensure_non_attribute_writer!
15
+ end
16
+
17
+ def to_proc
18
+ -> { }
19
+ end
20
+
21
+ def association?
22
+ false
23
+ end
24
+
25
+ def alias_for?(attr)
26
+ FactoryBot.aliases_for(attr).include?(name)
27
+ end
28
+
29
+ private
30
+
31
+ def ensure_non_attribute_writer!
32
+ NonAttributeWriterValidator.new(@name).validate!
33
+ end
34
+
35
+ class NonAttributeWriterValidator
36
+ def initialize(method_name)
37
+ @method_name = method_name.to_s
38
+ @method_name_setter_match = @method_name.match(/(.*)=$/)
39
+ end
40
+
41
+ def validate!
42
+ if method_is_writer?
43
+ raise AttributeDefinitionError, error_message
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def method_is_writer?
50
+ !!@method_name_setter_match
51
+ end
52
+
53
+ def attribute_name
54
+ @method_name_setter_match[1]
55
+ end
56
+
57
+ def error_message
58
+ "factory_bot uses '#{attribute_name} value' syntax rather than '#{attribute_name} = value'"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,27 @@
1
+ module FactoryBot
2
+ class Attribute
3
+ # @api private
4
+ class Association < Attribute
5
+ attr_reader :factory
6
+
7
+ def initialize(name, factory, overrides)
8
+ super(name, false)
9
+ @factory = factory
10
+ @overrides = overrides
11
+ end
12
+
13
+ def to_proc
14
+ factory = @factory
15
+ overrides = @overrides
16
+ traits_and_overrides = [factory, overrides].flatten
17
+ factory_name = traits_and_overrides.shift
18
+
19
+ -> { association(factory_name, *traits_and_overrides) }
20
+ end
21
+
22
+ def association?
23
+ true
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ module FactoryBot
2
+ class Attribute
3
+ # @api private
4
+ class Dynamic < Attribute
5
+ def initialize(name, ignored, block)
6
+ super(name, ignored)
7
+ @block = block
8
+ end
9
+
10
+ def to_proc
11
+ block = @block
12
+
13
+ -> {
14
+ value = case block.arity
15
+ when 1, -1 then instance_exec(self, &block)
16
+ else instance_exec(&block)
17
+ end
18
+ raise SequenceAbuseError if FactoryBot::Sequence === value
19
+ value
20
+ }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ module FactoryBot
2
+ class Attribute
3
+ # @api private
4
+ class Sequence < Attribute
5
+ def initialize(name, sequence, ignored)
6
+ super(name, ignored)
7
+ @sequence = sequence
8
+ end
9
+
10
+ def to_proc
11
+ sequence = @sequence
12
+ -> { FactoryBot.generate(sequence) }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module FactoryBot
2
+ class Attribute
3
+ # @api private
4
+ class Static < Attribute
5
+ def initialize(name, value, ignored)
6
+ super(name, ignored)
7
+ @value = value
8
+ end
9
+
10
+ def to_proc
11
+ value = @value
12
+ -> { value }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,97 @@
1
+ module FactoryBot
2
+ # @api private
3
+ class AttributeAssigner
4
+ def initialize(evaluator, build_class, &instance_builder)
5
+ @build_class = build_class
6
+ @instance_builder = instance_builder
7
+ @evaluator = evaluator
8
+ @attribute_list = evaluator.class.attribute_list
9
+ @attribute_names_assigned = []
10
+ end
11
+
12
+ def object
13
+ @evaluator.instance = build_class_instance
14
+ build_class_instance.tap do |instance|
15
+ attributes_to_set_on_instance.each do |attribute|
16
+ instance.public_send("#{attribute}=", get(attribute))
17
+ @attribute_names_assigned << attribute
18
+ end
19
+ end
20
+ end
21
+
22
+ def hash
23
+ @evaluator.instance = build_hash
24
+
25
+ attributes_to_set_on_hash.inject({}) do |result, attribute|
26
+ result[attribute] = get(attribute)
27
+ result
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def method_tracking_evaluator
34
+ @method_tracking_evaluator ||= Decorator::AttributeHash.new(decorated_evaluator, attribute_names_to_assign)
35
+ end
36
+
37
+ def decorated_evaluator
38
+ Decorator::InvocationTracker.new(
39
+ Decorator::NewConstructor.new(@evaluator, @build_class)
40
+ )
41
+ end
42
+
43
+ def methods_invoked_on_evaluator
44
+ method_tracking_evaluator.__invoked_methods__
45
+ end
46
+
47
+ def build_class_instance
48
+ @build_class_instance ||= method_tracking_evaluator.instance_exec(&@instance_builder)
49
+ end
50
+
51
+ def build_hash
52
+ @build_hash ||= NullObject.new(hash_instance_methods_to_respond_to)
53
+ end
54
+
55
+ def get(attribute_name)
56
+ @evaluator.send(attribute_name)
57
+ end
58
+
59
+ def attributes_to_set_on_instance
60
+ (attribute_names_to_assign - @attribute_names_assigned - methods_invoked_on_evaluator).uniq
61
+ end
62
+
63
+ def attributes_to_set_on_hash
64
+ attribute_names_to_assign - association_names
65
+ end
66
+
67
+ def attribute_names_to_assign
68
+ @attribute_names_to_assign ||= non_ignored_attribute_names + override_names - ignored_attribute_names - alias_names_to_ignore
69
+ end
70
+
71
+ def non_ignored_attribute_names
72
+ @attribute_list.non_ignored.names
73
+ end
74
+
75
+ def ignored_attribute_names
76
+ @attribute_list.ignored.names
77
+ end
78
+
79
+ def association_names
80
+ @attribute_list.associations.names
81
+ end
82
+
83
+ def override_names
84
+ @evaluator.__override_names__
85
+ end
86
+
87
+ def hash_instance_methods_to_respond_to
88
+ @attribute_list.names + override_names + @build_class.instance_methods
89
+ end
90
+
91
+ def alias_names_to_ignore
92
+ @attribute_list.non_ignored.flat_map do |attribute|
93
+ override_names.map { |override| attribute.name if attribute.alias_for?(override) && attribute.name != override && !ignored_attribute_names.include?(override) }
94
+ end.compact
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,67 @@
1
+ module FactoryBot
2
+ # @api private
3
+ class AttributeList
4
+ include Enumerable
5
+
6
+ def initialize(name = nil, attributes = [])
7
+ @name = name
8
+ @attributes = attributes
9
+ end
10
+
11
+ def define_attribute(attribute)
12
+ ensure_attribute_not_self_referencing! attribute
13
+ ensure_attribute_not_defined! attribute
14
+
15
+ add_attribute attribute
16
+ end
17
+
18
+ def each(&block)
19
+ @attributes.each(&block)
20
+ end
21
+
22
+ def names
23
+ map(&:name)
24
+ end
25
+
26
+ def associations
27
+ AttributeList.new(@name, select(&:association?))
28
+ end
29
+
30
+ def ignored
31
+ AttributeList.new(@name, select(&:ignored))
32
+ end
33
+
34
+ def non_ignored
35
+ AttributeList.new(@name, reject(&:ignored))
36
+ end
37
+
38
+ def apply_attributes(attributes_to_apply)
39
+ attributes_to_apply.each { |attribute| add_attribute(attribute) }
40
+ end
41
+
42
+ private
43
+
44
+ def add_attribute(attribute)
45
+ @attributes << attribute
46
+ attribute
47
+ end
48
+
49
+ def ensure_attribute_not_defined!(attribute)
50
+ if attribute_defined?(attribute.name)
51
+ raise AttributeDefinitionError, "Attribute already defined: #{attribute.name}"
52
+ end
53
+ end
54
+
55
+ def ensure_attribute_not_self_referencing!(attribute)
56
+ if attribute.respond_to?(:factory) && attribute.factory == @name
57
+ raise AssociationDefinitionError, "Self-referencing association '#{attribute.name}' in '#{attribute.factory}'"
58
+ end
59
+ end
60
+
61
+ def attribute_defined?(attribute_name)
62
+ @attributes.any? do |attribute|
63
+ attribute.name == attribute_name
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,40 @@
1
+ module FactoryBot
2
+ class Callback
3
+ attr_reader :name
4
+
5
+ def initialize(name, block)
6
+ @name = name.to_sym
7
+ @block = block
8
+ ensure_valid_callback_name!
9
+ end
10
+
11
+ def run(instance, evaluator)
12
+ case block.arity
13
+ when 1, -1 then syntax_runner.instance_exec(instance, &block)
14
+ when 2 then syntax_runner.instance_exec(instance, evaluator, &block)
15
+ else syntax_runner.instance_exec(&block)
16
+ end
17
+ end
18
+
19
+ def ==(other)
20
+ name == other.name &&
21
+ block == other.block
22
+ end
23
+
24
+ protected
25
+ attr_reader :block
26
+
27
+ private
28
+
29
+ def ensure_valid_callback_name!
30
+ unless FactoryBot.callback_names.include?(name)
31
+ raise InvalidCallbackNameError, "#{name} is not a valid callback name. " +
32
+ "Valid callback names are #{FactoryBot.callback_names.inspect}"
33
+ end
34
+ end
35
+
36
+ def syntax_runner
37
+ @syntax_runner ||= SyntaxRunner.new
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ module FactoryBot
2
+ # @api private
3
+ class CallbacksObserver
4
+ def initialize(callbacks, evaluator)
5
+ @callbacks = callbacks
6
+ @evaluator = evaluator
7
+ end
8
+
9
+ def update(name, result_instance)
10
+ callbacks_by_name(name).each do |callback|
11
+ callback.run(result_instance, @evaluator)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def callbacks_by_name(name)
18
+ @callbacks.select { |callback| callback.name == name }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ module FactoryBot
2
+ # @api private
3
+ class Configuration
4
+ attr_reader :factories, :sequences, :traits, :strategies, :callback_names
5
+
6
+ attr_accessor :allow_class_lookup, :use_parent_strategy
7
+
8
+ def initialize
9
+ @factories = Decorator::DisallowsDuplicatesRegistry.new(Registry.new('Factory'))
10
+ @sequences = Decorator::DisallowsDuplicatesRegistry.new(Registry.new('Sequence'))
11
+ @traits = Decorator::DisallowsDuplicatesRegistry.new(Registry.new('Trait'))
12
+ @strategies = Registry.new('Strategy')
13
+ @callback_names = Set.new
14
+ @definition = Definition.new
15
+
16
+ @allow_class_lookup = true
17
+
18
+ to_create { |instance| instance.save! }
19
+ initialize_with { new }
20
+ end
21
+
22
+ delegate :to_create, :skip_create, :constructor, :before, :after,
23
+ :callback, :callbacks, to: :@definition
24
+
25
+ def initialize_with(&block)
26
+ @definition.define_constructor(&block)
27
+ end
28
+
29
+ def duplicate_attribute_assignment_from_initialize_with
30
+ false
31
+ end
32
+
33
+ def duplicate_attribute_assignment_from_initialize_with=(value)
34
+ ActiveSupport::Deprecation.warn 'Assignment of duplicate_attribute_assignment_from_initialize_with is unnecessary as this is now default behavior in FactoryBot 4.0; this line can be removed', caller
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ require 'factory_bot/declaration/static'
2
+ require 'factory_bot/declaration/dynamic'
3
+ require 'factory_bot/declaration/association'
4
+ require 'factory_bot/declaration/implicit'
5
+
6
+ module FactoryBot
7
+ # @api private
8
+ class Declaration
9
+ attr_reader :name
10
+
11
+ def initialize(name, ignored = false)
12
+ @name = name
13
+ @ignored = ignored
14
+ end
15
+
16
+ def to_attributes
17
+ build
18
+ end
19
+
20
+ protected
21
+ attr_reader :ignored
22
+ end
23
+ end