Create Foreign Key Relationship Between Ecto Tables: Difference between revisions

From ElixirBlocks
Jump to: navigation, search
No edit summary
No edit summary
 
(17 intermediate revisions by the same user not shown)
Line 1: Line 1:
{{In_progress}}
'''Before You Begin'''


{{Phoenix-Installation-Required}}


My goal is to create two html generated tables in Phoenix.
==Overview==


* Testbed(s)
This document walks you through the creation of a has_many and belongs_to table relationship through the creation of two tables named '''Item''' and '''Group'''.
 
To accomplish this you will create two tables in Phoenix using the HTML generator.
 
* Item(s)
* Group(s)
* Group(s)


I want to create a relationship between these two tables so that:
You will create a relationship between these two tables so that:


* A Group can have many Testbeds
* A Group can have many Items
* Each Testbed can belong to 1 Group
* Each Item can belong to 1 Group
* I intend to write front end code that presents this relationship to the user.


Steps


In Phoenix I generated the two tables and related UI code using these commands:
==Getting Started==
 
In Phoenix, generate the two tables and related UI code using these commands:
<source>
<source>
mix phx.gen.html Testbeds Testbed testbeds name:string
mix phx.gen.html Items Item items name:string


mix phx.gen.html Groups Group groups name:string
mix phx.gen.html Groups Group groups name:string
</source>
</source>


The command line prompted me to add the resources / route for both of them. I did what it said.
Keep your eye on the command prompt.
The command line will prompt you to add the '''resources /''' route for each table. Do what it says.


I ran: <source> mix ecto.migrate </source>
After updating the routes, run the migrate command.


'''Testbed Migration
<source> mix ecto.migrate </source>
'''




'''Item Table Migration'''


For testbeds, I create a migration file and add this field: group_id:references:groups.


To create a migration file you CD into your app directory and run the migration creation code. In the code below replace "name-of-migration" with "test_update" or similar.
For the item table, you will create a migration file and add this field: group_id:references:groups. This is explained below.
 
To create a migration file, you CD into your app directory and run the migration creation command below. Replace "name-of-migration" with "items_update" or similar.


<source>
<source>
Line 39: Line 46:
</source>
</source>


In app/priv/repo/migrations is where the migration file is created. Update it to look like the following:
The migration file is created in this directory: '''app/priv/repo/migrations''' . Go to that directory and open the newly created migration file with your text editor. Update it to look like the following:


<source>
<source>
def change do
def change do
     alter table(:testbeds) do    # customize to your code
     alter table(:items) do     
       add :group_id, references(:groups)
       add :group_id, references(:groups) # create association
     end
     end
   end
   end
  </source>
  </source>
    
    
'''Change Group Schema'''
Run the migration:
I update Group schema. Changes in Comments
 
<source>
mix ecto.migrate
</source>
 
'''Change Group Table Schema'''
 
 
Update Group schema to look like the code below.  


<source>
<source>
Line 57: Line 72:
   import Ecto.Changeset
   import Ecto.Changeset
    
    
   alias App.Testbeds.Testbed   # Alias!
   alias App.Items.Item   # Alias!
   schema "groups" do
   schema "groups" do
     field :name, :string
     field :name, :string
     has_many :testbeds, Testbed   # Has many
     has_many :items, Item   # Has many
     timestamps()
     timestamps()
   end
   end
Line 75: Line 90:
</source>
</source>


'''Change Testbed Schema'''
'''Change Item Schema'''


<source>
<source>


defmodule App.Testbeds.Testbed do
defmodule App.Items.Item do
   use Ecto.Schema
   use Ecto.Schema
   import Ecto.Changeset
   import Ecto.Changeset
   alias App.Groups.Group      # Alias
   alias App.Groups.Group      # Alias
   schema "testbeds" do
   schema "items" do
     field :name, :string
     field :name, :string
     belongs_to :group, Group    # Belongs to
     belongs_to :group, Group    # Belongs to
Line 90: Line 105:


   @doc false
   @doc false
   def changeset(testbed, attrs) do
   def changeset(item, attrs) do
     testbed
     item
     |> cast(attrs, [:name, :group_id])  # group_id
     |> cast(attrs, [:name, :group_id])  # group_id
     |> validate_required([:name])
     |> validate_required([:name])
Line 98: Line 113:
</source>
</source>


