Reputation: 4588
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
Upvotes: 0
Views: 535
Reputation: 4588
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})
Upvotes: 0
Reputation: 923
The correct syntax to manually create an association between a pre-existing Testbed and a Group is to
Retrieve the group you want to assign the testbed to using its ID:
group = App.Groups.get_group!(1)
Retrieve the testbed you want to assign to the group using its ID:
testbed = App.Testbeds.get_testbed!(1)
Build the association:
thing = Ecto.build_assoc(testbed, :group, group)
Insert the association into the database using Repo.insert:
Repo.insert(thing)
This will create the association between the testbed and the group, and the group_id field in the testbed will be updated with the ID of the group.
If you are receiving error when trying to insert a new Group, it means there must be a duplicate name in the database
adding an unique_constraint to the changeset options will solve this issue, as this will add the constraint as an error and prevent the duplicate value from being inserted into the database.
def changeset(group, attrs) do
group
|> cast(attrs, [:name])
|> validate_required([:name])
|> unique_constraint(:name)
end
or you can check for duplicate names before inserting or updating the record using Ecto's Repo module.
Upvotes: 0