ex_machina
ex_machina copied to clipboard
Handle belongs_to associations foreign key
Hey guys. Thanks for the great lib first of all.
Imagine I've got schemas:
schema "comment" do
field :body
belongs_to :user, User
end
So I'm creating a factory:
def comment_factory do
%Comment{
body: "Some random text"
user: build(:user)
}
end
And I want to modify the user_id field in my tests to check constraints, for example, like insert(:comment, user_id: 1337)
, but it doesn't work: ** (ArgumentError) cannot change belongs_to association 'user' because there is already a change setting its foreign key 'user_id' to '1337'
.
Is there a way to do what I want? Does it make sense to patch the lib to make it work?
Hi @pyromaniac,
I don't know how others might solve this, but here are my thoughts (and I hope I understood your question correctly).
It all depends on what qualifies as a valid comment
in your system. The factory should define the minimum implementation required to construct that comment
.
In your case, if a comment
does not need a user
associated to it in order to be valid, I would update the factory to just have the body,
def comment_factory do·
%Comment{body: "Some random text"}
end
That way you can just pass the user_id
whenever you want.
If, on the other hand, a comment
must always have a user
associated to it in order to be valid, then leave the comment_factory
as is and test the constraint without the factory. So if you test was using something like this,
insert(:comment, user_id: 1337)
you can just do something like this instead,
%Comment{body: "Some random text", user_id: 1337}
|> Repo.insert
That way your factory represents the valid case, and you can just test this particular case without a factory.
Hey @germsvel,
Yeah, in my case user is required, so this is exactly the problem. And I would agree with you for simple cases. Troubles start when you have like a 10-columns model and you really don't want to insert it manually. So I'm just proposing some automation and, probably, convenient behavior, it is clear that it is possible to bypass it one way or another, but do we really have to?
@pyromaniac looking at this again, I think what you might want to do is something like this,
user = build(:user, id: 1337)
build(:comment, user: user)
|> insert()
That way, you can set the id
but don't have to handle setting the other 10-columns in the model you are talking about.
That seems to me like the solution most in line with making factories flexible with pipes described in the README.
I think there's another way to achieve the same thing, via the primary key:
insert(:user, id: 1337)
build(:comment, user: nil, user_id: 1337)
|> insert()
I imagine it would be a useful extension to ExMachina to allow setting the assoc's ID attribute or the assoc object attribute interchangeably. That way one wouldn't need to remember to nil
out the assoc attribute when an ID is passed.