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.
- checksums.yaml +7 -0
- data/.autotest +9 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.simplecov +4 -0
- data/.travis.yml +41 -0
- data/.yardopts +5 -0
- data/Appraisals +19 -0
- data/CONTRIBUTING.md +60 -0
- data/GETTING_STARTED.md +1387 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +123 -0
- data/LICENSE +19 -0
- data/NAME.md +11 -0
- data/NEWS +285 -0
- data/README.md +102 -0
- data/Rakefile +36 -0
- data/cucumber.yml +1 -0
- data/factory_bot.gemspec +38 -0
- data/factory_girl.gemspec +41 -0
- data/gemfiles/3.2.gemfile +10 -0
- data/gemfiles/3.2.gemfile.lock +112 -0
- data/gemfiles/4.0.gemfile +10 -0
- data/gemfiles/4.0.gemfile.lock +112 -0
- data/gemfiles/4.1.gemfile +10 -0
- data/gemfiles/4.1.gemfile.lock +111 -0
- data/gemfiles/4.2.gemfile +10 -0
- data/gemfiles/4.2.gemfile.lock +111 -0
- data/gemfiles/5.0.gemfile +10 -0
- data/gemfiles/5.0.gemfile.lock +110 -0
- data/lib/factory_bot.rb +154 -0
- data/lib/factory_bot/aliases.rb +18 -0
- data/lib/factory_bot/attribute.rb +62 -0
- data/lib/factory_bot/attribute/association.rb +27 -0
- data/lib/factory_bot/attribute/dynamic.rb +24 -0
- data/lib/factory_bot/attribute/sequence.rb +16 -0
- data/lib/factory_bot/attribute/static.rb +16 -0
- data/lib/factory_bot/attribute_assigner.rb +97 -0
- data/lib/factory_bot/attribute_list.rb +67 -0
- data/lib/factory_bot/callback.rb +40 -0
- data/lib/factory_bot/callbacks_observer.rb +21 -0
- data/lib/factory_bot/configuration.rb +37 -0
- data/lib/factory_bot/declaration.rb +23 -0
- data/lib/factory_bot/declaration/association.rb +28 -0
- data/lib/factory_bot/declaration/dynamic.rb +26 -0
- data/lib/factory_bot/declaration/implicit.rb +33 -0
- data/lib/factory_bot/declaration/static.rb +26 -0
- data/lib/factory_bot/declaration_list.rb +49 -0
- data/lib/factory_bot/decorator.rb +21 -0
- data/lib/factory_bot/decorator/attribute_hash.rb +16 -0
- data/lib/factory_bot/decorator/class_key_hash.rb +28 -0
- data/lib/factory_bot/decorator/disallows_duplicates_registry.rb +13 -0
- data/lib/factory_bot/decorator/invocation_tracker.rb +19 -0
- data/lib/factory_bot/decorator/new_constructor.rb +12 -0
- data/lib/factory_bot/definition.rb +136 -0
- data/lib/factory_bot/definition_hierarchy.rb +48 -0
- data/lib/factory_bot/definition_proxy.rb +174 -0
- data/lib/factory_bot/errors.rb +25 -0
- data/lib/factory_bot/evaluation.rb +23 -0
- data/lib/factory_bot/evaluator.rb +82 -0
- data/lib/factory_bot/evaluator_class_definer.rb +20 -0
- data/lib/factory_bot/factory.rb +162 -0
- data/lib/factory_bot/factory_runner.rb +33 -0
- data/lib/factory_bot/find_definitions.rb +25 -0
- data/lib/factory_bot/linter.rb +97 -0
- data/lib/factory_bot/null_factory.rb +18 -0
- data/lib/factory_bot/null_object.rb +24 -0
- data/lib/factory_bot/registry.rb +38 -0
- data/lib/factory_bot/reload.rb +8 -0
- data/lib/factory_bot/sequence.rb +62 -0
- data/lib/factory_bot/strategy/attributes_for.rb +13 -0
- data/lib/factory_bot/strategy/build.rb +15 -0
- data/lib/factory_bot/strategy/create.rb +18 -0
- data/lib/factory_bot/strategy/null.rb +11 -0
- data/lib/factory_bot/strategy/stub.rb +108 -0
- data/lib/factory_bot/strategy_calculator.rb +26 -0
- data/lib/factory_bot/strategy_syntax_method_registrar.rb +54 -0
- data/lib/factory_bot/syntax.rb +7 -0
- data/lib/factory_bot/syntax/default.rb +76 -0
- data/lib/factory_bot/syntax/methods.rb +111 -0
- data/lib/factory_bot/syntax_runner.rb +6 -0
- data/lib/factory_bot/trait.rb +30 -0
- data/lib/factory_bot/version.rb +3 -0
- data/lib/factory_girl.rb +5 -0
- data/lib/factory_girl/version.rb +1 -0
- metadata +302 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
module FactoryBot
|
2
|
+
class FactoryRunner
|
3
|
+
def initialize(name, strategy, traits_and_overrides)
|
4
|
+
@name = name
|
5
|
+
@strategy = strategy
|
6
|
+
|
7
|
+
@overrides = traits_and_overrides.extract_options!
|
8
|
+
@traits = traits_and_overrides
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(runner_strategy = @strategy, &block)
|
12
|
+
factory = FactoryBot.factory_by_name(@name)
|
13
|
+
|
14
|
+
factory.compile
|
15
|
+
|
16
|
+
if @traits.any?
|
17
|
+
factory = factory.with_traits(@traits)
|
18
|
+
end
|
19
|
+
|
20
|
+
instrumentation_payload = {
|
21
|
+
name: @name,
|
22
|
+
strategy: runner_strategy,
|
23
|
+
traits: @traits,
|
24
|
+
overrides: @overrides,
|
25
|
+
factory: factory
|
26
|
+
}
|
27
|
+
|
28
|
+
ActiveSupport::Notifications.instrument('factory_bot.run_factory', instrumentation_payload) do
|
29
|
+
factory.run(runner_strategy, @overrides, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module FactoryBot
|
2
|
+
class << self
|
3
|
+
# An Array of strings specifying locations that should be searched for
|
4
|
+
# factory definitions. By default, factory_bot will attempt to require
|
5
|
+
# "factories", "test/factories" and "spec/factories". Only the first
|
6
|
+
# existing file will be loaded.
|
7
|
+
attr_accessor :definition_file_paths
|
8
|
+
end
|
9
|
+
|
10
|
+
self.definition_file_paths = %w(factories test/factories spec/factories)
|
11
|
+
|
12
|
+
def self.find_definitions
|
13
|
+
absolute_definition_file_paths = definition_file_paths.map { |path| File.expand_path(path) }
|
14
|
+
|
15
|
+
absolute_definition_file_paths.uniq.each do |path|
|
16
|
+
load("#{path}.rb") if File.exist?("#{path}.rb")
|
17
|
+
|
18
|
+
if File.directory? path
|
19
|
+
Dir[File.join(path, '**', '*.rb')].sort.each do |file|
|
20
|
+
load file
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module FactoryBot
|
2
|
+
class Linter
|
3
|
+
|
4
|
+
def initialize(factories_to_lint, linting_strategy)
|
5
|
+
@factories_to_lint = factories_to_lint
|
6
|
+
@linting_method = "lint_#{linting_strategy}"
|
7
|
+
@invalid_factories = calculate_invalid_factories
|
8
|
+
end
|
9
|
+
|
10
|
+
def lint!
|
11
|
+
if invalid_factories.any?
|
12
|
+
raise InvalidFactoryError, error_message
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :factories_to_lint, :invalid_factories
|
17
|
+
private :factories_to_lint, :invalid_factories
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def calculate_invalid_factories
|
22
|
+
factories_to_lint.reduce(Hash.new([])) do |result, factory|
|
23
|
+
errors = send(@linting_method, factory)
|
24
|
+
result[factory] |= errors unless errors.empty?
|
25
|
+
result
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class FactoryError
|
30
|
+
def initialize(wrapped_error, factory)
|
31
|
+
@wrapped_error = wrapped_error
|
32
|
+
@factory = factory
|
33
|
+
end
|
34
|
+
|
35
|
+
def message
|
36
|
+
message = @wrapped_error.message
|
37
|
+
"* #{location} - #{message} (#{@wrapped_error.class.name})"
|
38
|
+
end
|
39
|
+
|
40
|
+
def location
|
41
|
+
@factory.name
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class FactoryTraitError < FactoryError
|
46
|
+
def initialize(wrapped_error, factory, trait_name)
|
47
|
+
super(wrapped_error, factory)
|
48
|
+
@trait_name = trait_name
|
49
|
+
end
|
50
|
+
|
51
|
+
def location
|
52
|
+
"#{@factory.name}+#{@trait_name}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def lint_factory(factory)
|
57
|
+
result = []
|
58
|
+
begin
|
59
|
+
FactoryBot.create(factory.name)
|
60
|
+
rescue => error
|
61
|
+
result |= [FactoryError.new(error, factory)]
|
62
|
+
end
|
63
|
+
result
|
64
|
+
end
|
65
|
+
|
66
|
+
def lint_traits(factory)
|
67
|
+
result = []
|
68
|
+
factory.definition.defined_traits.map(&:name).each do |trait_name|
|
69
|
+
begin
|
70
|
+
FactoryBot.create(factory.name, trait_name)
|
71
|
+
rescue => error
|
72
|
+
result |=
|
73
|
+
[FactoryTraitError.new(error, factory, trait_name)]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
result
|
77
|
+
end
|
78
|
+
|
79
|
+
def lint_factory_and_traits(factory)
|
80
|
+
errors = lint_factory(factory)
|
81
|
+
errors |= lint_traits(factory)
|
82
|
+
errors
|
83
|
+
end
|
84
|
+
|
85
|
+
def error_message
|
86
|
+
lines = invalid_factories.map do |_factory, exceptions|
|
87
|
+
exceptions.map(&:message)
|
88
|
+
end.flatten
|
89
|
+
|
90
|
+
<<-ERROR_MESSAGE.strip
|
91
|
+
The following factories are invalid:
|
92
|
+
|
93
|
+
#{lines.join("\n")}
|
94
|
+
ERROR_MESSAGE
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module FactoryBot
|
2
|
+
# @api private
|
3
|
+
class NullFactory
|
4
|
+
attr_reader :definition
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@definition = Definition.new(:null_factory)
|
8
|
+
end
|
9
|
+
|
10
|
+
delegate :defined_traits, :callbacks, :attributes, :constructor,
|
11
|
+
:to_create, to: :definition
|
12
|
+
|
13
|
+
def compile; end
|
14
|
+
def class_name; end
|
15
|
+
def evaluator_class; FactoryBot::Evaluator; end
|
16
|
+
def hierarchy_class; FactoryBot::DefinitionHierarchy; end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module FactoryBot
|
2
|
+
# @api private
|
3
|
+
class NullObject < ::BasicObject
|
4
|
+
def initialize(methods_to_respond_to)
|
5
|
+
@methods_to_respond_to = methods_to_respond_to.map(&:to_s)
|
6
|
+
end
|
7
|
+
|
8
|
+
def method_missing(name, *args, &block)
|
9
|
+
if respond_to?(name)
|
10
|
+
nil
|
11
|
+
else
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def respond_to?(method, include_private=false)
|
17
|
+
@methods_to_respond_to.include? method.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def respond_to_missing?(*args)
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module FactoryBot
|
2
|
+
class Registry
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
@items = Decorator::ClassKeyHash.new({})
|
10
|
+
end
|
11
|
+
|
12
|
+
def clear
|
13
|
+
@items.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
def each(&block)
|
17
|
+
@items.values.uniq.each(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def find(name)
|
21
|
+
if registered?(name)
|
22
|
+
@items[name]
|
23
|
+
else
|
24
|
+
raise ArgumentError, "#{@name} not registered: #{name}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
alias :[] :find
|
29
|
+
|
30
|
+
def register(name, item)
|
31
|
+
@items[name] = item
|
32
|
+
end
|
33
|
+
|
34
|
+
def registered?(name)
|
35
|
+
@items.key?(name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module FactoryBot
|
2
|
+
|
3
|
+
# Sequences are defined using sequence within a FactoryBot.define block.
|
4
|
+
# Sequence values are generated using next.
|
5
|
+
# @api private
|
6
|
+
class Sequence
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
def initialize(name, *args, &proc)
|
10
|
+
@name = name
|
11
|
+
@proc = proc
|
12
|
+
|
13
|
+
options = args.extract_options!
|
14
|
+
@value = args.first || 1
|
15
|
+
@aliases = options.fetch(:aliases) { [] }
|
16
|
+
|
17
|
+
if !@value.respond_to?(:peek)
|
18
|
+
@value = EnumeratorAdapter.new(@value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def next(scope = nil)
|
23
|
+
if @proc && scope
|
24
|
+
scope.instance_exec(value, &@proc)
|
25
|
+
elsif @proc
|
26
|
+
@proc.call(value)
|
27
|
+
else
|
28
|
+
value
|
29
|
+
end
|
30
|
+
ensure
|
31
|
+
increment_value
|
32
|
+
end
|
33
|
+
|
34
|
+
def names
|
35
|
+
[@name] + @aliases
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def value
|
41
|
+
@value.peek
|
42
|
+
end
|
43
|
+
|
44
|
+
def increment_value
|
45
|
+
@value.next
|
46
|
+
end
|
47
|
+
|
48
|
+
class EnumeratorAdapter
|
49
|
+
def initialize(value)
|
50
|
+
@value = value
|
51
|
+
end
|
52
|
+
|
53
|
+
def peek
|
54
|
+
@value
|
55
|
+
end
|
56
|
+
|
57
|
+
def next
|
58
|
+
@value = @value.next
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module FactoryBot
|
2
|
+
module Strategy
|
3
|
+
class Create
|
4
|
+
def association(runner)
|
5
|
+
runner.run
|
6
|
+
end
|
7
|
+
|
8
|
+
def result(evaluation)
|
9
|
+
evaluation.object.tap do |instance|
|
10
|
+
evaluation.notify(:after_build, instance)
|
11
|
+
evaluation.notify(:before_create, instance)
|
12
|
+
evaluation.create(instance)
|
13
|
+
evaluation.notify(:after_create, instance)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module FactoryBot
|
2
|
+
module Strategy
|
3
|
+
class Stub
|
4
|
+
@@next_id = 1000
|
5
|
+
|
6
|
+
DISABLED_PERSISTENCE_METHODS = [
|
7
|
+
:connection,
|
8
|
+
:decrement!,
|
9
|
+
:decrement,
|
10
|
+
:delete,
|
11
|
+
:destroy!,
|
12
|
+
:destroy,
|
13
|
+
:destroyed?,
|
14
|
+
:increment!,
|
15
|
+
:increment,
|
16
|
+
:reload,
|
17
|
+
:save!,
|
18
|
+
:save,
|
19
|
+
:toggle!,
|
20
|
+
:toggle,
|
21
|
+
:touch,
|
22
|
+
:update!,
|
23
|
+
:update,
|
24
|
+
:update_attribute,
|
25
|
+
:update_attributes!,
|
26
|
+
:update_attributes,
|
27
|
+
:update_column,
|
28
|
+
:update_columns,
|
29
|
+
].freeze
|
30
|
+
|
31
|
+
def association(runner)
|
32
|
+
runner.run(:build_stubbed)
|
33
|
+
end
|
34
|
+
|
35
|
+
def result(evaluation)
|
36
|
+
evaluation.object.tap do |instance|
|
37
|
+
stub_database_interaction_on_result(instance)
|
38
|
+
clear_changed_attributes_on_result(instance)
|
39
|
+
evaluation.notify(:after_stub, instance)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def next_id
|
46
|
+
@@next_id += 1
|
47
|
+
end
|
48
|
+
|
49
|
+
def stub_database_interaction_on_result(result_instance)
|
50
|
+
result_instance.id ||= next_id
|
51
|
+
|
52
|
+
result_instance.instance_eval do
|
53
|
+
def persisted?
|
54
|
+
!new_record?
|
55
|
+
end
|
56
|
+
|
57
|
+
def new_record?
|
58
|
+
id.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
DISABLED_PERSISTENCE_METHODS.each do |write_method|
|
62
|
+
define_singleton_method(write_method) do |*args|
|
63
|
+
raise "stubbed models are not allowed to access the database - #{self.class}##{write_method}(#{args.join(",")})"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
created_at_missing_default = result_instance.respond_to?(:created_at) && !result_instance.created_at
|
69
|
+
result_instance_missing_created_at = !result_instance.respond_to?(:created_at)
|
70
|
+
|
71
|
+
if created_at_missing_default || result_instance_missing_created_at
|
72
|
+
result_instance.instance_eval do
|
73
|
+
def created_at
|
74
|
+
@created_at ||= Time.now.in_time_zone
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
has_updated_at = result_instance.respond_to?(:updated_at)
|
80
|
+
updated_at_no_default = has_updated_at && !result_instance.updated_at
|
81
|
+
result_instance_missing_updated_at = !has_updated_at
|
82
|
+
|
83
|
+
if updated_at_no_default || result_instance_missing_updated_at
|
84
|
+
result_instance.instance_eval do
|
85
|
+
def updated_at
|
86
|
+
@updated_at ||= Time.current
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def clear_changed_attributes_on_result(result_instance)
|
93
|
+
unless result_instance.respond_to?(:clear_changes_information)
|
94
|
+
result_instance.extend ActiveModelDirtyBackport
|
95
|
+
end
|
96
|
+
|
97
|
+
result_instance.clear_changes_information
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
module ActiveModelDirtyBackport
|
102
|
+
def clear_changes_information
|
103
|
+
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
|
104
|
+
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|