A puggle (it’ll make sense later)
They’re not a new idea, even in Elixir:
Contexts are dedicated modules that expose and group related functionality. For example, anytime you call Elixir’s standard library, be it
Logger.info/1
orStream.map/2
, you are accessing different contexts. Internally, Elixir’s logger is made of multiple modules, such asLogger.Config
andLogger.Backends
, but we never interact with those modules directly. We call theLogger
module the context, exactly because it exposes and groups all of the logging functionality.
—https://hexdocs.pm/phoenix/contexts.html
They’re not required unless you use the generators:
# Phoenix 1.2mix phx.gen.html CreditCard credit_cards number:string# Phoenix 1.3 👇mix phx.gen.html Payments CreditCard credit_cards number:string
(But you should use them anyway!)
They’re not code or magic, just modules and functions.
# mix phx.gen.context Payments CreditCard credit_cards number:stringdefmodule Puggle.Payments do @moduledoc """ The Payments context. """ @doc """ Returns the list of credit_cards. ## Examples iex> Payments.list_credit_cards() [%CreditCard{}, ...] """ def list_credit_cards do Repo.all(CreditCard) end
An interface layer:
# Phoenix 1.2: Controller -> Repodef show(conn, %{"id" => id}) do user = Repo.get!(User, id) render(conn, "show.html", user: user)end
# Phoenix 1.3: Controller -> Context -> Repodef show(conn, %{"id" => id}) do user = Accounts.get_user!(id) render(conn, "show.html", user: user)end# Context moduledefmodule Accounts to def get_user!(id), do: Repo.get!(User, id)end
# Phoenix 1.2defmodule PuggleWeb.UserController do def create(conn, %{"user" => user_params}) do changeset = User.changeset(%User{}, user_params) {:ok, user} = Repo.insert(changeset) Puggle.Email.welcome_email(user.email) |> Mailer.deliver_now Intercom.add_user(user) conn |> put_flash(:info, "User created successfully.") |> redirect(to: user_path(conn, :index)) enddefmodule PuggleWeb.API.V1.UserController do def create(conn, %{"user" => user_params}) do changeset = User.changeset(%User{}, user_params) {:ok, user} = Repo.insert(changeset) Puggle.Email.welcome_email(user.email) |> Mailer.deliver_now Intercom.add_user(user) render conn, "show.json", user: user end
# Phoenix 1.3defmodule PuggleWeb.UserController do def create(conn, %{"user" => user_params}) do {:ok, _user} = Accounts.create_user(user_params) conn |> put_flash(:info, "User created successfully.") |> redirect(to: user_path(conn, :index)) enddefmodule PuggleWeb.API.V1.UserController do def create(conn, %{"user" => user_params}) do {:ok, user} = Accounts.create_user(user_params) render conn, "show.json", user: user enddefmodule Puggle.Accounts do def create_user(user_params) do changeset = User.changeset(%User{}, user_params) {:ok, user} = Repo.insert(changeset) Puggle.Email.welcome_email(user.email) |> Mailer.deliver_now Intercom.add_user(user) {:ok, user} end
iex> Accounts.create_user(%{name: "Steve"})iex> # vs.iex> Repo.insert(User.changeset(%User{}, %{name: "Steve"}))
# Phoenix 1.2web/ models/ company.ex # Ecto schema # ... user.ex # Ecto schemalib/ sync_account_service.ex
# Phoenix 1.3lib/ puggle/ accounts/ accounts.ex # The context company.ex # Ecto schema user.ex # Ecto schema sync_account_service.ex # other related files
Phoenix 1.2:
lib/ # Where most Elixir apps put their codeweb/ # Where Phoenix 1.2 puts your code
Phoenix 1.3:
lib/ my_app/ # your business logic my_app_web/ # HTTP stuff (e.g. controllers)
“Phoenix is not your application” —Lance Halvorsen
Phoenix 1.2:
web/ # Phoenix stuff models/ # Oh, hey, Business Logic 👋 controllers/ # HTTP views/ # HTTP endpoint.ex # HTTP
Phoenix 1.3:
lib/ my_app/ # Business logic my_app_web/ # Phoenix/HTTP stuff
Orders.ship_order(order_id)# vs.%ShipmentRequest{order_id: order_id}|> ShipmentRequest.changeset()|> Repo.insert()
# Payments Contextpuggle/ lib/ puggle/ payments/
# Payments umbrella apppuggle/ apps/ core/ payments/
# separate Payments servicepuggle/puggle_payments/ # own project! lib/ puggle/ puggle_web/
# Controller testtest "lists all users", %{conn: conn} do conn = get conn, user_path(conn, :index) assert html_response(conn, 200) =~ "Listing Users"end# Context testtest "list_users/0 returns all users" do user = user_fixture() assert Accounts.list_users() == [user]end
Possibility for redundant coverage, though.
defmodule Puggle.Owners.Dog do schema "dogs" do belongs_to :owner, Puggle.Owners.Owner field :name, :string field :good_dog, :boolean # Admin-only info timestamps() endendowner|> Ecto.assoc(:dogs)|> Repo.all|> Poison.encode!# {name: "Fido", good_dog: false} 😡# Whoops, sorry!
defmodule Puggle.Owners.Dog do schema "dogs" do belongs_to :owner, Puggle.Owners.Owner field :name, :string timestamps() endenddefmodule Puggle.Admin.Owners.Dog do schema "dogs" do belongs_to :owner, Puggle.Owners.Owner field :name, :string field :good_dog, :boolean timestamps() endend
mix phx.gen.context Ummmm
A little more thinking up front (But it’s easy to change your mind!)
If you’re stuck when trying to come up with a context name when the grouped functionality in your system isn’t yet clear, you can simply use the plural form of the resource you’re creating...As you grow your application and the parts of your system become clear, you can simply rename the context to a more refined name at a later time.
— the docs
Examples:
# Contexts + HTML stuff like Controllers and templatesmix phx.gen.html Accounts User users name:string email:string# Context + API stuff like Controller with JSON outputmix phx.gen.json Blog Post posts title:string body:text# Just a context, no web stuffmix phx.gen.context Blog Author authors name:string
"If you are unsure, you should prefer explicit modules (contexts) between resources."
— the docs
A puggle (it’ll make sense later)
Keyboard shortcuts
↑, ←, Pg Up, k | Go to previous slide |
↓, →, Pg Dn, Space, j | Go to next slide |
Home | Go to first slide |
End | Go to last slide |
Number + Return | Go to specific slide |
b / m / f | Toggle blackout / mirrored / fullscreen mode |
c | Clone slideshow |
p | Toggle presenter mode |
t | Restart the presentation timer |
?, h | Toggle this help |
Esc | Back to slideshow |