Hello! Relatively new to Elixir, and brand new Phoenix LiveView & Ecto user here.
(Using: Elixir 1.17.1 and Phoenix LiveView 1.0.0-rc.6.)
At a high level, my question boils down to:
How can I insert/create a new record in the database using Ecto when one of the schema fields is a belongs_to
association, and the given value already exists in the database?
Please see the code provided below for the full context, but I’d like to be able to create a new user by providing an existing organization, not create a new associated organization.
I have run the Phoenix authentication generator for LiveView (mix phx.gen.auth
) and am in the process of making tweaks to the generated schema to suit my project needs. Namely, I have the following simple structure:
- an organization can have many users associated with it
- a user can only belong to a single organization
I have mapped these relationships out using the following two modules (I have omitted other functions in each module for brevity):
The users schema (this is what was generated from mix phx.gen.auth
and has been modified for my requirements):
# lib/example/accounts/user.ex
defmodule Example.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "users" do
field :email, :string
field :password, :string, virtual: true, redact: true
field :hashed_password, :string, redact: true
field :current_password, :string, virtual: true, redact: true
field :confirmed_at, :utc_datetime
belongs_to :organization, Example.Accounts.Organization
timestamps(type: :utc_datetime)
end
end
The organizations schema:
# lib/example/accounts/organization.ex
defmodule Example.Accounts.Organization do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "organizations" do
field :name, :string
field :active, :boolean, default: true
has_many :users, Example.Accounts.User
timestamps(type: :utc_datetime)
end
end
This is how I’ve currently solved the issue, but I feel like it’s unnecessary to first query the Organization
simply as a way to use it in the User
for the Repo.insert()
changeset (since I already have the organization UUID)
# (simplified example)
def register_user(attrs) when is_map(attrs) do
# example `attrs`
# %{"email" => "name@example.com", "organization_id" => "7051b9fd-cb24-41ba-b5ca-79cd257612ff", "password" => "[FILTERED]"}
organization = get_organization!(attrs["organization_id"])
%User{organization: organization}
|> cast(attrs, [:email, :password])
|> validate_required([:organization])
|> validate_email(opts)
|> validate_password(opts)
|> Repo.insert()
end
This approach works but I’m not sure it’s the recommended approach for this pattern. Can someone please help point me in the right direction or offer some advice on the canonical way to achieve this (if this is not correct)? Thanks so much in advance!