Open dkarter opened 2 weeks ago
Not one of the maintainers and I may be completely wrong, but let's see if I can help.
I don't remember seeing that specific error, and I'm assuming a lot of stuff since I don't know the underlying database schema, so apologies if I'm completely off.
Attributes passed to factories are expected to be the final attributes for the struct. Looking at your use of factories, when you run:
insert(:account, users: [%{email: "foo@example.com"}])
what you're actually doing is creating the following account:
%Account{
# other fields
users: [%{email: "foo@example.com"}]
}
Notice the list under :users
is just being put in the struct, not being passed down to another factory. ExMachina makes no assumptions on how you want to build the dependencies. Also, even if you were to pass an actual user struct, this would not create the account-user association.
So you need to actually create the Account
and the User
, and then the AccountUser
, and then preload it from the account:
account = insert(:account)
user = insert(:user, email: "foo@example.com")
_account_user = insert(:account_user, account: account, user: user)
# this will load the users into the account struct
account = Repo.reload(account, :users)
I personally prefer to use pipes as described in the docs, and have something like with_user
handle the details of creating the user, and linking it to the account:
insert(:account)
|> with_user(email: "foo@example.com")
# and in the factories
def account_factory
# ...
end
def with_user(%Account{id: id} = account) when not is_nil(id) do
# if ID is not nil, assume it is persisted
user = insert(:user, email: "foo@example.com")
account_user = insert(:account_user, account: account, user: user)
account
|> Repo.preload([:account_users, :users])
|> Map.update!(:account_users, &[account_user | &1])
|> Map.update!(:users, &[user | &1])
end
May be completely unrelated, but I noticed some possibly weird things in your schemas.
schema
will assume there is an integer ID field and that field is the primary key.belongs_to
and has_many
work by using a foreign key that links the two database tables, and usually the foreign key on one side is the primary key on the other side.Looking at all your schemas, you did not override the primary key definition, so I would assume they are using the integer ID as the primary key. But on AccountUser
you override the belongs_to
type to use :string
.
I would expect AccountUser
to work just fine like this:
defmodule ShareStream.Accounts.AccountUser do
use Ecto.Schema
schema "account_users" do
belongs_to :account, Account
belongs_to :user, User
timestamps(type: :utc_datetime)
end
end
@pfac Thank you so much for the detailed response! 💜
The with_user
approach worked!
And I guess I can create other custom wrappers (which is good enough for now), I just wish it was part of the library. This is my first time using ExMachina - usually I just roll my own factory module that uses pipes to automatically chain related records.
This code though was not necessary because the preload already populates the keys on the struct:
def with_user(%Account{id: id} = account) when not is_nil(id) do
# if ID is not nil, assume it is persisted
user = insert(:user, email: "foo@example.com")
account_user = insert(:account_user, account: account, user: user)
account
|> Repo.preload([:account_users, :users])
- |> Map.update!(:account_users, &[account_user | &1])
- |> Map.update!(:users, &[user | &1])
end
For the schema issues - I just omitted the primary key definition because I felt that it was unrelated and I didn't want to distract from the problem - turns out it was more distracting to leave it out 😆, but good eye!
This what the account really looks like:
defmodule ShareStream.Accounts.Account do
use Ecto.Schema
@primary_key {:id, UXID, autogenerate: true, prefix: "act", size: :medium}
schema "accounts" do
field :name, :string
has_many :account_users, AccountUser
has_many :users, through: [:account_users, :user]
timestamps(type: :utc_datetime)
end
end
They all have a similar primary key annotation.
Thanks again!
I read through the code and documentation but can't figure out how to insert a has_many through association using ExMachina.
I have the following schema:
Account:
User:
AccountUser:
Here's my factory:
Pretty common setup I think.
I'm trying to insert an account with associated users:
I'm hoping this will automatically insert the connecting table without having to explicitly specify that.
The test code for ExMachina contains a fixture with a
has_many
through
foreditors
on User: https://github.com/beam-community/ex_machina/blob/6663050ef2f8fe0f3a1109451689d30f3dcb46b4/test/support/models/user.ex#L12However I couldn't find any test that uses that field.
Seems like it's looking for a key that's no longer there? I'm using Ecto 3.11.2 and EctoSQL 3.11.3