Ecto - What is the best practice for populating two tables in one form

I am trying to make an association between tables.

I currently have a “users” table with the following fields:

id email password first_name last_name role
1 hash Bob White teacher
2 hash Joe Brown student

I am trying to implement two new user types: teachers and students

As such, I want to make two different tables for both user types which will reference the main table like so:

  • teachers table
id user_id first_name last_name
1 1 (reference to “users”) Bob White
  • students table
id user_id first_name last_name
1 2 (reference to “users”) Joe Brown

I generated a new migration and created the “teachers” table like so:

create table("teachers") do
   add :user_id, references("users")
   add :first_name, :string
   add :last_name, :string

I then defined a schema for the teachers table like this:

schema "teachers" do
   field :first_name, :string
   field :last_name, :string

   belongs_to :user, User

Lastly, I added this to the user schema:

   has_many :teachers, Teacher

I have now successfully added the teachers table with my desired columns. The problem is, my registration form is only accommodating one changeset. The user schema does all the validation and adds the user into the “users” table when the registration is successful. But the “teachers” table remains empty. What would be the best practice to populate the teachers table with the registering user’s info?

The best way is to use cast_assoc in the changeset.

But You did not show the changeset, nor the input form.

It seems to me that You are duplicating information in two tables, first_name and last_name…

BTW Your design looks like You are trying to design some inheritance. Like a teacher/student are inheriting from user?

You might try STI.

Don’t use inheritance in Elixir, use composition.

This is the input form:


<%= form_for @changeset, @action, [as: :user], fn f -> %>
  <%= if @changeset.action do %>
    <div class="alert alert-danger">
      <p>Oops, something went wrong! Please check the errors below.</p>
  <% end %>

  <%= label f, :role, "Are you a Teacher or Student?" %>
  <%= select f, :role, ["Student": "student", "Teacher": "teacher"] %>
  <%= error_tag f, :role %>

  <%= label f, Pow.Ecto.Schema.user_id_field(@changeset) %>
  <%= text_input f, Pow.Ecto.Schema.user_id_field(@changeset) %>
  <%= error_tag f, Pow.Ecto.Schema.user_id_field(@changeset) %>

  <%= label f, :first_name %>
  <%= text_input f, :first_name %>
  <%= error_tag f, :first_name %>

  <%= label f, :last_name %>
  <%= text_input f, :last_name %>
  <%= error_tag f, :last_name %>

  <%= label f, :password %>
  <%= password_input f, :password %>
  <%= error_tag f, :password %>

  <%= label f, :password_confirmation %>
  <%= password_input f, :password_confirmation %>
  <%= error_tag f, :password_confirmation %>

    <%= submit "Register" %>
<% end %>

I am using pow library for user authentication. This is what the changeset looks like in the user schema:

  def changeset(user_or_changeset, attrs) do
    |> pow_changeset(attrs)
    |> Ecto.Changeset.cast(attrs, [
    |> Ecto.Changeset.validate_required([:role, :first_name, :last_name])

Yes I want the “teachers” table to inherit some fields from the user table.

You might try to use STI with postgresql, or Ecto STI.

But You should also think data first and composition, not classes and inheritance :slight_smile:

It’s not really an association, it does not have custom fields, and You could generate automatically.

And Your form show that a simple boolean field is_teacher could be a simpler solution.

Thanks for this, I am still a beginner so could you elaborate on data & composition?

Do you have any links to documentation that could help me set this up and populate the teachers table automatically?

The design of OOP and FP systems is different.

I just watched this video, where You can see how different it is… but don’t remember where I got the recommendation.

For Your request, I would add an Ecto Enum field type ~w(student teacher somethingelse) to your User table.