(Github/Facebook/Twitter) • internationalization/localization/globalization (gettext/translator) • REST API (maru) • master/slave access to database (read_repos) • transaction (Ecto.Multi) • many to many relationship (Ecto 2.0) • pagination (scrivener) • using memcached as session storage (plug_session_memcached) • image file upload to S3 (arc) • send email via SES (mailman) • classification value (ex_enum) • service layer • database concurrent test • metaprogramming
pipeline :browser do ... plug MediaSample.Locale # url͔ΒlocaleΛఆ͢Δplug end # localeΛؚΉscopeΛ࡞͢Δɻ # ͨͩ͠Railsͷ"/(:locale)"ͷΑ͏ͳɺׅހͰғΉΦϓγϣφϧࢦఆॻ͕ࣜଘࡏ͠ͳ͍ͷͰɺ # ʮϩέʔϧ͕ࢦఆ͞Εͳ͚ΕσϑΥϧτͷϩέʔϧ͕ࢦఆ͞Εͨͱղऍ͢ΔʯΈ͍ͨͳ͜ͱग़དྷ·ͤΜɻ # (ৗʹϩέʔϧ͖ͷURLͰΞΫηεͯ͠Β͏ or ϩέʔϧ͕ࢦఆ͞Εͳ͚Εڧ੍తʹϦμΠϨΫτ͢Δඞཁ͕͋Δ) scope "/:locale" do scope "/admin", MediaSample.Admin, as: :admin do ... resources "/users", UserController end scope "/", MediaSample do ... resources "/entries", EntryController end end end
init(opts), do: opts def call(conn, _opts) do supported_locales = MediaSample.Gettext.supported_locales cond do # ରԠ͍ͯ͠Δlocaleͷ߹GettextͷlocaleͱconnͷlocaleΛઃఆ͢Δɻ conn.params["locale"] in supported_locales -> conn |> assign_locale!(conn.params["locale"]) ... end end defp assign_locale!(conn, locale) do Gettext.put_locale(MediaSample.Gettext, locale) conn |> assign(:locale, locale) end end
schema "users" do ... has_one :translation, MediaSample.UserTranslation # ༁ςʔϒϧͱͷϦϨʔγϣϯΛઃఆ end @required_fields ~w(email name status user_type)a @optional_fields ~w(profile)a def changeset(user, params \\ %{}) do ... end # ༁ςʔϒϧͷpreloadઃఆɻuser_idҎ֎ʹlocaleΛࢦఆͯ͠preload͢Δඞཁ͕͋ΔͷͰԼهͷΑ͏ͳهड़͕ඞ ཁ def preload_all(query, locale) do from query, preload: [translation: ^UserTranslation.translation_query(locale)] end end
... end # web.ex defmodule MediaSample.Web do def admin_controller do quote do ... plug :check_logged_in # ϩάΠϯ͍ͯ͠ͳ͍߹ϩάΠϯը໘ʹϦμΠϨΫτͤ͞Δɻ # plugΛΘͳ͍ཧ༝ɺʮplugͰpathϔϧύʔΛ༻Ͱ͖ͳ͍ʯͨΊɻ # (pathϔϧύʔͷఆٛ͞ΕΔMediaSample.Router.HelpersϞδϡʔϧಈతʹੜ͞ΕΔϞδϡʔϧͰ͕͢ɺ # plug͕ίϯύΠϧ͞ΕΔλΠϛϯάͰ·ͩੜ͞Ε͍ͯͳ͍ͷͰࢀরͰ͖ͳ͍Α͏Ͱ͢) def check_logged_in(conn, _params) do session_paths = [ admin_session_path(conn, :new, conn.assigns.locale), admin_session_path(conn, :callback, conn.assigns.locale) ] if !(conn.request_path in session_paths) && !admin_logged_in?(conn) do conn |> redirect(to: admin_session_path(conn, :new, conn.assigns.locale)) |> halt else conn end end end end
"/session" do params do use [:auth] end post "/create" do case UserAuthService.auth_and_validate(params) do {:ok, user} -> # Userใ͔ΒτʔΫϯΛੜ(͜͜ͰSerializer͕ΘΕ·͢) {:ok, jwt, _full_claims} = user |> Guardian.encode_and_sign(:token) conn |> put_status(:created) |> json(%{jwt: jwt}) ... end end end def unauthenticated(conn, _params) do conn |> put_status(:forbidden) |> json(%{error: "Not Authenticated"}) end end
conn.privateʹਖ਼͍͠τʔΫϯ͕ઃఆ͞Ε͍ͯΔ͔Ͳ͏͔Λఆ͠·͢ɻ # ਖ਼͍͠τʔΫϯ͕ઃఆ͞Ε͍ͯͳ͍ɺ͘͠claimsͷΩʔͷ͕Ұக͠ͳ͚Εɺ # handlerʹࢦఆͨ͠Ϟδϡʔϧͷunauthenticated͕ؔݺͼग़͞Ε·͢ɻ plug Guardian.Plug.EnsureAuthenticated, handler: MediaSample.API.V1.Session def check_user_permission!(user), do: ... def check_owner!(entry, user), do: ... resource "/entry" do params do use [:entry] end post "/save" do # conn.privateʹอ࣋͞Ε͍ͯΔUserใΛϩʔυ͠·͢ɻ user = Guardian.Plug.current_resource(conn) check_user_permission!(user) locale = conn.assigns.locale multi = ... case Repo.transaction(multi) do {:ok, %{entry: entry}} -> conn |> json(render(EntryView, "show.json", entry: entry)) ... end end end end
Ecto.Repo, otp_app: :media_sample use Scrivener, page_size: @page_size # ReadReposύοέʔδΛ༻ɻ # ͜ΕʹΑΓʮRepo.slaveʯͱ͍͏ؔΛ༻ͯ͠ɺεϨʔϒRepo͕ # ϥϯμϜʹબ͞ΕΔΑ͏ʹͳΔɻ use ReadRepos, page_size: @page_size end # media_sample.ex defmodule MediaSample do use Application def start(_type, _args) do import Supervisor.Spec, warn: false children = […] # supervisionπϦʔʹεϨʔϒ༻RepoΛՃ children = children ++ Enum.map(MediaSample.Repo.slaves, &supervisor(&1, [])) … end end
do # Repo.slaveؔܦ༝ͰεϨʔϒDBʹϥϯμϜʹΞΫηε page = AdminUser |> from |> Repo.slave.paginate(params) render(conn, "index.html", admin_users: page.entries, page: page) end def create(conn, %{"admin_user" => admin_user_params}) do changeset = AdminUser.changeset(%AdminUser{}, admin_user_params) # Insert/Updateॲཧͷ߹ϚελʔDBΛ༻͢Δ case Repo.insert(changeset) do … end end end
templates/admin/user/form.html.eex # selectλάͷoptionཁૉͱͯ͠༻ग़དྷ·͢ɻ <%= select f, :status, Status.select([:text, :id]), class: "form-control" %> # models/user.ex defmodule MediaSample.User do def changeset(user, params \\ %{}) do user |> cast(params, @required_fields ++ @optional_fields) # όϦσʔγϣϯ༻ʹ۠ͷϦετΛऔಘग़དྷ·͢ɻ |> validate_inclusion(:status, Status.select(:id)) end def valid(query) do # Ͱͳ໊͘લͰ(ԼهͰ`valid`)۠ʹΞΫηεग़དྷ·͢ɻ from u in query, where: u.status == ^Status.valid.id end end