Skip to content

Instantly share code, notes, and snippets.

@zorn
Created September 7, 2021 20:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zorn/03c05c182457c63e91776022a06c09bb to your computer and use it in GitHub Desktop.
Save zorn/03c05c182457c63e91776022a06c09bb to your computer and use it in GitHub Desktop.

Ecto Changeset Demo and Talk

What is Ecto?

  • Historic personal use of Active Record pattern in PHP, Rails, iOS dev.
  • Ecto described itself as a toolkit for data mapping and language integrated query for Elixir.
  • Changeset were (for me) a new concept that I've really come to appriciate.

Many of the demos you'll see come from Ecto's own Getting Started and I encourage you to check it out as well.

https://hexdocs.pm/ecto/getting-started.html

What is a Changeset?

https://hexdocs.pm/ecto/Ecto.Changeset.html

Changesets allow filtering, casting, validation and definition of constraints when manipulating structs.

Setup Ecto ETS storage

Mix.install([
  {:ecto, "~> 3.6"},
  {:etso, "~> 0.1.5"}
])

Setup Repo

defmodule Friends.Repo do
  use Ecto.Repo, otp_app: :friends, adapter: Etso.Adapter
end

Friends.Repo.start_link([])

Create Person Schema

defmodule Friends.Person do
  use Ecto.Schema

  schema "people" do
    field(:first_name, :string)
    field(:last_name, :string)
    field(:age, :integer)
  end
end

Sample People Schema Usage

person = %Friends.Person{}
person = %Friends.Person{age: 28}

Inserting data into the Repo

person = %Friends.Person{first_name: "Mike", last_name: "Zornek", age: 21}
Friends.Repo.insert(person)

Validating data using a Changeset

# Can't reopen a previous module in a LiveBook.
defmodule PersonTools do
  def changeset(person, params \\ %{}) do
    person
    |> Ecto.Changeset.cast(params, [:first_name, :last_name, :age])
    |> Ecto.Changeset.validate_required([:first_name, :last_name])
  end
end

person = %Friends.Person{age: 25}
changeset = PersonTools.changeset(person, %{})
{:error, result_changeset} = Friends.Repo.insert(changeset)
result_changeset.data

Changeset internals

The Ecto.Changeset struct

The public fields are:

  • valid? - Stores if the changeset is valid
  • data - The changeset source data, for example, a struct
  • params - The parameters as given on changeset creation
  • changes - The changes from parameters that were approved in casting
  • errors - All errors from validations
  • required - All required fields as a list of atoms
  • action - The action to be performed with the changeset
  • types - Cache of the data's field types
  • empty_values - A list of values to be considered empty
  • repo - The repository applying the changeset (only set after a Repo function is called)
  • repo_opts - A keyword list of options given to the underlying repository operation

The following fields are private and must not be accessed directly.

  • validations
  • constraints
  • filters
  • prepare

Validation tools

Lots of validation options: https://hexdocs.pm/ecto/Ecto.Changeset.html#validate_acceptance/3

Handy database constraint tools: https://hexdocs.pm/ecto/Ecto.Changeset.html#unique_constraint/3

Changesets to power web forms

https://github.com/elixirfocus/get_shorty/blob/main/lib/get_shorty_web/templates/short_link/new.html.eex

<%= form_for @changeset, Routes.short_link_path(@conn, :create), fn f -> %>

  <%= if @changeset.action do %>
    <div class="alert alert-danger">
      <p>Could not create short link. See errors below.</p>
    </div>
  <% end %>

  <p>Enter in the <%= label f, :long_link, "link", [style: "display: inline; font-weight:300"] %> 
  you would like shortened in the field below.</p>

  <%= text_input f, :long_link %>
  <%= error_tag f, :long_link %>

  <div>
    <%= submit "Create Short Link" %>
  </div>
<% end %>

Understanding the risk of common Changeset dependancies

https://elixirfocus.com/posts/ecto-schemaless-changesets/

A common but brittle approach that creates strong dependencies across all the layers.

Schemaless Changesets

params = %{"sign_up" => %{"name" => "Mike", "email" => "zorn@elixirfocus.com"}}
data = %{}
types = %{name: :string, email: :string}

# The data+types tuple is equivalent to %Registration{}
changeset =
  {data, types}
  |> Ecto.Changeset.cast(params["sign_up"], Map.keys(types))
  |> Ecto.Changeset.validate_required([:name])
  |> Ecto.Changeset.validate_length(:name, min: 1, max: 100)

More info on blog

https://elixirfocus.com/posts/ecto-schemaless-changesets/

Unknown challenge/homework of keeping validations DRY and dealing with issue where web form changeset WAS valid but DB version was not.

@NickDubelman
Copy link

does this work? i can't figure out how to define config in a livebook.. i see you define otp_app: :friends, but where is :friends configured??

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment