thoughtbot--factory_bot/lib/factory_bot.rb

156 lines
4.4 KiB
Ruby
Raw Normal View History

require "set"
require "active_support/core_ext/module/delegation"
require "active_support/core_ext/module/attribute_accessors"
require "active_support/deprecation"
require "active_support/notifications"
require "factory_bot/definition_hierarchy"
require "factory_bot/configuration"
require "factory_bot/errors"
require "factory_bot/factory_runner"
require "factory_bot/strategy_syntax_method_registrar"
require "factory_bot/strategy_calculator"
require "factory_bot/strategy/build"
require "factory_bot/strategy/create"
require "factory_bot/strategy/attributes_for"
require "factory_bot/strategy/stub"
require "factory_bot/strategy/null"
require "factory_bot/registry"
require "factory_bot/null_factory"
require "factory_bot/null_object"
require "factory_bot/evaluation"
require "factory_bot/factory"
require "factory_bot/attribute_assigner"
require "factory_bot/evaluator"
require "factory_bot/evaluator_class_definer"
require "factory_bot/attribute"
require "factory_bot/callback"
require "factory_bot/callbacks_observer"
require "factory_bot/declaration_list"
require "factory_bot/declaration"
require "factory_bot/sequence"
require "factory_bot/attribute_list"
require "factory_bot/trait"
require "factory_bot/aliases"
require "factory_bot/definition"
require "factory_bot/definition_proxy"
require "factory_bot/syntax"
require "factory_bot/syntax_runner"
require "factory_bot/find_definitions"
require "factory_bot/reload"
require "factory_bot/decorator"
require "factory_bot/decorator/attribute_hash"
require "factory_bot/decorator/disallows_duplicates_registry"
require "factory_bot/decorator/invocation_tracker"
require "factory_bot/decorator/new_constructor"
require "factory_bot/linter"
require "factory_bot/version"
module FactoryBot
def self.configuration
@configuration ||= Configuration.new
end
def self.reset_configuration
@configuration = nil
end
Default `use_parent_strategy` to true Background --- In issues #64 and #66 (from 2010) people expressed surprise that using the `build` strategy would still `create` any associations. I remember being similarly surprised by that behavior when I first started using factory_bot. The main reason for this behavior is to ensure the built instance will be valid if there were any foreign key validations (like `validates_presence_of :user_id`). If we don't save the associations, there won't be any ids present. PR #191 (from 2011) offers a workaround for people who don't want records to be saved automatically. Passing `strategy: :build` (originally `method: :build`, but later renamed) when declaring the association will prevent it from being saved when using `build`. But #191 isn't really a complete solution (as discussed on the PR after it was merged). `strategy: :build` may do the right thing when building objects with the `build` strategy, but it can cause new problems when using the `create` strategy. A better solution would be something like: `strategy: <whatever strategy I was already using>`. PRs #749 and #961 (merged in 2016) introduce something like that, with the `use_parent_strategy` configuration option. With this turned on `build` end up being generally [a little faster][] than `build_stubbed`, since it no longer needs to hit the database for each association. [a little faster]: https://gist.github.com/composerinteralia/d4796df9140f431e36f88dfb6fe9733a I have set `use_parent_strategy` on several projects now. I also added it to suspenders in thoughtbot/suspenders#952. On newer projects I have not run into any problems. On existing projects I have seen the occasional test failure, which are easy enough to fix by changing `build` to `create`. Unfortunately I don't think `use_parent_strategy` is widely known, since it wasn't documented until #1234 (about a month ago). I also learned in #1234 that the `use_parent_strategy` setting gets wiped out with `FactoryBot.reload`, which can be problematic when using factory_bot_rails. To summarize, we have been exploring having a `build` strategy that uses `build` all the way down since 2010, but there still isn't a totally reliable way to get that. This PR --- * Default to using the parent strategy for factory_bot 5, as suggested in #749. * In the original PR for this (#1240) I had removed `use_parent_strategy`, but to make the transition smoother I ended up fixing the reload problem in #1244 Once factory\_bot 5 is released and the dust has settled, it may make sense to deprecate `use_parent_strategy`, and maybe also the strategy option for associations.
2019-01-04 19:57:22 -05:00
mattr_accessor :use_parent_strategy, instance_accessor: false, default: true
# Look for errors in factories and (optionally) their traits.
# Parameters:
# factories - which factories to lint; omit for all factories
# options:
# traits: true - to lint traits as well as factories
# strategy: :create - to specify the strategy for linting
Add option for verbose linting This has come up a few times, and I can see why it might be helpful to have access to full backtraces when debugging a factory error uncovered by `FactoryBot.lint`. But since most of the time I don't want the extra noise from the backtrace, I added this as a verbose option. The default message is still: ``` The following factories are invalid: * user - undefined method `save!' for #<User:0x00007ff0cbc89100> * admin - undefined method `save!' for #<User:0x00007ff0cbc73e40> ``` And with the verbose option (usually with more lines of backtrace): ``` The following factories are invalid: * user - undefined method `save!' for #<User:0x00007ff0cbc89100> /Users/.../thoughtbot/factory_bot/lib/factory_bot/evaluation.rb:18:in `create' /Users/.../factory_bot/lib/factory_bot/strategy/create.rb:12:in `block in result' * admin - undefined method `save!' for #<User:0x00007ff0cbc73e40> /Users/.../thoughtbot/factory_bot/lib/factory_bot/evaluation.rb:18:in `create' /Users/.../factory_bot/lib/factory_bot/strategy/create.rb:12:in `block in result' ``` I moved the linting option defaults out of the FactoryBot.lint method and into keyword argument defaults in Linter#initialize. This seems a bit cleaner, and now we will get an error if we pass an option we don't understand (before 6e511597 we had a test that passed in a bogus option) Closes #710 Closes #1124 I am opening a new PR since the original PR is years old and it seemed unkind to request changes after so long. Instead I will list the authors as co-authors. Co-authored-by: Jack Kinsella <jack.kinsella@gmail.com> Co-authored-by: Jasper Woudenberg <mail@jasperwoudenberg.com>
2018-11-21 15:28:28 -05:00
# verbose: true - to include full backtraces for each linting error
def self.lint(*args)
options = args.extract_options!
factories_to_lint = args[0] || FactoryBot.factories
Linter.new(factories_to_lint, options).lint!
end
class << self
delegate :factories,
:sequences,
:traits,
:callbacks,
:strategies,
:callback_names,
:to_create,
:skip_create,
:initialize_with,
:constructor,
to: :configuration
end
def self.register_factory(factory)
factory.names.each do |name|
factories.register(name, factory)
end
factory
end
def self.factory_by_name(name)
factories.find(name)
end
def self.register_sequence(sequence)
sequence.names.each do |name|
sequences.register(name, sequence)
end
sequence
end
def self.sequence_by_name(name)
sequences.find(name)
end
2017-09-30 23:06:14 -04:00
def self.rewind_sequences
sequences.each(&:rewind)
end
def self.register_trait(trait)
trait.names.each do |name|
traits.register(name, trait)
end
trait
end
def self.trait_by_name(name)
traits.find(name)
end
def self.register_strategy(strategy_name, strategy_class)
strategies.register(strategy_name, strategy_class)
StrategySyntaxMethodRegistrar.new(strategy_name).define_strategy_methods
end
def self.strategy_by_name(name)
strategies.find(name)
end
def self.register_default_strategies
register_strategy(:build, FactoryBot::Strategy::Build)
register_strategy(:create, FactoryBot::Strategy::Create)
register_strategy(:attributes_for, FactoryBot::Strategy::AttributesFor)
register_strategy(:build_stubbed, FactoryBot::Strategy::Stub)
register_strategy(:null, FactoryBot::Strategy::Null)
end
def self.register_default_callbacks
register_callback(:after_build)
register_callback(:after_create)
register_callback(:after_stub)
register_callback(:before_create)
end
def self.register_callback(name)
name = name.to_sym
callback_names << name
end
end
FactoryBot.register_default_strategies
FactoryBot.register_default_callbacks