mirror of
https://github.com/thoughtbot/factory_bot.git
synced 2022-11-09 11:43:51 -05:00
Optionally disable duplicate assignment of attributes in initialize_with
By setting: FactoryGirl.duplicate_attribute_assignment_from_initialize_with = false This turns off duplicate assignment of attributes accessed from initialize_with. This means any attributes accessed from the factory and assigned in the initialize_with block won't be subsequently set after the object has been instantiated. This will be the default functionality in 4.0. Closes #345
This commit is contained in:
parent
64e50a3121
commit
a5b3a97c9d
10 changed files with 136 additions and 3 deletions
|
@ -771,6 +771,15 @@ FactoryGirl.define do
|
|||
end
|
||||
```
|
||||
|
||||
When using `initialize_with`, attributes accessed from the `initialize_with`
|
||||
block are assigned a second time to the instance. Duplicate assignment can be
|
||||
disabled by configuring FactoryGirl as such:
|
||||
|
||||
FactoryGirl.duplicate_attribute_assignment_from_initialize_with = false
|
||||
|
||||
This would allow for attributes declared as ignored to not be ignored, since
|
||||
it won't assign them for a second time.
|
||||
|
||||
Custom Strategies
|
||||
-----------------
|
||||
|
||||
|
|
|
@ -37,6 +37,9 @@ require 'factory_girl/syntax'
|
|||
require 'factory_girl/syntax_runner'
|
||||
require 'factory_girl/find_definitions'
|
||||
require 'factory_girl/reload'
|
||||
require 'factory_girl/decorator'
|
||||
require 'factory_girl/decorator/invocation_tracker'
|
||||
require 'factory_girl/decorator/invocation_ignorer'
|
||||
require 'factory_girl/version'
|
||||
|
||||
module FactoryGirl
|
||||
|
@ -50,7 +53,8 @@ module FactoryGirl
|
|||
|
||||
class << self
|
||||
delegate :factories, :sequences, :traits, :strategies, :callback_names,
|
||||
:to_create, :skip_create, :initialize_with, :constructor, to: :configuration
|
||||
:to_create, :skip_create, :initialize_with, :constructor, :duplicate_attribute_assignment_from_initialize_with,
|
||||
:duplicate_attribute_assignment_from_initialize_with=, to: :configuration
|
||||
end
|
||||
|
||||
def self.register_factory(factory)
|
||||
|
|
|
@ -30,8 +30,28 @@ module FactoryGirl
|
|||
|
||||
private
|
||||
|
||||
def method_tracking_evaluator
|
||||
@method_tracking_evaluator ||= invocation_decorator.new(@evaluator)
|
||||
end
|
||||
|
||||
def invocation_decorator
|
||||
if FactoryGirl.duplicate_attribute_assignment_from_initialize_with
|
||||
Decorator::InvocationIgnorer
|
||||
else
|
||||
Decorator::InvocationTracker
|
||||
end
|
||||
end
|
||||
|
||||
def methods_invoked_on_evaluator
|
||||
@method_tracking_evaluator.__invoked_methods__
|
||||
end
|
||||
|
||||
def build_class_instance
|
||||
@build_class_instance ||= @evaluator.instance_exec(&@instance_builder)
|
||||
@build_class_instance ||= method_tracking_evaluator.instance_exec(&@instance_builder).tap do
|
||||
if @instance_builder != FactoryGirl.constructor && FactoryGirl.duplicate_attribute_assignment_from_initialize_with
|
||||
ActiveSupport::Deprecation.warn 'Accessing attributes from initialize_with when duplicate assignment is enabled is deprecated; use FactoryGirl.duplicate_attribute_assignment_from_initialize_with = false.', caller
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_hash
|
||||
|
@ -43,7 +63,7 @@ module FactoryGirl
|
|||
end
|
||||
|
||||
def attributes_to_set_on_instance
|
||||
(attribute_names_to_assign - @attribute_names_assigned).uniq
|
||||
(attribute_names_to_assign - @attribute_names_assigned - methods_invoked_on_evaluator).uniq
|
||||
end
|
||||
|
||||
def attributes_to_set_on_hash
|
||||
|
|
|
@ -3,6 +3,8 @@ module FactoryGirl
|
|||
class Configuration
|
||||
attr_reader :factories, :sequences, :traits, :strategies, :callback_names
|
||||
|
||||
attr_accessor :duplicate_attribute_assignment_from_initialize_with
|
||||
|
||||
def initialize
|
||||
@factories = DisallowsDuplicatesRegistry.new(Registry.new('Factory'))
|
||||
@sequences = DisallowsDuplicatesRegistry.new(Registry.new('Sequence'))
|
||||
|
@ -11,6 +13,8 @@ module FactoryGirl
|
|||
@callback_names = Set.new
|
||||
@definition = Definition.new
|
||||
|
||||
@duplicate_attribute_assignment_from_initialize_with = true
|
||||
|
||||
to_create {|instance| instance.save! }
|
||||
initialize_with { new }
|
||||
end
|
||||
|
|
17
lib/factory_girl/decorator.rb
Normal file
17
lib/factory_girl/decorator.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
module FactoryGirl
|
||||
class Decorator < BasicObject
|
||||
undef_method :==
|
||||
|
||||
def initialize(component)
|
||||
@component = component
|
||||
end
|
||||
|
||||
def method_missing(name, *args, &block)
|
||||
@component.send(name, *args, &block)
|
||||
end
|
||||
|
||||
def send(symbol, *args)
|
||||
__send__(symbol, *args)
|
||||
end
|
||||
end
|
||||
end
|
9
lib/factory_girl/decorator/invocation_ignorer.rb
Normal file
9
lib/factory_girl/decorator/invocation_ignorer.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
module FactoryGirl
|
||||
class Decorator
|
||||
class InvocationIgnorer < Decorator
|
||||
def __invoked_methods__
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
lib/factory_girl/decorator/invocation_tracker.rb
Normal file
19
lib/factory_girl/decorator/invocation_tracker.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
module FactoryGirl
|
||||
class Decorator
|
||||
class InvocationTracker < Decorator
|
||||
def initialize(component)
|
||||
super
|
||||
@invoked_methods = []
|
||||
end
|
||||
|
||||
def method_missing(name, *args, &block)
|
||||
@invoked_methods << name
|
||||
super
|
||||
end
|
||||
|
||||
def __invoked_methods__
|
||||
@invoked_methods.uniq
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,6 +2,8 @@ require 'spec_helper'
|
|||
|
||||
describe 'global initialize_with' do
|
||||
before do
|
||||
ActiveSupport::Deprecation.silenced = true
|
||||
|
||||
define_class('User') do
|
||||
attr_accessor:name
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ describe "initialize_with with non-FG attributes" do
|
|||
include FactoryGirl::Syntax::Methods
|
||||
|
||||
before do
|
||||
ActiveSupport::Deprecation.silenced = true
|
||||
|
||||
define_model("User", name: :string, age: :integer) do
|
||||
def self.construct(name, age)
|
||||
new(name: name, age: age)
|
||||
|
@ -26,6 +28,8 @@ describe "initialize_with with FG attributes that are ignored" do
|
|||
include FactoryGirl::Syntax::Methods
|
||||
|
||||
before do
|
||||
ActiveSupport::Deprecation.silenced = true
|
||||
|
||||
define_model("User", name: :string) do
|
||||
def self.construct(name)
|
||||
new(name: "#{name} from .construct")
|
||||
|
@ -51,6 +55,8 @@ describe "initialize_with with FG attributes that are not ignored" do
|
|||
include FactoryGirl::Syntax::Methods
|
||||
|
||||
before do
|
||||
ActiveSupport::Deprecation.silenced = true
|
||||
|
||||
define_model("User", name: :string) do
|
||||
def self.construct(name)
|
||||
new(name: "#{name} from .construct")
|
||||
|
@ -75,6 +81,8 @@ describe "initialize_with non-ORM-backed objects" do
|
|||
include FactoryGirl::Syntax::Methods
|
||||
|
||||
before do
|
||||
ActiveSupport::Deprecation.silenced = true
|
||||
|
||||
define_class("ReportGenerator") do
|
||||
attr_reader :name, :data
|
||||
|
||||
|
@ -108,6 +116,8 @@ end
|
|||
|
||||
describe "initialize_with parent and child factories" do
|
||||
before do
|
||||
ActiveSupport::Deprecation.silenced = true
|
||||
|
||||
define_class("Awesome") do
|
||||
attr_reader :name
|
||||
|
||||
|
@ -148,6 +158,8 @@ end
|
|||
|
||||
describe "initialize_with implicit constructor" do
|
||||
before do
|
||||
ActiveSupport::Deprecation.silenced = true
|
||||
|
||||
define_class("Awesome") do
|
||||
attr_reader :name
|
||||
|
||||
|
@ -171,3 +183,38 @@ describe "initialize_with implicit constructor" do
|
|||
FactoryGirl.build(:awesome, name: "Awesome name").name.should == "Awesome name"
|
||||
end
|
||||
end
|
||||
|
||||
describe "initialize_with doesn't duplicate assignment on attributes accessed from initialize_with" do
|
||||
before do
|
||||
ActiveSupport::Deprecation.silenced = true
|
||||
|
||||
define_class("User") do
|
||||
attr_reader :name
|
||||
attr_accessor :email
|
||||
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
end
|
||||
|
||||
FactoryGirl.define do
|
||||
sequence(:email) {|n| "person#{n}@example.com" }
|
||||
|
||||
factory :user do
|
||||
email
|
||||
|
||||
name { email.gsub(/\@.+/, "") }
|
||||
|
||||
initialize_with { new(name) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "instantiates the correct object" do
|
||||
FactoryGirl.duplicate_attribute_assignment_from_initialize_with = false
|
||||
|
||||
built_user = FactoryGirl.build(:user)
|
||||
built_user.name.should == "person1"
|
||||
built_user.email.should == "person1@example.com"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -490,6 +490,8 @@ end
|
|||
|
||||
describe "traits with initialize_with" do
|
||||
before do
|
||||
ActiveSupport::Deprecation.silenced = true
|
||||
|
||||
define_class("User") do
|
||||
attr_reader :name
|
||||
|
||||
|
|
Loading…
Reference in a new issue