Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Abandoning models - embracing data

Abandoning models - embracing data

A gentle introduction to Ecto

Michał Muskała

June 09, 2016
Tweet

More Decks by Michał Muskała

Other Decks in Programming

Transcript

  1. @michalmuskala Warszawa, 9.06.2016
    Abandoning models - embracing data
    A gentle introduction to Ecto

    View Slide

  2. View Slide

  3. View Slide

  4. @michalmuskala Warszawa, 9.06.2016
    Abandoning models - embracing data
    A gentle introduction to Ecto

    View Slide

  5. @michalmuskala Warszawa, 9.06.2016
    Michał Muskała
    http://michal.muskala.eu/
    https://github.com/michalmuskala/
    @michalmuskala

    View Slide

  6. View Slide

  7. View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. @michalmuskala Warszawa, 9.06.2016
    The Experiment

    View Slide

  12. @michalmuskala Warszawa, 9.06.2016
    Who used Elixir?

    View Slide

  13. @michalmuskala Warszawa, 9.06.2016
    Who used Ecto?

    View Slide

  14. @michalmuskala Warszawa, 9.06.2016
    Who has Ruby background?

    View Slide

  15. @michalmuskala Warszawa, 9.06.2016
    Who has C# or .NET background?

    View Slide

  16. @michalmuskala Warszawa, 9.06.2016
    Inspirations
    LINQ - queries
    ActiveRecord - associations
    Other Elixir projects - Moebius - pipeline queries
    Other Ruby projects - rom, trailblazer - focus on data

    View Slide

  17. @michalmuskala Warszawa, 9.06.2016
    Adapter-based architecture
    Database adapters
    Connection pool adapters
    db_connection library

    View Slide

  18. @michalmuskala Warszawa, 9.06.2016
    Ecto
    Ecto.Schema
    Ecto.Query
    Ecto.Repo
    Ecto.Changeset

    View Slide

  19. @michalmuskala Warszawa, 9.06.2016
    Ecto.Schema
    defmodule Weather do
    use Ecto.Schema
    schema "weather" do
    belongs_to :city, City
    field :wdate, Ecto.Date
    field :temp_lo, :integer
    field :temp_hi, :integer
    field :prcp, :float, default: 0.0
    timestamps
    end
    end

    View Slide

  20. @michalmuskala Warszawa, 9.06.2016
    Ecto.Schema
    declares fields - no SELECT * FROM foos
    allows for custom types
    thin wrapper around Elixir’s structs
    defines data - not behaviour

    View Slide

  21. @michalmuskala Warszawa, 9.06.2016
    Ecto.Query
    from w in Weather,
    where: w.prcp <= 0.0 or is_nil(w.prcp),
    select: w
    from s in Search,
    where: ilike(fragment("?::text", s.name),
    ^name_search_term) or
    fragment("to_tsvector(?) @@ to_tsquery(?)",
    s.description, ^term)

    View Slide

  22. @michalmuskala Warszawa, 9.06.2016
    Ecto.Query
    from p in Post,
    where: fragment(position: ["$geoWithin": [...]])
    def all(filter) do
    filter.q
    |> sanitize
    |> search
    |> preload(:location)
    |> order_by([s] asc: s.date, desc: s.type)
    end

    View Slide

  23. @michalmuskala Warszawa, 9.06.2016
    Ecto.Query
    defmacrop geo_search(coordinates, geo_point, radius) do
    quote do
    fragment("ST_DWithin(?, ?, ?)",
    unquote(coordinates),
    unquote(geo_point),
    unquote(radius)
    )
    end
    end

    View Slide

  24. @michalmuskala Warszawa, 9.06.2016
    Ecto.Query
    def by_location(query, %{lat: lat, lng: lng, radius: radius}) do
    geo_point = %Geo.Point{coordinates: {lat, lng}}
    from provider in query,
    left_join: location_provider in assoc(provider, :location),
    left_join: events in assoc(provider, :events),
    left_join: location_event in assoc(events, :location),
    where:
    geo_search(location_provider.coordinates, ^geo_point, ^radius)
    or
    geo_search(location_event.coordinates, ^geo_point, ^radius),
    distinct: true,
    select: provider
    end

    View Slide

  25. @michalmuskala Warszawa, 9.06.2016
    Ecto.Query
    query = from p in Post,
    select: [:visits],
    order_by: [desc: :visits],
    limit: 10
    TestRepo.all(from p in subquery(query),
    select: avg(p.visits))

    View Slide

  26. @michalmuskala Warszawa, 9.06.2016
    Ecto.Query
    Two syntaxes - keyword and pipeline
    No lazy-loading, explicit preloads
    Easily extensible via fragments and macros
    Translated by adapter
    Composable

    View Slide

  27. @michalmuskala Warszawa, 9.06.2016
    Ecto.Repo
    defmodule Simple.Repo do
    use Ecto.Repo, otp_app: :simple
    end
    def start(_type, _args) do
    import Supervisor.Spec
    tree = [supervisor(Simple.Repo, [])]
    opts = [name: Simple.Sup, strategy: :one_for_one]
    Supervisor.start_link(tree, opts)
    end

    View Slide

  28. @michalmuskala Warszawa, 9.06.2016
    Ecto.Repo
    Isolates all impure operations
    Is an interface to the database
    Outside of repo - everything is plain data

    View Slide

  29. @michalmuskala Warszawa, 9.06.2016
    Ecto.Changeset
    def changeset(schema, params) do
    schema
    |> cast(params, @params_required ++ @params_optional)
    |> validate_required(@params_required)
    |> validate_params
    |> cast_assoc(:location, required: true)
    |> cast_assoc(:age_group, required: true)
    |> cast_embed(:links)
    |> validate_length(:name, min: @min_length_name)
    |> validate_format(:image_url, @regexp_image)
    |> validate_inclusion(:category, Category.values)
    |> validate_email
    |> unique_constraint(:email)
    end

    View Slide

  30. @michalmuskala Warszawa, 9.06.2016
    Ecto.Changeset
    Encodes data changes as a data structure
    No validations are global
    Multiple changieret functions for different circumstances
    Easily extensible with functions

    View Slide

  31. @michalmuskala Warszawa, 9.06.2016
    Ecto.Multi
    def password_reset_multi(account, params) do
    account = account_password_reset(account, params)
    access_log = log_password_reset(account, params)
    Multi.new
    |> Multi.update(:account, account)
    |> Multi.insert(:log, access_log)
    |> Multi.delete_all(:sessions, assoc(account, :sessions))
    end

    View Slide

  32. @michalmuskala Warszawa, 9.06.2016
    Ecto.Multi
    defmodule UserManager do
    def password_reset(params) do
    with {:ok, account} <- load_account(params),
    multi = password_reset_multi(account, params),
    {:ok, results} <- Repo.transaction(multi),
    :ok <- send_notification(results.account),
    do: {:ok, results.account}
    end
    # ...
    end

    View Slide

  33. @michalmuskala Warszawa, 9.06.2016
    Ecto.Multi
    Encodes transaction as a data structure
    Changesets for transactions
    You can test your transaction without database!

    View Slide

  34. @michalmuskala Warszawa, 9.06.2016
    Schema less queries
    from u in "users",
    join: a in "activities",
    on: a.user_id == u.id,
    where: a.start_at > type(^start_at, Ecto.DateTime) and
    a.end_at < type(^end_at, Ecto.DateTime),
    group_by: a.user_id,
    select: %{user_id: a.user_id,
    interval: a.start_at - a.end_at,
    count: count(u.id)}

    View Slide

  35. @michalmuskala Warszawa, 9.06.2016
    Schema less queries
    # insert data
    [%{id: id}] =
    MyApp.Repo.insert_all("posts", [[title: "hello"]],
    returning: [:id])
    # use query for updates
    post = from p in "posts", where: p.id == ^id
    {1, _} = MyApp.Repo.update_all(post,
    set: [title: "new title"])
    # and deletes
    {1, _} = MyApp.Repo.delete_all post

    View Slide

  36. @michalmuskala Warszawa, 9.06.2016
    Table less schemas
    defmoudule RegistrationSchema do
    use Ecto.Schema
    schema "" do
    field :foo, :string
    field :bar, :integer
    embeds_one :resource, Resource
    end
    # ...
    end

    View Slide

  37. @michalmuskala Warszawa, 9.06.2016
    Schema less changesets
    data = %{}
    types = %{first_name: :string,
    last_name: :string,
    email: :string}
    fields = Map.keys(types)
    changeset =
    {data, types}
    |> Ecto.Changeset.cast(params, fields)
    |> validate_required(...)
    |> validate_length(...)

    View Slide

  38. @michalmuskala Warszawa, 9.06.2016
    Concurrent, transactional tests
    In 1.1 - database tests sequential, 2.0 - concurrent
    Works with postgres, mysql has wired transaction semantics
    Works with Wallaby and Hound

    View Slide

  39. @michalmuskala Warszawa, 9.06.2016
    No models, no callbacks
    Ecto 1.1 deprecated models, embraced schemas, 2.0 removes models
    Ecto 1.1 deprecated callbacks, 2.0 removed them
    I’m forbidden by José to use the world “model”
    Phoenix will switch away from the model nomenclature

    View Slide

  40. @michalmuskala Warszawa, 9.06.2016
    KISS
    Plain CRUD interfaces are easy
    When you need more power - you have it
    We want not to force anything on the users, but keep the simplicity

    View Slide

  41. @michalmuskala Warszawa, 9.06.2016
    –José Valim
    “I am completely OK with taking the blame regarding Ecto 1.0. We did
    push folks to the wrong direction. But Ecto 2.0 no longer puts you in
    this place[…]. If you are using structs as your domain models, then it
    will be more on you than on me, since you have everything to stop
    doing that[…].”

    View Slide

  42. @michalmuskala Warszawa, 9.06.2016
    Ecto is less opinionated

    View Slide

  43. @michalmuskala Warszawa, 9.06.2016
    Ecto is more extensible

    View Slide

  44. @michalmuskala Warszawa, 9.06.2016
    Ecto is data centric

    View Slide

  45. @michalmuskala Warszawa, 9.06.2016
    Ecto is not a solution to the problem

    View Slide

  46. @michalmuskala Warszawa, 9.06.2016
    Ecto is a toolbox for building solutions

    View Slide

  47. @michalmuskala Warszawa, 9.06.2016
    Abandoning models - embracing data
    A gentle introduction to Ecto

    View Slide