Generating nested attributes
rjasper-frohraum opened this issue · comments
rjasper-frohraum commented
Hi, I've been looking for a nice way to generate nested attributes with FactoryBot. There are already quite a few posts about this but I didn't find a solution which worked for me.
What I'd like to have is something like attributes_for
which also resolves associations as hashes recursively. Below is a solution I came up with. (I am not sure whether this is the right place for this but maybe it is of some help.)
Register this strategy
class NestedAttributesForStrategy
Config = Struct.new(:associations, :nested_attributes)
def association(runner)
runner.run(:nested_attributes_for)
end
def result(evaluation)
config = Config.new([], {})
evaluation.notify(:nested_attributes_for, config)
result = evaluation.hash.merge(config.nested_attributes)
config.associations.each do |association|
result.delete(association)
end
result
end
end
FactoryBot.register_strategy(:nested_attributes_for, NestedAttributesForStrategy)
Define global trait
FactoryBot.define do
trait :with_nested_attributes do
transient do
nested_attributes_for { [] }
end
callback(:nested_attributes_for) do |config, ctx|
associations = ctx.nested_attributes_for
nested_attributes = config.nested_attributes
config.associations = associations
associations.each do |association|
nested_attributes[:"#{association}_attributes"] = ctx.send(association)
end
end
end
end
Use it in your factories
FactoryBot.define do
# The models below normally wouldn't make for a good example of nested attributes.
# Ignore OpenStruct. I used it so you don't need corresponding models for copy-paste testing.
factory :post, class: 'OpenStruct' do
with_nested_attributes
transient do
nested_attributes_for { %i[comments author] }
end
author
comments { [association(:comment)] }
content { 'Nice weather today!' }
end
factory :comment, class: 'OpenStruct' do
with_nested_attributes
transient do
nested_attributes_for { %i[author] }
end
association :author, name: 'Jon Doe'
message { 'I like your post' }
end
factory :author, class: 'OpenStruct' do
name { 'Jane Doe' }
end
end
Invoke
nested_attributes_for(:post).to_yaml
Result:
---
:content: Nice weather today!
:comments_attributes:
- :message: I like your post
:author_attributes:
:name: Jon Doe
:author_attributes:
:name: Jane Doe
sofiyareverse commented
Good idea! Maybe it would be better to create a pull request?