thoughtbot / thoughtbot/factory_bot
How to handle polymorphic associations using exclusive arc
Nobody has claimed this yet.
- Dominant language
- Ruby
- Stars
- 8.2k
- Forks
- 2.6k
- PR merge metrics
- No merged PRs in 30d
Description
Situation
Currently, modeling polymorphic associations is pretty simple, as demonstrated in GETTING_STARTED.md. However, I have a polymorphic association using the Exclusive Belongs To/Exclusive Arc method (also seen here using Rails), which makes the above solution not work anymore because I have to make two optional associations instead of a single polymorphic one (I've left out the validation logic for clarity):
class Team < ActiveRecord::Base
has_many :posts
end
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :team, optional: true
belongs_to :user, optional: true
end
This leads me to have to awkwardly define the associations in an after(:build) block, relying on a transient and traits that only set the transient to determine which of the two associated models to build/create:
FactoryBot.define do
factory :team
factory :user
factory :post do
transient do
# default to :team
team_or_user { :team }
end
trait :team_owned do
team_or_user { :team }
end
trait :user_owned do
team_or_user { :user }
end
after(:build) do |post, evaluator|
if evaluator.team_or_user == :team
post.team = evaluator.team || evaluator.association(:team, strategy: :build)
elsif evaluator.team_or_user == :user
post.user = evaluator.user || evaluator.association(:user, strategy: :build)
end
end
end
end
As seen above, I call strategy: :build, but that's not ideal because I don't know the chosen strategy (which could lead to issues or missing data in other strategies). I also have to rely on the consumers of this factory to know to pass in team or user without being able to see it in the main factory body (defined like a regular association). I feel like there are other edge cases I'm not considering yet, but I haven't come across them yet. Finally, this is a lot of work to get a "simple" association to work properly.
Question/Discussion
Is this the best solution? Any ideas for better ways to do this that aren't so cumbersome? It makes me wish for an "anti-trait", a way to say "in this invocation, ignore the specified trait" so I could just put the associations in their traits and then put the anti-traits in the opposite traits, making it "obvious":
factory :post do
team_owned
trait :team_owned do
anti_trait :user_owned
team
end
trait :user_owned do
anti_trait :team_owned
user
end
end
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with REPRODUCTION_SCRIPT.txt and the polymorphic-associations section of GETTING_STARTED.md, then compare them with the factory definition shown in the issue. The issue has no failing test or defined implementation target; a contribution would first need to establish the desired behavior for exclusive-arc associations and strategies.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rails, ruby
- Domain
- testing
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100