Add ability to register custom strategies
This commit is contained in:
parent
c006dd186f
commit
c8c1a801ed
|
@ -745,6 +745,49 @@ You can override the initializer in order to:
|
|||
* Use a method other than `new` to instantiate the instance
|
||||
* Do crazy things like decorate the instance after it's built
|
||||
|
||||
Custom Strategies
|
||||
-----------------
|
||||
|
||||
There are times where you may want to extend behavior of factory\_girl by
|
||||
adding a custom build strategy.
|
||||
|
||||
Strategies define two methods: `association` and `result`. `association`
|
||||
receives a `FactoryGirl::FactoryRunner` instance, upon which you can call
|
||||
`run`, overriding the strategy if you want. The second method, `result`,
|
||||
receives a `FactoryGirl::Evaluation` instance. It provides a way to trigger
|
||||
callbacks (with `notify`), `object` or `hash` (to get the result instance or a
|
||||
hash based on the attributes defined in the factory), and `create`, which
|
||||
executes the `to_create` callback defined on the factory.
|
||||
|
||||
To understand how factory\_girl uses strategies internally, it's probably
|
||||
easiest to just view the source for each of the four default strategies.
|
||||
|
||||
Inheritance can occasionally be useful; here's an example of inheriting from
|
||||
`FactoryGirl::Strategy::Create` to build a JSON representation of your model.
|
||||
|
||||
```ruby
|
||||
class JsonStrategy < FactoryGirl::Strategy::Create
|
||||
def result(evaluation)
|
||||
super.to_json
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
For factory\_girl to recognize the new strategy, you can register it:
|
||||
|
||||
```ruby
|
||||
FactoryGirl.register_strategy(:json, JsonStrategy)
|
||||
```
|
||||
|
||||
This allows you to call
|
||||
|
||||
```ruby
|
||||
FactoryGirl.json(:user)
|
||||
```
|
||||
|
||||
Finally, you can override factory\_girl's own strategies if you'd like by
|
||||
registering a new object in place of the strategies.
|
||||
|
||||
Cucumber Integration
|
||||
--------------------
|
||||
|
||||
|
|
|
@ -80,7 +80,34 @@ module FactoryGirl
|
|||
traits.find(name)
|
||||
end
|
||||
|
||||
def self.strategies
|
||||
@strategies ||= Registry.new("Strategy")
|
||||
end
|
||||
|
||||
def self.register_strategy(strategy_name, strategy_class)
|
||||
strategies.register(strategy_name, strategy_class)
|
||||
|
||||
FactoryGirl::Syntax::Methods.module_exec do
|
||||
define_method(strategy_name) do |name, *traits_and_overrides, &block|
|
||||
FactoryRunner.new(name, strategy_class, traits_and_overrides).run(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.strategy_by_name(name)
|
||||
strategies.find(name)
|
||||
end
|
||||
|
||||
def self.register_default_strategies
|
||||
FactoryGirl.register_strategy(:build, FactoryGirl::Strategy::Build)
|
||||
FactoryGirl.register_strategy(:create, FactoryGirl::Strategy::Create)
|
||||
FactoryGirl.register_strategy(:attributes_for, FactoryGirl::Strategy::AttributesFor)
|
||||
FactoryGirl.register_strategy(:build_stubbed, FactoryGirl::Strategy::Stub)
|
||||
end
|
||||
|
||||
def self.callback_names
|
||||
[:after_build, :after_create, :after_stub, :before_create].freeze
|
||||
end
|
||||
end
|
||||
|
||||
FactoryGirl.register_default_strategies
|
||||
|
|
|
@ -28,7 +28,7 @@ module FactoryGirl
|
|||
end
|
||||
|
||||
def association(factory_name, overrides = {})
|
||||
strategy_override = overrides.fetch(:strategy) { Strategy::Create }
|
||||
strategy_override = overrides.fetch(:strategy) { FactoryGirl.strategy_by_name(:create) }
|
||||
|
||||
build_strategy = StrategyCalculator.new(strategy_override).strategy
|
||||
runner = FactoryRunner.new(factory_name, build_strategy, [overrides.except(:strategy)])
|
||||
|
|
|
@ -3,6 +3,8 @@ module FactoryGirl
|
|||
self.factories.clear
|
||||
self.sequences.clear
|
||||
self.traits.clear
|
||||
self.strategies.clear
|
||||
self.register_default_strategies
|
||||
self.find_definitions
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,11 +19,8 @@ module FactoryGirl
|
|||
end
|
||||
|
||||
def strategy_name_to_object
|
||||
case @name_or_object
|
||||
when :build then Strategy::Build
|
||||
when :create then Strategy::Create
|
||||
else raise "unrecognized method #{@name_or_object}"
|
||||
end
|
||||
FactoryGirl.strategy_by_name(@name_or_object) ||
|
||||
raise("unrecognized method #{@name_or_object}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -43,7 +43,7 @@ module FactoryGirl
|
|||
|
||||
def generate(overrides = {}, &block)
|
||||
ActiveSupport::Deprecation.warn "Model.generate is deprecated; use the FactoryGirl.define syntax instead", caller
|
||||
instance = FactoryRunner.new(name.underscore, Strategy::Build, [overrides]).run
|
||||
instance = FactoryRunner.new(name.underscore, FactoryGirl.strategy_by_name(:build), [overrides]).run
|
||||
instance.save
|
||||
yield(instance) if block_given?
|
||||
instance
|
||||
|
@ -51,14 +51,14 @@ module FactoryGirl
|
|||
|
||||
def generate!(overrides = {}, &block)
|
||||
ActiveSupport::Deprecation.warn "Model.generate! is deprecated; use the FactoryGirl.define syntax instead", caller
|
||||
instance = FactoryRunner.new(name.underscore, Strategy::Create, [overrides]).run
|
||||
instance = FactoryRunner.new(name.underscore, FactoryGirl.strategy_by_name(:create), [overrides]).run
|
||||
yield(instance) if block_given?
|
||||
instance
|
||||
end
|
||||
|
||||
def spawn(overrides = {}, &block)
|
||||
ActiveSupport::Deprecation.warn "Model.spawn is deprecated; use the FactoryGirl.define syntax instead", caller
|
||||
instance = FactoryRunner.new(name.underscore, Strategy::Build, [overrides]).run
|
||||
instance = FactoryRunner.new(name.underscore, FactoryGirl.strategy_by_name(:build), [overrides]).run
|
||||
yield(instance) if block_given?
|
||||
instance
|
||||
end
|
||||
|
|
|
@ -29,12 +29,12 @@ module FactoryGirl
|
|||
|
||||
def make(overrides = {})
|
||||
ActiveSupport::Deprecation.warn "Model.make is deprecated; use the FactoryGirl.define syntax instead", caller
|
||||
FactoryRunner.new(name.underscore, Strategy::Build, [overrides]).run
|
||||
FactoryRunner.new(name.underscore, FactoryGirl.strategy_by_name(:build), [overrides]).run
|
||||
end
|
||||
|
||||
def make!(overrides = {})
|
||||
ActiveSupport::Deprecation.warn "Model.make! is deprecated; use the FactoryGirl.define syntax instead", caller
|
||||
FactoryRunner.new(name.underscore, Strategy::Create, [overrides]).run
|
||||
FactoryRunner.new(name.underscore, FactoryGirl.strategy_by_name(:create), [overrides]).run
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,87 +1,6 @@
|
|||
module FactoryGirl
|
||||
module Syntax
|
||||
module Methods
|
||||
# Generates and returns a Hash of attributes from this factory. Attributes
|
||||
# can be individually overridden by passing in a Hash of attribute => value
|
||||
# pairs.
|
||||
#
|
||||
# Arguments:
|
||||
# * name: +Symbol+ or +String+
|
||||
# The name of the factory that should be used.
|
||||
# * traits_and_overrides: +Array+
|
||||
# [+*Array+] Traits to be applied
|
||||
# [+Hash+] Attributes to overwrite for this set.
|
||||
# * block:
|
||||
# Yields the hash of attributes.
|
||||
#
|
||||
# Returns: +Hash+
|
||||
# A set of attributes that can be used to build an instance of the class
|
||||
# this factory generates.
|
||||
def attributes_for(name, *traits_and_overrides, &block)
|
||||
FactoryRunner.new(name, Strategy::AttributesFor, traits_and_overrides).run(&block)
|
||||
end
|
||||
|
||||
# Generates and returns an instance from this factory. Attributes can be
|
||||
# individually overridden by passing in a Hash of attribute => value pairs.
|
||||
#
|
||||
# Arguments:
|
||||
# * name: +Symbol+ or +String+
|
||||
# The name of the factory that should be used.
|
||||
# * traits_and_overrides: +Array+
|
||||
# [+*Array+] Traits to be applied
|
||||
# [+Hash+] Attributes to overwrite for this instance.
|
||||
# * block:
|
||||
# Yields the built instance.
|
||||
#
|
||||
# Returns: +Object+
|
||||
# An instance of the class this factory generates, with generated attributes
|
||||
# assigned.
|
||||
def build(name, *traits_and_overrides, &block)
|
||||
FactoryRunner.new(name, Strategy::Build, traits_and_overrides).run(&block)
|
||||
end
|
||||
|
||||
# Generates, saves, and returns an instance from this factory. Attributes can
|
||||
# be individually overridden by passing in a Hash of attribute => value
|
||||
# pairs.
|
||||
#
|
||||
# Instances are saved using the +save!+ method, so ActiveRecord models will
|
||||
# raise ActiveRecord::RecordInvalid exceptions for invalid attribute sets.
|
||||
#
|
||||
# Arguments:
|
||||
# * name: +Symbol+ or +String+
|
||||
# The name of the factory that should be used.
|
||||
# * traits_and_overrides: +Array+
|
||||
# [+*Array+] Traits to be applied
|
||||
# [+Hash+] Attributes to overwrite for this instance.
|
||||
# * block:
|
||||
# Yields the created instance.
|
||||
#
|
||||
# Returns: +Object+
|
||||
# A saved instance of the class this factory generates, with generated
|
||||
# attributes assigned.
|
||||
def create(name, *traits_and_overrides, &block)
|
||||
FactoryRunner.new(name, Strategy::Create, traits_and_overrides).run(&block)
|
||||
end
|
||||
|
||||
# Generates and returns an object with all attributes from this factory
|
||||
# stubbed out. Attributes can be individually overridden by passing in a Hash
|
||||
# of attribute => value pairs.
|
||||
#
|
||||
# Arguments:
|
||||
# * name: +Symbol+ or +String+
|
||||
# The name of the factory that should be used.
|
||||
# * traits_and_overrides: +Array+
|
||||
# [+*Array+] Traits to be applied
|
||||
# [+Hash+] Attributes to overwrite for this instance.
|
||||
# * block
|
||||
# Yields the stubbed object.
|
||||
#
|
||||
# Returns: +Object+
|
||||
# An object with generated attributes stubbed out.
|
||||
def build_stubbed(name, *traits_and_overrides, &block)
|
||||
FactoryRunner.new(name, Strategy::Stub, traits_and_overrides).run(&block)
|
||||
end
|
||||
|
||||
# Builds and returns multiple instances from this factory as an array. Attributes can be
|
||||
# individually overridden by passing in a Hash of attribute => value pairs.
|
||||
#
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
require "spec_helper"
|
||||
|
||||
shared_context "registering custom strategies" do
|
||||
before do
|
||||
define_class("NamedObject") do
|
||||
attr_accessor :name
|
||||
end
|
||||
end
|
||||
|
||||
let(:custom_strategy) do
|
||||
Class.new do
|
||||
def result(evaluation)
|
||||
evaluation.object.tap do |instance|
|
||||
instance.name = "Custom strategy"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "register custom strategies" do
|
||||
include_context "registering custom strategies"
|
||||
|
||||
before do
|
||||
FactoryGirl.define do
|
||||
factory :named_object do
|
||||
name "Great"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "allows overriding default strategies" do
|
||||
FactoryGirl.build(:named_object).name.should == "Great"
|
||||
FactoryGirl.register_strategy(:build, custom_strategy)
|
||||
FactoryGirl.build(:named_object).name.should == "Custom strategy"
|
||||
end
|
||||
|
||||
it "allows adding additional strategies" do
|
||||
FactoryGirl.register_strategy(:insert, custom_strategy)
|
||||
|
||||
FactoryGirl.build(:named_object).name.should == "Great"
|
||||
FactoryGirl.insert(:named_object).name.should == "Custom strategy"
|
||||
end
|
||||
end
|
||||
|
||||
describe "including FactoryGirl::Syntax::Methods when custom strategies have been declared" do
|
||||
include FactoryGirl::Syntax::Methods
|
||||
|
||||
include_context "registering custom strategies"
|
||||
|
||||
before do
|
||||
FactoryGirl.define do
|
||||
factory :named_object do
|
||||
name "Great"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "allows adding additional strategies" do
|
||||
FactoryGirl.register_strategy(:insert, custom_strategy)
|
||||
|
||||
insert(:named_object).name.should == "Custom strategy"
|
||||
end
|
||||
end
|
||||
|
||||
describe "associations without overriding :strategy" do
|
||||
include_context "registering custom strategies"
|
||||
|
||||
before do
|
||||
define_model("Post", user_id: :integer) do
|
||||
belongs_to :user
|
||||
end
|
||||
|
||||
define_model("User", name: :string)
|
||||
|
||||
FactoryGirl.define do
|
||||
factory :post do
|
||||
user
|
||||
end
|
||||
|
||||
factory :user do
|
||||
name "John Doe"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "uses the overridden create strategy to create the association" do
|
||||
FactoryGirl.register_strategy(:create, custom_strategy)
|
||||
post = FactoryGirl.build(:post)
|
||||
post.user.name.should == "Custom strategy"
|
||||
end
|
||||
end
|
||||
|
||||
describe "associations overriding :strategy" do
|
||||
include_context "registering custom strategies"
|
||||
|
||||
before do
|
||||
define_model("Post", user_id: :integer) do
|
||||
belongs_to :user
|
||||
end
|
||||
|
||||
define_model("User", name: :string)
|
||||
|
||||
FactoryGirl.define do
|
||||
factory :post do
|
||||
association :user, strategy: :insert
|
||||
end
|
||||
|
||||
factory :user do
|
||||
name "John Doe"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "uses the overridden create strategy to create the association" do
|
||||
FactoryGirl.register_strategy(:insert, custom_strategy)
|
||||
post = FactoryGirl.build(:post)
|
||||
post.user.name.should == "Custom strategy"
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue