Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes '<attribute>' and '<attribute>_id' conflict. #1709

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions lib/factory_bot/attribute_assigner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def attribute_names_to_assign
non_ignored_attribute_names +
override_names -
ignored_attribute_names -
alias_names_to_ignore
aliased_attribute_names_to_ignore
end

def non_ignored_attribute_names
Expand All @@ -91,22 +91,39 @@ def override_names
@evaluator.__override_names__
end

def attribute_names
@attribute_list.names
end

def hash_instance_methods_to_respond_to
@attribute_list.names + override_names + @build_class.instance_methods
end

def alias_names_to_ignore
##
# Creat a list of attribute names that will be
# overridden by an alias, so any defaults can
# ignored.
#
def aliased_attribute_names_to_ignore
@attribute_list.non_ignored.flat_map { |attribute|
override_names.map do |override|
attribute.name if ignorable_alias?(attribute, override)
attribute.name if aliased_attribute?(attribute, override)
end
}.compact
end

def ignorable_alias?(attribute, override)
attribute.alias_for?(override) &&
attribute.name != override &&
!ignored_attribute_names.include?(override)
##
# Is the override an alias for the attribute and not the
# actual name of another attribute?
#
# Note: Checking against the names of all attributes, resolves any
# issues with having both <attribute> and <attribute>_id
# in the same factory.
#
def aliased_attribute?(attribute, override)
return false if attribute_names.include?(override)

attribute.alias_for?(override)
end
end
end
86 changes: 86 additions & 0 deletions spec/factory_bot/attribute_assignment_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
describe "Attribute Assignment" do
it "sets the <attribute> default value if not overriden" do
name = :user
define_class("User") { attr_accessor :name, :response }
factory = FactoryBot::Factory.new(name)
FactoryBot::Internal.register_factory(factory)

attr_1 = FactoryBot::Declaration::Dynamic.new(:name, false, -> { "orig name" })
attr_2 = FactoryBot::Declaration::Dynamic.new(:response, false, -> { "orig response" })
factory.declare_attribute(attr_1)
factory.declare_attribute(attr_2)

user = factory.run(FactoryBot::Strategy::Build, {})

expect(user.name).to eq "orig name"
expect(user.response).to eq "orig response"
end

it "set the <attribute> when directly named" do
name = :user
define_class("User") { attr_accessor :name, :response }
factory = FactoryBot::Factory.new(name)
FactoryBot::Internal.register_factory(factory)

attr_1 = FactoryBot::Declaration::Dynamic.new(:name, false, -> { "orig name" })
attr_2 = FactoryBot::Declaration::Dynamic.new(:response, false, -> { "orig response" })
factory.declare_attribute(attr_1)
factory.declare_attribute(attr_2)

user = factory.run(
FactoryBot::Strategy::Build,
name: "new name",
response: "new response"
)

expect(user.name).to eq "new name"
expect(user.response).to eq "new response"
end

it "Does not set a default if the attribute's alias is used" do
name = :user
define_class("User") { attr_accessor :name, :response_id }
factory = FactoryBot::Factory.new(name)
FactoryBot::Internal.register_factory(factory)

attr_1 = FactoryBot::Declaration::Dynamic.new(:name, false, -> { "orig name" })
attr_2 = FactoryBot::Declaration::Dynamic.new(:response_id, false, -> { 42 })
factory.declare_attribute(attr_1)
factory.declare_attribute(attr_2)

user = factory.run(
FactoryBot::Strategy::AttributesFor,
name: "new name",
response: 13.75
)

expect(user[:name]).to eq "new name"
expect(user[:response]).to eq 13.75
expect(user[:response_id]).to be_nil
end

it "sets both <attribute> and <attribute>_id correctly" do
name = :user
define_class("User") { attr_accessor :name, :response, :response_id }
factory = FactoryBot::Factory.new(name)
FactoryBot::Internal.register_factory(factory)

attr_1 = FactoryBot::Declaration::Dynamic.new(:name, false, -> { "orig name" })
attr_2 = FactoryBot::Declaration::Dynamic.new(:response, false, -> { "orig response" })
attr_3 = FactoryBot::Declaration::Dynamic.new(:response_id, false, -> { 42 })
factory.declare_attribute(attr_1)
factory.declare_attribute(attr_2)
factory.declare_attribute(attr_3)

user = factory.run(
FactoryBot::Strategy::Build,
name: "new name",
response: "new response",
response_id: 13.75
)

expect(user.name).to eq "new name"
expect(user.response).to eq "new response"
expect(user.response_id).to eq 13.75
end
end