I am learning how to configure a belong_to / has_many relationship between two tables. I have the configuration complete but I don't know the syntax to invoke it. I want to take a pre-existing Table called Testbed and assign it to another Table called Group.
This is a document I am writing to record the process , so I walk through most of the steps to where I am now.
In Phoenix I generated the two tables and related UI code using these commands:
mix phx.gen.html Testbeds Testbed testbeds name:string
mix phx.gen.html Groups Group groups name:string
The command line prompted me to add the resources / route for both of them. I did what it said.
I ran:
mix ecto.migrate
Testbed Migration
For testbeds, I create a migration file and add this field: :group_id, references(:groups)
defmodule App.Repo.Migrations.TestbedUpdate do
use Ecto.Migration
def change do
alter table(:testbeds) do
add :group_id, references(:groups) # added
end
end
end
Change Group Schema
I update the Group schema. My changes are noted in Comments
defmodule App.Groups.Group do
use Ecto.Schema
import Ecto.Changeset
alias App.Testbeds.Testbed # Alias!
schema "groups" do
field :name, :string
has_many :testbeds, Testbed # Has many
timestamps()
end
@doc false
def changeset(group, attrs) do
group
|> cast(attrs, [:name])
|> validate_required([:name])
end
end
Change Testbed Schema
defmodule App.Testbeds.Testbed do
use Ecto.Schema
import Ecto.Changeset
alias App.Groups.Group # Alias
schema "testbeds" do
field :name, :string
belongs_to :group, Group # Belongs to
timestamps()
end
@doc false
def changeset(testbed, attrs) do
testbed
|> cast(attrs, [:name])
|> validate_required([:name])
end
end
I read that I needed to incorporate “preload” so that I can read data. So I did this:
def list_testbeds do
Repo.all(Testbed)
|> Repo.preload([:group])
end
and this
def list_groups do
Repo.all(Group)
|> Repo.preload([:testbeds])
end
When I create Groups and Testbeds and I query them, the result looks like this:
%App.Groups.Group{
__meta__: #Ecto.Schema.Metadata<:loaded, "groups">,
id: 3,
name: "FUNK",
testbeds: [],
inserted_at: ~N[2023-09-27 18:13:15],
updated_at: ~N[2023-09-27 18:13:15]
}
%App.Testbeds.Testbed{
__meta__: #Ecto.Schema.Metadata<:loaded, "testbeds">,
id: 1,
name: "Bloop",
group_id: nil,
group: nil,
inserted_at: ~N[2023-09-27 14:04:15],
updated_at: ~N[2023-09-27 18:22:23]
}
I now need to know how to write code that assigns a Testbed to a Group.
Create a Group
App.Groups.create_group(%{name: "OINK"})
Submit Testbed and Assign to a Group Association
This code will create a new testbed and assign it to the Group that has an ID of 1.
group = App.Groups.get_group!(1)
thing = Ecto.build_assoc(group, :testbeds, name: "DUMB")
alias App.{Repo}
Repo.insert(thing)
When you query the group you will see one with a field name testbeds that is assigned a List. The List contains a Testbed struct.
So now my question, if I want to take a pre-created testbed and make the same kind of association that the previous commands did, what do I do? I simply don't know the syntax and everything that I've tried leads to error.
I've tried to create a testbed and assign it a group_id manually like this:
App.Testbeds.update_testbed(my_testbed,%{group_id: 2,name: "some-name"})
It doesn't affect the group_id and it remains nil.
EDIT NOTE
These are the Testbed Function Created Per the Generator
defmodule App.Testbeds do
@moduledoc """
The Testbeds context.
"""
import Ecto.Query, warn: false
alias App.Repo
alias App.Testbeds.Testbed
@doc """
Returns the list of testbeds.
## Examples
iex> list_testbeds()
[%Testbed{}, ...]
"""
def list_testbeds do
Repo.all(Testbed)
|> Repo.preload([:group])
end
@doc """
Gets a single testbed.
Raises `Ecto.NoResultsError` if the Testbed does not exist.
## Examples
iex> get_testbed!(123)
%Testbed{}
iex> get_testbed!(456)
** (Ecto.NoResultsError)
"""
def get_testbed!(id), do: Repo.get!(Testbed, id)
@doc """
Creates a testbed.
## Examples
iex> create_testbed(%{field: value})
{:ok, %Testbed{}}
iex> create_testbed(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_testbed(attrs \\ %{}) do
%Testbed{}
|> Testbed.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a testbed.
## Examples
iex> update_testbed(testbed, %{field: new_value})
{:ok, %Testbed{}}
iex> update_testbed(testbed, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_testbed(%Testbed{} = testbed, attrs) do
testbed
|> Testbed.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a testbed.
## Examples
iex> delete_testbed(testbed)
{:ok, %Testbed{}}
iex> delete_testbed(testbed)
{:error, %Ecto.Changeset{}}
"""
def delete_testbed(%Testbed{} = testbed) do
Repo.delete(testbed)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking testbed changes.
## Examples
iex> change_testbed(testbed)
%Ecto.Changeset{data: %Testbed{}}
"""
def change_testbed(%Testbed{} = testbed, attrs \\ %{}) do
Testbed.changeset(testbed, attrs)
end
end
I needed to update the schema to reference the group_id.
defmodule App.Testbeds.Testbed do
use Ecto.Schema
import Ecto.Changeset
alias App.Groups.Group # Alias
schema "testbeds" do
field :name, :string
belongs_to :group, Group # Belongs to
timestamps()
end
@doc false
def changeset(testbed, attrs) do
testbed
|> cast(attrs, [:name, :group_id]) # group_id <--- Answer
|> validate_required([:name])
end
end
Then performing these commands adds the testbed to the group
group = App.Groups.get_group!(1)
item = App.Items.get_item!(1)
App.Items.update_item(item, %{group_id: group.id})