Successfully reported this slideshow.

Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir



Upcoming SlideShare
Yurii Bodarev - Ecto DSL
Yurii Bodarev - Ecto DSL
Loading in …3
1 of 110
1 of 110

More Related Content

More from Elixir Club

Related Books

Free with a 14 day trial from Scribd

See all

Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

  1. 1. OTP, Phoenix & Ecto: Three Pillars of Elixir Elixir Club Ternopil, 2017
  2. 2. Erlang Functional Programming in Erlang Concurrent Programming in Erlang Learn You Some Erlang for great good!
  3. 3. Overview 1. Processes & OTP 2. Phoenix Elixir web framework 3. Ecto database wrapper and language integrated query for Elixir
  4. 4. Erlang/Elixir processes • All code runs inside processes • Processes are isolated from each other, run concurrent to one another and communicate via message passing (Actor model) • Processes are extremely lightweight in terms of memory and CPU and managed by Erlang VM • It is common to have tens or even hundreds of thousands of processes running simultaneously
  5. 5. Spawning basic process iex> spawn fn -> 100 * 100 end #PID<0.94.0> iex> self() #PID<0.80.0>
  6. 6. Sending and receiving messages iex> parent = self() #PID<0.80.0> iex> spawn fn -> send(parent, {:hello, self()}) end #PID<0.103.0> iex> receive do ...> {:hello, pid} -> "Got hello from #{inspect pid}" ...> {:other, _} -> "Something other" ...> end "Got hello from #PID<0.103.0>"
  7. 7. Receive timeout iex> receive do ...> {:other, msg} -> msg ...> after ...> 1_000 -> "Nothing received after 1s" ...> end "Nothing received after 1s"
  8. 8. Flush iex> send self(), :hello :hello iex> send self(), :world :world iex> flush() :hello :world :ok
  9. 9. Linked processes: spawn_link iex> spawn_link fn -> raise "something bad happened" end 23:53:50.503 [error] Process #PID<0.93.0> raised an exception ** (RuntimeError) something bad happened :erlang.apply/2 ** (EXIT from #PID<0.91.0>) an exception was raised: ** (RuntimeError) something bad happened :erlang.apply/2
  10. 10. Linked processes & “Failing fast” philosophy Parent process, which is the shell process, has received an EXIT signal from another process causing the parent process to terminate. Often we will link our processes to supervisors which will detect when a process dies and start a new process in its place. In Elixir we are actually fine with letting processes fail because we expect supervisors to properly restart our systems.
  11. 11. Elixir: Task iex> task = Task.async(fn -> 100 * 100 end) %Task{owner: #PID<0.94.0>, pid: #PID<0.96.0>, ref: #Reference<>} iex> res = Task.await(task) 10000
  12. 12. State def start_link do Task.start_link(fn -> loop(%{}) end) end defp loop(map) do receive do {:get, key, caller} -> send caller, Map.get(map, key) loop(map) {:put, key, value} -> loop(Map.put(map, key, value)) end end
  13. 13. State Spawn LoopInit Exit Send Receive
  14. 14. Elixir: Agent iex> {:ok, agent} = Agent.start_link(fn -> %{} end) {:ok, #PID<0.88.0>} iex> Agent.update(agent, &Map.put(&1, :hello, "world")) :ok iex> Agent.get(agent, &Map.get(&1, :hello)) "world"
  15. 15. Behaviours • Many of the processes have similar structures, they follow similar patterns • Behaviours provide a way to define a set of functions that have to be implemented by a module • You can think of behaviours like interfaces in OO languages
  16. 16. Behaviours defmodule GenServer do @callback init(args :: term) :: {:ok, state} ... @callback handle_call(request :: term, from, state :: term) :: {:reply, reply, new_state} ... @callback handle_cast(request :: term, state :: term) :: {:noreply, new_state} ... ...
  17. 17. Implementing behaviours ... # Callbacks def handle_call(:pop, _from, [h | t]) do {:reply, h, t} end def handle_cast({:push, item}, state) do {:noreply, [item | state]} end ...
  18. 18. GenServer • “Generic servers” (processes) that encapsulate state, provide sync and async calls, support code reloading, and more. • The GenServer behaviour abstracts the common client-server interaction. Developers are only required to implement the callbacks and functionality they are interested in. • A GenServer is a process like any other Elixir process and it can be used to keep state, execute code asynchronously and so on.
  19. 19. GenServer example defmodule Stack do use GenServer # Callbacks def handle_call(:pop, _from, [h | t]) do {:reply, h, t} end def handle_cast({:push, item}, state) do {:noreply, [item | state]} end end
  20. 20. GenServer example iex> {:ok, pid} = GenServer.start_link(Stack, [:hello]) iex>, :pop) :hello iex> GenServer.cast(pid, {:push, :world}) :ok iex>, :pop) :world
  21. 21. GenServer cheatsheet Benjamin Tan Wei Hao
  22. 22. Supervision Trees • Supervision trees are a nice way to structure fault-tolerant applications. • Process structuring model based on the idea of workers and supervisors. S W S W S W
  23. 23. Supervision Trees • Workers are processes that perform computations, that is, they do the actual work. • Supervisors are processes that monitor the behaviour of workers. A supervisor can restart a worker if something goes wrong. • The supervision strategy dictates what happens when one of the children crashes.
  24. 24. Supervisor • A behaviour module for implementing supervision functionality. • A supervisor is a process which supervises other processes, which we refer to as child processes.
  25. 25. Supervisor module defmodule MyApp.Supervisor do use Supervisor def start_link do Supervisor.start_link(__MODULE__, []) end def init([]) do children = [ worker(Stack, [[:hello]]) ] supervise(children, strategy: :one_for_one) end end
  26. 26. Supervisor Cheat Sheet Benjamin Tan Wei Hao
  27. 27. Application behaviour • In Erlang/OTP, an application is a component implementing some specific functionality, that can be started and stopped as a unit • Mix is responsible for compiling your source code and generating your application .app file in Elixir. • Mix is also responsible for configuring, starting and stopping your application and its dependencies (mix.exs). • .app holds our application definition
  28. 28. Application callback defmodule MyApp do use Application def start(_type, _args) do MyApp.Supervisor.start_link() end end
  29. 29. Application project mix new hello_world --sup hello_world |-- |-- config | `-- config.exs |-- lib | |-- hello_world | | `-- application.ex | `-- hello_world.ex |-- mix.exs `-- test |-- hello_world_test.exs `-- test_helper.exs
  30. 30. Application project: configuration hello_worldmix.exs def application do # Specify extra applications you'll use from Erlang/Elixir [extra_applications: [:logger], mod: {HelloWorld.Application, []}] end
  31. 31. Application project: callback module hello_worldlibhello_worldapplication.ex defmodule HelloWorld.Application do ... def start(_type, _args) do import Supervisor.Spec, warn: false children = [] opts = [strategy: :one_for_one, name: HelloWorld.Supervisor] Supervisor.start_link(children, opts) end end
  32. 32. Umbrella projects mix new hello_umbrella --umbrella hello_umbrella |-- |-- apps |-- config | `-- config.exs `-- mix.exs
  33. 33. Umbrella projects: configuration ... def project do [apps_path: "apps", build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, deps: deps()] end ...
  34. 34. In umbrella dependencies Mix supports an easy mechanism to make one umbrella child depend on another. ... defp deps do [{:hello_world, in_umbrella: true}] end ...
  35. 35. GenStage & Flow Announcing GenStage GenStage and Flow - Jose Valim | Elixir Club 5
  36. 36. GenStage is a new Elixir behaviour for exchanging events with back-pressure between Elixir processes producer producer consumer producer consumer consumer
  37. 37. Flow: concurrent data processing def process_flow(path_to_file) do path_to_file |>!() |> Flow.from_enumerable() |> Flow.flat_map(&String.split/1) |>, ~r/W/u, "")) |> Flow.filter_map(fn w -> w != "" end, &String.downcase/1) |> Flow.partition() |> Flow.reduce(fn -> %{} end, fn word, map -> Map.update(map, word, 1, &(&1 + 1)) end) |> Enum.into(%{}) end
  38. 38. Flow: concurrent data processing def process_flow(path_to_file) do path_to_file |>!() |> Flow.from_enumerable() |> Flow.flat_map(&String.split/1) |>, ~r/W/u, "")) |> Flow.filter_map(fn w -> w != "" end, &String.downcase/1) |> Flow.partition() |> Flow.reduce(fn -> %{} end, fn word, map -> Map.update(map, word, 1, &(&1 + 1)) end) |> Enum.into(%{}) end P PC PC DemandDispatcher PartitionDispatcher PC PC C CReducers %{} %{}
  39. 39. Flow P PC PC PC PC C C %{} %{} "The Project Gutenberg EBook of The Complete Works of William Shakespeare, byn" "William Shakespearen" "The", "Project", "Gutenberg", "EBook", "of", "The", "Complete", "Works", "of", "William", "Shakespeare,", "by" "William", "Shakespeare" "the", "project", "of", “the", "william", "of", "by ", "william" "gutenberg", "ebook", "complete", "shakespeare", "works", "shakespeare"
  40. 40. Flow Experimental.Flow, Yurii Bodarev at KyivElixirMeetup 3.1
  41. 41. Phoenix Framework • Phoenix is a web development framework written in Elixir which implements the server-side MVC pattern • Phoenix provides the best of both worlds - high developer productivity and high application performance • Phoenix is actually the top layer of a multi-layer system designed to be modular and flexible. The other layers include Plug, and Ecto • The Erlang HTTP server, Cowboy, acts as the foundation for Plug and Phoenix
  42. 42. Phoenix Framework • The Plug • The Endpoint • The Router • Controllers • Actions • Views • Templates • Channels
  43. 43. The Plug • Plug is a specification for constructing composable modules to build web applications. • Plugs are reusable modules or functions built to that specification. • They provide discrete behaviors - like request header parsing or logging. • Because the Plug API is small and consistent, plugs can be defined and executed in a set order, like a pipeline. • Core Phoenix components like Endpoints, Routers, and Controllers are all just Plugs internally
  44. 44. Module Plug example defmodule Example.HelloWorldPlug do import Plug.Conn def init(options), do: options def call(conn, _opts) do conn |> put_resp_content_type("text/plain") |> send_resp(200, "Hello World!") end end %Plug.Conn{…}
  45. 45. Plug pipelines pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers end
  46. 46. Module Plug example ... @locales ["en", "fr", "de"] def init(default), do: default def call(%Plug.Conn{params: %{"locale" => loc}} = conn, _default) when loc in @locales do assign(conn, :locale, loc) end def call(conn, default), do: assign(conn, :locale, default) ...
  47. 47. Adding Plug to the pipeline pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers plug PhoenixApp.Plugs.Locale, "en" end
  48. 48. The Endpoint • provides a wrapper for starting and stopping the endpoint as part of a supervision tree; • handles all aspects of requests up until the point where the router takes over • to define an initial plug pipeline where requests are sent through; • to host web specific configuration for your application. • dispatches requests into a designated router
  49. 49. The Endpoint phoenix_applibphoenix_appwebendpoint.ex defmodule PhoenixApp.Web.Endpoint do use Phoenix.Endpoint, otp_app: :phoenix_app # plug ... # plug ... plug PhoenixApp.Web.Router end
  50. 50. Starting Endpoint phoenix_applibphoenix_appapplication.ex ... children = [ # Start the Ecto repository supervisor(PhoenixApp.Repo, []), # Start the endpoint when the application starts supervisor(PhoenixApp.Web.Endpoint, []) ] opts = [strategy: :one_for_one, name: PhoenixApp.Supervisor] Supervisor.start_link(children, opts) ...
  51. 51. The Router • parses incoming requests and dispatches them to the correct controller/action, passing parameters as needed • provides helpers to generate route paths or urls to resources • defines named pipelines through which we may pass our requests
  52. 52. The Router phoenix_applibphoenix_appwebrouter.ex ... pipeline :browser do plug :accepts, ["html"] ... plug :put_secure_browser_headers end scope "/", PhoenixApp.Web do pipe_through :browser # Use the default browser stack get "/", PageController, :index end ...
  53. 53. Controllers & Actions Controllers provide functions, called actions, to handle requests Actions • prepare data and pass it into views • invoke rendering via views • perform redirects
  54. 54. Controller get "/pages/:id", PageController, :show phoenix_applibphoenix_appwebcontrollerspage_controller.ex defmodule PhoenixApp.Web.PageController do use PhoenixApp.Web, :controller def show(conn, %{"id" => id}) do user = Accounts.get_user(id) render(conn, "show.html", user: user) end end
  55. 55. webweb.ex use PhoenixApp.Web, :controller phoenix_applibphoenix_appwebweb.ex ... def controller do quote do use Phoenix.Controller, namespace: PhoenixApp.Web import Plug.Conn import PhoenixApp.Web.Router.Helpers import PhoenixApp.Web.Gettext end end ...
  56. 56. Views • Defines the view layer of a Phoenix application • Render templates • Define helper functions, available in templates, to decorate data for presentation
  57. 57. Rendering Templates Phoenix assumes a strong naming convention from controllers to views to the templates they render. The PageController requires a PageView to render templates in the webtemplatespage directory. phoenix_applibphoenix_appwebweb.ex ... def view do quote do use Phoenix.View, root: "lib/phoenix_app/web/templates", namespace: PhoenixApp.Web ...
  58. 58. Rendering Templates phoenix_applibphoenix_appwebviewspage_view.ex defmodule PhoenixApp.Web.PageView do use PhoenixApp.Web, :view end Phoenix.View will automatically load all templates at “phoenix_applibphoenix_appwebtemplatespage” and include them in the PhoenixApp.Web.PageView
  59. 59. Rendering JSON def render("index.json", %{pages: pages}) do %{data: render_many(pages, PhoenixApp.PageView, "page.json")} end def render("show.json", %{page: page}) do %{data: render_one(page, PhoenixApp.PageView, "page.json")} end def render("page.json", %{page: page}) do %{title: page.title} end
  60. 60. Templates foo.html.eex • templates are precompiled and fast • template name - is the name of the template as given by the user, without the template engine extension, for example: “foo.html” • template path - is the complete path of the template in the filesystem, for example, “path/to/foo.html.eex” • template root - the directory where templates are defined • template engine (EEx)- a module that receives a template path and transforms its source code into Elixir quoted expressions.
  61. 61. Template examples Hello <%= @name %> <h3>Keys for the conn Struct</h3> <%= for key <- connection_keys @conn do %> <p><%= key %></p> <% end %> function that returns List of keys
  62. 62. Channels • manage sockets for easy real-time communication • are analogous to controllers except that they allow bi-directional communication with persistent connections • Every time you join a channel, you need to choose which particular topic you want to listen to. The topic is just an identifier, but by convention it is often made of two parts: "topic:subtopic".
  63. 63. Channel endpoint phoenix_applibphoenix_appwebendpoint.ex socket "/socket", PhoenixApp.Web.UserSocket phoenix_applibphoenix_appwebchannelsuser_socket.ex channel "room:*", PhoenixApp.Web.RoomChannel Any topic coming into the router with the "room:" prefix would dispatch to MyApp.RoomChannel
  64. 64. Joining Channels defmodule PhoenixApp.Web.RoomChannel do use Phoenix.Channel def join("room:lobby", _message, socket) do {:ok, socket} end def join("room:" <> _private_room_id, _params, _socket) do {:error, %{reason: "unauthorized"}} end end
  65. 65. JS phoenix_appassetsjssocket.js ... socket.connect() // Now that you are connected, you can join channels with a topic: let channel ="topic:subtopic", {}) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) })
  66. 66. JS … channel.push("new_msg", {body: “Hello world!”}) … … channel.on("new_msg", payload => { … }) …
  67. 67. Incoming Events We handle incoming events with handle_in/3. We can pattern match on the event names, like “new_msg” def handle_in("new_msg", %{"body" => body}, socket) do broadcast! socket, "new_msg", %{body: body} {:noreply, socket} end
  68. 68. Outgoing Events: default implementation def handle_out("new_msg", payload, socket) do push socket, "new_msg", payload {:noreply, socket} end
  69. 69. Intercepting Outgoing Events intercept ["smth_important"] def handle_out("smth_important", msg, socket) do if … do {:noreply, socket} else push socket, " smth_important", msg {:noreply, socket} end end
  70. 70. Phoenix Framework 1.3 v1.3.0-rc.1 project generator mix archive.install
  71. 71. Phoenix Framework 1.3 Lonestar ElixirConf 2017- KEYNOTE: Phoenix 1.3 by Chris McCord Phoenix v1.3.0-rc.0 released @ ElixirForum Upcoming book “Programming Phoenix 1.3” @ ElixirForum
  72. 72. 1.2: mix phoenix_old phoenix_old |-- config |-- lib | `-- phoenix_old |-- priv |-- test `-- web |-- channels |-- controllers |-- models |-- static |-- templates `-- views
  73. 73. 1.3: mix phoenix_new phoenix_new |-- assets |-- config |-- lib | `-- phoenix_new | `-- web | |-- channels | |-- controllers | |-- templates | `-- views |-- priv `-- test
  74. 74. 1.3: mix phoenix --umbrella phoenix_umbrella |-- apps | |-- phoenix … | `-- phoenix_web phoenix_umbrellaappsphoenix_webmix.exs ... {:phoenix, in_umbrella: true}, ...
  75. 75. Web Namespace defmodule PhoenixApp.Web.PageController do use PhoenixApp.Web, :controller def index(conn, _params) do render conn, "index.html" end end
  76. 76. Context 1.2: mix phoenix.gen.json User users email:string Repo.get(User, id) 1.3: mix phx.gen.json Accounts User users email:string Accounts.get_user(2) iex> %User{email:} Accounts.create_user(params) iex> {:ok, new_user}
  77. 77. 1.2: Models `-- web |-- channels |-- controllers |-- models | |-- comment.ex | |-- invoice.ex | |-- order.ex | |-- payment.ex | |-- post.ex | `-- user.ex |-- static |-- templates `-- views
  78. 78. 1.3: Context |-- lib | `-- phoenix_new | |-- blog | | |-- blog.ex | | |-- comment.ex | | `-- post.ex | |-- sales | | |-- order.ex | | |-- payment.ex | | `-- sales.ex | `-- web | |-- channels
  79. 79. The boundary for the Sales system. libphoenix_newsalessales.ex defmodule PhoenixNew.Sales do def list_orders do Repo.all(Order) end def get_order!(id), do: Repo.get!(Order, id) def create_order(attrs %{}) do %Order{} |> order_changeset(attrs) |> Repo.insert() end ...
  80. 80. Action Fallback On Phoenix 1.2, every controller needed to return a valid %Plug.Conn{} for every request. Otherwise, an exception would rise. On Phoenix 1.3 we can register the plug to call as a fallback to the controller action. If the controller action fails to return a %Plug.Conn{}, the provided plug will be called and receive the controller’s %Plug.Conn{} as it was before the action was invoked along with the value returned from the controller action.
  81. 81. 1.2: Controller Action phoenix_oldwebcontrollerspost_controller.ex case Repo.insert(changeset) do {:ok, post} -> conn |> put_status(:created) |> put_resp_header("location", post_path(conn, :show, post)) |> render("show.json", post: post) {:error, changeset} -> conn |> put_status(:unprocessable_entity) |> render(PhoenixOld.ChangesetView, "error.json", changeset: changeset) end
  82. 82. 1.3: Controller Action phoenix_newlibphoenix_newwebcontrollerspost_controller.ex def create(conn, %{"post" => post_params}) do with {:ok, %Post{} = post} <- Blog.create_post(post_params) do conn |> put_status(:created) |> put_resp_header("location", post_path(conn, :show, post)) |> render("show.json", post: post) end end
  83. 83. 1.3 Action Fallback phoenix_newlibphoenix_newwebcontrollersfallback_controller.ex def call(conn, {:error, %Ecto.Changeset{} = changeset}) do conn |> put_status(:unprocessable_entity) |> render(PhoenixNew.Web.ChangesetView, "error.json", changeset: changeset) end
  84. 84. Ecto Domain specific language for writing queries and interacting with databases in Elixir.
  85. 85. Ecto Ecto is split into 4 main components: • Ecto.Repo - repositories are wrappers around the data store. • Ecto.Schema - schemas are used to map any data source into an Elixir struct. • Ecto.Changeset - allow developers to filter, cast, and validate changes before we apply them to the data. • Ecto.Query - written in Elixir syntax, queries are used to retrieve information from a given repository.
  86. 86. Repositories Via the repository, we can create, update, destroy and query existing database entries.
  87. 87. Repositories Ecto.Repo is a wrapper around the database. We can define a repository as follows: defmodule Blog.Repo do use Ecto.Repo, otp_app: :blog end
  88. 88. Repositories A repository needs an adapter and credentials to communicate to the database. Configuration for the Repo usually defined in your config/config.exs: config :blog, Blog.Repo, adapter: Ecto.Adapters.Postgres, database: "blog_repo", username: "postgres", password: "postgres", hostname: "localhost"
  89. 89. Repositories Each repository in Ecto defines a start_link/0. Usually this function is invoked as part of your application supervision tree: def start(_type, _args) do import Supervisor.Spec, warn: false children = [ worker(Blog.Repo, []), ] opts = [strategy: :one_for_one, name: Blog.Supervisor] Supervisor.start_link(children, opts) end
  90. 90. Schema Schemas allows developers to define the shape of their data. defmodule Blog.User do use Ecto.Schema schema "users" do field :name, :string field :reputation, :integer, default: 0 has_many :posts, Blog.Post, on_delete: :delete_all timestamps end end
  91. 91. Schema By defining a schema, Ecto automatically defines a struct: iex> user = %Blog.User{name: "Bill"} %Blog.User{__meta__: #Ecto.Schema.Metadata<:built, "users">, id: nil, inserted_at: nil, name: "Bill"}, posts: #Ecto.Association.NotLoaded<association :posts is not loaded>, reputation: 0, updated_at: nil}
  92. 92. Schema Using Schema we can interact with a repository: iex> user = %Blog.User{name: "Bill", reputation: 10} %Blog.User{…} iex> Blog.Repo.insert!(user) %Blog.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 6, inserted_at: ~N[2016-12-13 16:16:35.983000], name: "Bill", posts: #Ecto.Association.NotLoaded<association :posts is not loaded>, reputation: 10, updated_at: ~N[2016-12-13 16:16:36.001000]}
  93. 93. Schema # Get the user back iex> newuser = Blog.Repo.get(Blog.User, 6) iex> 6 # Delete it iex> Blog.Repo.delete(newuser) {:ok, %Blog.User{…, id: 6,…}}
  94. 94. Schema We can use pattern matching on Structs created with Schemas: iex> %{name: name, reputation: reputation} = ...> Blog.Repo.get(Blog.User, 1) iex> name "Alex" iex> reputation 144
  95. 95. Changesets We can add changesets to our schemas to validate changes before we apply them to the data: def changeset(user, params %{}) do user |> cast(params, [:name, :reputation]) |> validate_required([:name, :reputation]) |> validate_inclusion(:reputation, -999..999) end
  96. 96. Changesets iex> alina = %Blog.User{name: "Alina"} iex> correct_changeset = Blog.User.changeset(alina, %{reputation: 55}) #Ecto.Changeset<action: nil, changes: %{reputation: 55}, errors: [], data: #Blog.User<>, valid?: true> iex> invalid_changeset = Blog.User.changeset(alina, %{reputation: 1055}) #Ecto.Changeset<action: nil, changes: %{reputation: 1055}, errors: [reputation: {"is invalid", [validation: :inclusion]}], data: #Blog.User<>, valid?: false>
  97. 97. Changeset with Repository functions iex> valid_changeset.valid? true iex> Blog.Repo.insert(valid_changeset) {:ok, %Blog.User{…, id: 7, …}}
  98. 98. Changeset with Repository functions iex> invalid_changeset.valid? false iex> Blog.Repo.insert(invalid_changeset) {:error, #Ecto.Changeset<action: :insert, changes: %{reputation: 1055}, errors: [reputation: {"is invalid", [validation: :inclusion]}], data: #Blog.User<>, valid?: false>}
  99. 99. Changeset with Repository functions case Blog.Repo.update(changeset) do {:ok, user} -> # user updated {:error, changeset} -> # an error occurred end
  100. 100. We can provide different changeset functions for different use cases def registration_changeset(user, params) do # Changeset on create end def update_changeset(user, params) do # Changeset on update end
  101. 101. Query Ecto allows you to write queries in Elixir and send them to the repository, which translates them to the underlying database.
  102. 102. Query using predefined Schema # Query using predefined Schema query = from u in User, where: u.reputation > 35, select: u # Returns %User{} structs matching the query Repo.all(query) [%Blog.User{…, id: 2, …, name: "Bender", …, reputation: 42, …}, %Blog.User{…, id: 1, …, name: "Alex", …, reputation: 144, …}]
  103. 103. Direct query with “users” table # Directly querying the “users” table query = from u in "users", where: u.reputation > 30, select: %{name:, reputation: u.reputation} # Returns maps as defined in select Repo.all(query) [%{name: "Bender", reputation: 42}, %{name: "Alex", reputation: 144}]
  104. 104. External values in Queries # ^ operator min = 33 query = from u in "users", where: u.reputation > ^min, select: # casting mins = "33" query = from u in "users", where: u.reputation > type(^mins, :integer), select:
  105. 105. External values in Queries If the query is made against Schema than Ecto will automatically cast external value min = "35" Repo.all(from u in User, where: u.reputation > ^min) You can also skip Select to retrieve all fields specified in the Schema
  106. 106. Ecto Multi Ecto.Multi is a data structure for grouping multiple Repo operations in a single database transaction. def reset(account, params) do |> Multi.update(:account, Account.password_reset_changeset(account, params)) |> Multi.insert(:log, Log.password_reset_changeset(account, params)) |> Multi.delete_all(:sessions, Ecto.assoc(account, :sessions)) end Repo.transaction(PasswordManager.reset(account, params))
  107. 107. Ecto Multi case result do {:ok, %{account: account, log: log, sessions: sessions}} -> # We can access results under keys we used # for naming the operations. {:error, failed_operation, failed_value, changes_so_far} -> # One of the operations failed. # We can access the operation's failure value (changeset) # Successful operations would have been rolled back. end
  108. 108. Books Saša Jurić “Elixir in Action” Benjamin Tan Wei Hao “The Little Elixir & OTP Guidebook” New! Lance Halvorsen “Functional Web Development with Elixir, OTP, and Phoenix” Chris McCord “Programming Phoenix” (1.2 -> 1.3) “What's new in Ecto 2.0”
  109. 109. THANK YOU! Yurii Bodarev @bodarev_yurii