ex_machina icon indicating copy to clipboard operation
ex_machina copied to clipboard

Handle belongs_to associations foreign key

Open pyromaniac opened this issue 6 years ago • 4 comments

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?

pyromaniac avatar Sep 15 '17 18:09 pyromaniac

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.

germsvel avatar Sep 19 '17 22:09 germsvel

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 avatar Sep 20 '17 04:09 pyromaniac

@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.

germsvel avatar Oct 27 '17 14:10 germsvel

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.

febeling avatar Nov 10 '17 16:11 febeling