I read that I needed to incorporate “preload” so that I can submit data. So I did this:
You will need to incorporate “preload” so that you can submit data. Go to app/lib/app/items.ex file and update it like this:


<source>
<source>
   def list_testbeds do
   def list_items do
     Repo.all(Testbed)
     Repo.all(Item)
     |> Repo.preload([:group])
     |> Repo.preload([:group]) # Preload
   end
   end
  </source>
  </source>
   
   
and this
Go to app/lib/app/groups.ex file and update it like this:


<source>
<source>
   def list_groups do
   def list_groups do
     Repo.all(Group)
     Repo.all(Group)
     |> Repo.preload([:testbeds])
     |> Repo.preload([:items])
   end
   end
  </source>
  </source>
    
    
When I create Groups and Testbeds and I query them, the result looks like this:
 
You now write code that assigns a Item to a Group.
==Create Table Entries ==
 
Open your terminal in your app directory to write this code.
 
<source>
App.Groups.create_group(%{name: "Group of items"})
App.Items.create_item(%{name: "an item"})
 
</source>
 
You can query the Groups and Items and view the result.
<source>
App.Groups.list_groups
 
App.Items.list_items
</source>
 
The result looks like this:


<source>
<source>
Line 123: Line 158:
     __meta__: #Ecto.Schema.Metadata<:loaded, "groups">,
     __meta__: #Ecto.Schema.Metadata<:loaded, "groups">,
     id: 3,
     id: 3,
     name: "FUNK",
     name: "Group of items",
     testbeds: [],
     items: [],
     inserted_at: ~N[2023-09-27 18:13:15],
     inserted_at: ~N[2023-09-27 18:13:15],
     updated_at: ~N[2023-09-27 18:13:15]
     updated_at: ~N[2023-09-27 18:13:15]
Line 130: Line 165:
    
    
   </source>
   </source>
When I query Testbeds they now look like this:
When you query Items they look like this:


