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

More Decks by Michał Muskała

Other Decks in Programming


  1. @michalmuskala Warszawa, 9.06.2016 Inspirations LINQ - queries ActiveRecord - associations

    Other Elixir projects - Moebius - pipeline queries Other Ruby projects - rom, trailblazer - focus on data
  2. @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
  3. @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
  4. @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)
  5. @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
  6. @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
  7. @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
  8. @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))
  9. @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
  10. @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
  11. @michalmuskala Warszawa, 9.06.2016 Ecto.Repo Isolates all impure operations Is an

    interface to the database Outside of repo - everything is plain data
  12. @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
  13. @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
  14. @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
  15. @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
  16. @michalmuskala Warszawa, 9.06.2016 Ecto.Multi Encodes transaction as a data structure

    Changesets for transactions You can test your transaction without database!
  17. @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)}
  18. @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
  19. @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
  20. @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(...)
  21. @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
  22. @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
  23. @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
  24. @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[…].”