Insert a record using Ecto with a belongs_to association field

Hello! :wave: 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!

1 Like

Hey there, welcome! If I understand what you’re aiming at, the docs here should give you a good starting point: Associations — Ecto v3.11.2

And maybe more specific: Associations — Ecto v3.11.2

1 Like

the belongs_to adds a organization_id field to your schema, you can just cast that.

3 Likes

Thank you very much! That seemed to do the trick. Simple and effective, I appreciate it.