<source>
<source>
%App.Testbeds.Testbed{
%App.Items.Item{
     __meta__: #Ecto.Schema.Metadata<:loaded, "testbeds">,
     __meta__: #Ecto.Schema.Metadata<:loaded, "items">,
     id: 1,
     id: 1,
     name: "Bloop",
     name: "an item",
     group_id: nil,
     group_id: nil,
     group: nil,
     group: nil,
Line 144: Line 179:
</source>
</source>


I now need to know how to write code that assigns a Testbed to a Group.
 
==Create a Group ==
== Assign an Existing Item to a Group ==
 
Open the terminal in your app directory to write this code.


<source>
<source>
App.Groups.create_group(%{name: "OINK"})
group = App.Groups.get_group!(1)
item = App.Items.get_item!(1)
App.Items.update_item(item, %{group_id: group.id})
</source>


</source>
Run this code to view the result. You will see the group with a field name items that is assigned a List. The List contains a Item struct.


<source>
App.Groups.list_groups


==Submit Testbed and Assign to a Group Association==
</source>
This code will create a new testbed and assign it to the Group that has an ID of 1.


==Submit Item and Assign to a Group Association==
This code creates a new item "on the fly" and assign it to the Group that has an ID of 1.


<source>
<source>
group = App.Groups.get_group!(1)
group = App.Groups.get_group!(1)
thing = Ecto.build_assoc(group, :testbeds, name: "DUMB")
thing = Ecto.build_assoc(group, :items, name: "DUMB")
alias App.{Repo}
alias App.{Repo}
Repo.insert(thing)
Repo.insert(thing)
Line 165: Line 208:
</source>
</source>


Run this code to view the result. You will see the group with a field name testbeds that is assigned a List. The List contains a Testbed struct.
==Aside ==
=== Rendering Nested Data in Templates ===


<source>
To render this form of nested data in Templates you use the inputs-for construct.
App.Groups.list_groups
 
https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#inputs_for/1


</source>
'''Example:'''


== Assign an Existing Testbed to a Group ==
<source>
<source>
group = App.Groups.get_group!(1)
<.inputs_for :let={f_nested} field={f[:nested]}>
testbed = App.Testbeds.get_testbed!(1)
    <.input type="text" field={f_nested[:name]} />
App.Testbeds.update_testbed(testbed, %{group_id: group.id})
</.inputs_for>
</source>
</source>

Latest revision as of 20:11, 20 October 2023

Before You Begin


Installing Phoenix

This article assumes that you have installed the Phoenix web framework and all its dependencies correctly without errors. If you have not installed the Phoenix web framework please view the documentation here


Overview

This document walks you through the creation of a has_many and belongs_to table relationship through the creation of two tables named Item and Group.

To accomplish this you will create two tables in Phoenix using the HTML generator.

  • Item(s)
  • Group(s)

You will create a relationship between these two tables so that:

  • A Group can have many Items
  • Each Item can belong to 1 Group


Getting Started

In Phoenix, generate the two tables and related UI code using these commands:

mix phx.gen.html Items Item items name:string

mix phx.gen.html Groups Group groups name:string

Keep your eye on the command prompt. The command line will prompt you to add the resources / route for each table. Do what it says.

After updating the routes, run the migrate command.

 mix ecto.migrate


Item Table Migration


For the item table, you will create a migration file and add this field: group_id:references:groups. This is explained below.

To create a migration file, you CD into your app directory and run the migration creation command below. Replace "name-of-migration" with "items_update" or similar.

mix ecto.gen.migration name-of-migration

The migration file is created in this directory: app/priv/repo/migrations . Go to that directory and open the newly created migration file with your text editor. Update it to look like the following:

def change do
    alter table(:items) do    
      add :group_id, references(:groups) # create association
    end
  end

Run the migration:

mix ecto.migrate

Change Group Table Schema


Update Group schema to look like the code below.

defmodule App.Groups.Group do
  use Ecto.Schema
  import Ecto.Changeset
  
  alias App.Items.Item   # Alias!
  schema "groups" do
    field :name, :string
    has_many :items, Item    # Has many
    timestamps()
  end

  @doc false
  def changeset(group, attrs) do
    group
    |> cast(attrs, [:name])
    |> validate_required([:name])
  end
end

Change Item Schema


defmodule App.Items.Item do
  use Ecto.Schema
  import Ecto.Changeset
  alias App.Groups.Group      # Alias
  schema "items" do
    field :name, :string
    belongs_to :group, Group    # Belongs to
    timestamps()
  end

  @doc false
  def changeset(item, attrs) do
    item
    |> cast(attrs, [:name, :group_id])   # group_id
    |> validate_required([:name])
  end
end

You will need to incorporate “preload” so that you can submit data. Go to app/lib/app/items.ex file and update it like this:

  def list_items do
    Repo.all(Item)
    |> Repo.preload([:group]) # Preload
  end

Go to app/lib/app/groups.ex file and update it like this:

  def list_groups do
    Repo.all(Group)
     |> Repo.preload([:items])
  end


You now write code that assigns a Item to a Group.

Create Table Entries

Open your terminal in your app directory to write this code.

App.Groups.create_group(%{name: "Group of items"})
App.Items.create_item(%{name: "an item"})

You can query the Groups and Items and view the result.

App.Groups.list_groups

App.Items.list_items

The result looks like this:


 %App.Groups.Group{
    __meta__: #Ecto.Schema.Metadata<:loaded, "groups">,
    id: 3,
    name: "Group of items",
    items: [],
    inserted_at: ~N[2023-09-27 18:13:15],
    updated_at: ~N[2023-09-27 18:13:15]
  }

When you query Items they look like this:

%App.Items.Item{
    __meta__: #Ecto.Schema.Metadata<:loaded, "items">,
    id: 1,
    name: "an item",
    group_id: nil,
    group: nil,
    inserted_at: ~N[2023-09-27 14:04:15],
    updated_at: ~N[2023-09-27 18:22:23]
  }


Assign an Existing Item to a Group

Open the terminal in your app directory to write this code.

group = App.Groups.get_group!(1)
item = App.Items.get_item!(1)
App.Items.update_item(item, %{group_id: group.id})

Run this code to view the result. You will see the group with a field name items that is assigned a List. The List contains a Item struct.

App.Groups.list_groups

Submit Item and Assign to a Group Association

This code creates a new item "on the fly" and assign it to the Group that has an ID of 1.

group = App.Groups.get_group!(1)
thing = Ecto.build_assoc(group, :items, name: "DUMB")
alias App.{Repo}
Repo.insert(thing)

Aside

Rendering Nested Data in Templates

To render this form of nested data in Templates you use the inputs-for construct.

https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#inputs_for/1

Example:

<.inputs_for :let={f_nested} field={f[:nested]}>
    <.input type="text" field={f_nested[:name]} />
</.inputs_for>