Subscribe for only $15 to access all of our content

#46: Ecto Virtual Attributes

Elixir 1.5

Phoenix 1.3


In this episode let’s see how we can use virtual attributes in Ecto to solve a common problem - when we need to include fields that don’t map to columns in the database.

Here we have a signup form with email, full name, and password fields

And now instead of displaying a full name field, we want to break it up into two field - one for the first name and one for the last name.

Let’s open our signup template and change our form to use a first name and last name.

Template path: lib/teacher_web/templates/user/form.html.eex

...
<div class="form-group">
  <%= label f, :first_name, class: "control-label" %>
  <%= text_input f, :first_name, class: "form-control" %>
  <%= error_tag f, :first_name %>
</div>

<div class="form-group">
  <%= label f, :last_name, class: "control-label" %>
  <%= text_input f, :last_name, class: "form-control" %>
  <%= error_tag f, :last_name %>
</div>
...

Then we’ll go to our user.ex module and add the first name and last name as virtual attributes.

lib/teacher/accounts/user.ex

...
schema "users" do
  field :encrypted_password, :string
  field :email, :string
  field :full_name, :string
  field :first_name, :string, virtual: true
  field :last_name, :string, virtual: true

  timestamps()
end
...

Now let’s go to our browser and if we try to signup we get an error.

This is because we’re now collecting the first and last name and submitting them to the server, but we’re not using them to build a full name - which as we can see in our changeset function is a required field. Let’s fix that.

We’ll need to create a function that will get the first and last name from the changeset and combine them to create our full name that we can then persist to the database. Let’s define a private function named build_full_name that will take the changeset as a parameter.

Then we can use the Ecto.Changeset.get_field function to grab the first name and then the last name. Then we’ll use Ecto.Changeset.put_change to update our changes with the full_name key.

And for the value we’ll use string interpolation to build our name. put_change will return our updated changeset, which is great because we can now include our new build_full_name function in our existing pipeline.

So let’s go back to our changeset function and include build_full_name. We’ll also need to update our cast function to include :first_name and :last_name.

lib/teacher/accounts/user.ex

...
def changeset(%User{} = user, attrs) do
  user
  |> cast(attrs, [:email, :encrypted_password, :first_name, :last_name])
  |> build_full_name()
  |> validate_required([:email, :encrypted_password, :full_name])
  |> unique_constraint(:email)
  |> update_change(:encrypted_password, &Bcrypt.hashpwsalt/1)
end

defp build_full_name(changeset) do
  first_name = get_field(changeset, :first_name)
  last_name = get_field(changeset, :last_name)
  put_change(changeset, :full_name, "#{first_name} #{last_name}")
end

And if we go back to the browser and sign up - it works.

Let’s also check the database just to make sure. And our new user is in there with the full name in the proper format.

More Episodes

Alchemist's Edition

#93: Ecto.Multi

In this episode we’ll explore Ecto.Multi in order to compose database transactions in Elixir.

Watch episode

#92: Introduction to Testing

Testing is an essential part of software development. In this episode we’ll get an introduction to testing in Elixir using the ExUnit test framework.

Watch episode

#91: Filtering Parameters in Phoenix

Often you’ll have sensitive information that you don’t want to show up logs. In this episode we’ll see how to configure Phoenix to specify what parameters you want filtered in your logs.

Watch episode