Skip to content
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
11 changes: 6 additions & 5 deletions lib/factory_bot/declaration/association.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,28 @@ module FactoryBot
class Declaration
# @api private
class Association < Declaration
def initialize(name, *options)
def initialize(name, *options, **overrides)
super(name, false)
@options = options.dup
@overrides = options.extract_options!
@overrides = overrides.dup
@factory_name = @overrides.delete(:factory) || name
@traits = options
end

def ==(other)
self.class == other.class &&
name == other.name &&
options == other.options
options == other.options &&
overrides == other.overrides
end

protected

attr_reader :options
attr_reader :options, :overrides

private

attr_reader :factory_name, :overrides, :traits
attr_reader :factory_name, :traits

def build
raise_if_arguments_are_declarations!
Expand Down
20 changes: 9 additions & 11 deletions lib/factory_bot/definition_proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,15 @@ def transient(&block)
# end
#
# are equivalent.
def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing
association_options = args.first

if association_options.nil?
def method_missing(name, *args, **kwargs, &block) # rubocop:disable Style/MissingRespondToMissing
if args.empty? && kwargs.empty?
__declare_attribute__(name, block)
elsif __valid_association_options?(association_options)
association(name, association_options)
elsif __valid_association_options?(args, kwargs)
association(name, *args, **kwargs)
else
raise NoMethodError.new(<<~MSG)
undefined method '#{name}' in '#{@definition.name}' factory
Did you mean? '#{name} { #{association_options.inspect} }'
Did you mean? '#{name} { #{args.first.inspect} }'
MSG
end
end
Expand Down Expand Up @@ -152,14 +150,14 @@ def sequence(name, *args, &block)
# If no name is given, the name of the attribute is assumed to be the
# name of the factory. For example, a "user" association will by
# default use the "user" factory.
def association(name, *options)
def association(name, *options, **overrides)
if block_given?
raise AssociationDefinitionError.new(
"Unexpected block passed to '#{name}' association " \
"in '#{@definition.name}' factory"
)
else
declaration = Declaration::Association.new(name, *options)
declaration = Declaration::Association.new(name, *options, **overrides)
@definition.declare_attribute(declaration)
end
end
Expand Down Expand Up @@ -253,8 +251,8 @@ def __declare_attribute__(name, block)
end
end

def __valid_association_options?(options)
options.respond_to?(:has_key?) && options.has_key?(:factory)
def __valid_association_options?(options, overrides)
(!options.empty? && options.all?(Symbol)) || !overrides.empty?
end

##
Expand Down
49 changes: 49 additions & 0 deletions spec/acceptance/traits_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,55 @@ def initialize(name)
end
end

describe "traits used in implicit associations" do
before do
define_model("User", admin: :boolean, name: :string)

define_model("Comment", text: :string, user_id: :integer) do
belongs_to :user
end

define_model("Post", user_id: :integer) do
belongs_to :user, class_name: "User"
end

FactoryBot.define do
factory :user do
admin { false }

trait :admin do
admin { true }
end

trait :robot do
name { "robot" }
end
end

factory :comment do
text { "testing" }
user :admin, name: "Joe Slick"
end

factory :post do
user :admin, :robot
end
end
end

it "allows for a single inline trait with the default association" do
user = FactoryBot.create(:comment).user
expect(user).to be_admin
expect(user.name).to eq "Joe Slick"
end

it "allows for multiple inline traits with the default association" do
user = FactoryBot.create(:post).user
expect(user).to be_admin
expect(user.name).to eq "robot"
end
end

describe "traits used in associations" do
before do
define_model("User", admin: :boolean, name: :string)
Expand Down
11 changes: 8 additions & 3 deletions spec/support/matchers/declaration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ def with_factory(factory)
self
end

def with_options(options)
def with_options(*options, **kwargs)
@options = options
@kwargs = kwargs
self
end

Expand All @@ -60,7 +61,7 @@ def expected_declaration
when :implicit then FactoryBot::Declaration::Implicit.new(@name, @factory, ignored?)
when :association
if @options
FactoryBot::Declaration::Association.new(@name, options)
FactoryBot::Declaration::Association.new(@name, *options, **kwargs)
else
FactoryBot::Declaration::Association.new(@name)
end
Expand All @@ -72,7 +73,11 @@ def ignored?
end

def options
@options || {}
@options || []
end

def kwargs
@kwargs || {}
end
end
end