Current section

Files

Jump to
sugar lib sugar controller helpers.ex
Raw

lib/sugar/controller/helpers.ex

defmodule Sugar.Controller.Helpers do
@moduledoc """
All controller actions should have an arrity of 2, with the
first argument being a `Plug.Conn` representing the current
connection and the second argument being a `Keyword` list
of any parameters captured in the route path.
Sugar bundles these response helpers to assist in sending a
response:
* `render/4` - `conn`, `template_key`, `assigns`, `opts` - sends a normal
response.
* `halt!/2` - `conn`, `opts` - ends the response.
* `not_found/1` - `conn`, `message` - sends a 404 (Not found) response.
* `json/2` - `conn`, `data` - sends a normal response with
`data` encoded as JSON.
* `raw/1` - `conn` - sends response as-is. It is expected
that status codes, headers, body, etc have been set by
the controller action.
* `static/2` - `conn`, `file` - reads and renders a single static file.
#### Example
defmodule Hello do
use Sugar.Controller
def index(conn, []) do
render conn, "showing index controller"
end
def show(conn, args) do
render conn, "showing page \#{args[:id]}"
end
def create(conn, []) do
render conn, "page created"
end
def get_json(conn, []) do
json conn, [message: "foobar"]
end
end
"""
@type status_code :: 100..999
@type headers :: [{binary, binary}]
import Plug.Conn
@doc """
sets connection status
## Arguments
* `conn` - `Plug.Conn`
* `status_code` - `Integer`
## Returns
`Plug.Conn`
"""
@spec status(Plug.Conn.t, status_code) :: Plug.Conn.t
def status(conn, status_code) do
%Plug.Conn{conn | status: status_code, state: :set}
end
@doc """
sets response headers
## Arguments
* `conn` - `Plug.Conn`
* `status_code` - `List`
## Returns
`Plug.Conn`
"""
@spec headers(Plug.Conn.t, headers) :: Plug.Conn.t
def headers(conn, headers) do
%Plug.Conn{conn | resp_headers: headers, state: :set}
end
@doc """
reads and renders a single static file.
## Arguments
* `conn` - `Plug.Conn`
* `file` - `String`
## Returns
`Plug.Conn`
"""
@spec static(Plug.Conn.t, binary) :: Plug.Conn.t
def static(conn, file) do
filename = Path.join(["priv/static", file])
if File.exists? filename do
body = filename |> File.read!
conn
|> put_resp_content_type_if_not_sent("text/html")
|> send_resp_if_not_sent(200, body)
else
conn
|> not_found
end
end
@doc """
Sends a normal response with `data` encoded as JSON.
## Arguments
* `conn` - `Plug.Conn`
* `data` - `Keyword|List`
## Returns
`Plug.Conn`
"""
@spec json(Plug.Conn.t, Keyword.t | list, Keyword.t) :: Plug.Conn.t
def json(conn, data, opts) do
opts = [status: 200] |> Keyword.merge opts
conn
|> put_resp_content_type_if_not_sent("application/json")
|> send_resp_if_not_sent(opts[:status], Poison.encode! data)
end
@spec json(Plug.Conn.t, Keyword.t | list) :: Plug.Conn.t
def json(conn, data) do
status = conn.status || 200
if get_resp_header(conn, "content-type") == [] do
conn = put_resp_content_type_if_not_sent(conn, "application/json")
end
conn |> send_resp_if_not_sent(status, Poison.encode! data)
end
@doc """
Sends response as-is. It is expected that status codes,
headers, body, etc have been set by the controller
action.
## Arguments
* `conn` - `Plug.Conn`
## Returns
`Plug.Conn`
"""
@spec raw(Plug.Conn.t) :: Plug.Conn.t
def raw(conn) do
conn |> send_resp
end
@doc """
Sends a normal response.
Automatically renders a template based on the
current controller and action names when no
template is passed.
## Arguments
* `conn` - `Plug.Conn`
* `template_key` - `String`
* `assigns` - `Keyword`
* `opts` - `Keyword`
## Returns
`Plug.Conn`
"""
@spec render(Plug.Conn.t, binary | Keyword.t | nil, Keyword.t, Keyword.t) :: Plug.Conn.t
def render(conn, template \\ nil, assigns \\ [], opts \\ [])
def render(conn, template, assigns, opts) when is_atom(template)
or is_binary(template) do
template = build_template_key(conn, template)
render_view(conn, template, assigns, opts)
end
def render(conn, assigns, opts, _) when is_list(assigns) do
template = build_template_key(conn)
render_view(conn, template, assigns, opts)
end
defp render_view(conn, template_key, assigns, opts) do
opts = [status: 200] |> Keyword.merge opts
html = Sugar.Config.get(:sugar, :views_dir, "lib/#{Mix.Project.config[:app]}/views")
|> Sugar.Views.Finder.one(template_key)
|> Sugar.Templates.render(assigns)
conn
|> put_resp_content_type_if_not_sent(opts[:content_type] || "text/html")
|> send_resp_if_not_sent(opts[:status], html)
end
@doc """
Ends the response.
## Arguments
* `conn` - `Plug.Conn`
* `opts` - `Keyword`
## Returns
`Plug.Conn`
"""
@spec halt!(Plug.Conn.t, Keyword.t) :: Plug.Conn.t
def halt!(conn, opts \\ []) do
opts = [status: 401, message: ""] |> Keyword.merge opts
conn
|> send_resp_if_not_sent(opts[:status], opts[:message])
end
@doc """
Sends a 404 (Not found) response.
## Arguments
* `conn` - `Plug.Conn`
## Returns
`Plug.Conn`
"""
@spec not_found(Plug.Conn.t, binary) :: Plug.Conn.t
def not_found(conn, message \\ "Not Found") do
conn
|> send_resp_if_not_sent(404, message)
end
@doc """
Forwards the response to another controller action.
## Arguments
* `conn` - `Plug.Conn`
* `controller` - `Atom`
* `action` - `Atom`
* `args` - `Keyword`
## Returns
`Plug.Conn`
"""
@spec forward(Plug.Conn.t, atom, atom, Keyword.t) :: Plug.Conn.t
def forward(conn, controller, action, args \\ []) do
apply controller, action, [conn, args]
end
@doc """
Redirects the response.
## Arguments
* `conn` - `Plug.Conn`
* `location` - `String`
* `opts` - `Keyword`
## Returns
`Plug.Conn`
"""
@spec redirect(Plug.Conn.t, binary, Keyword.t) :: Plug.Conn.t
def redirect(conn, location, opts \\ []) do
opts = [status: 302] |> Keyword.merge opts
conn
|> put_resp_header_if_not_sent("Location", location)
|> send_resp_if_not_sent(opts[:status], "")
end
defp build_template_key(conn, template \\ nil) do
default = Map.get(conn.private, :action) || :index
template = template || default
controller = "#{Map.get(conn.private, :controller, "")}"
|> String.split(".")
|> List.last
|> String.downcase
"#{controller}/#{template}"
end
defp put_resp_header_if_not_sent(%Plug.Conn{state: :sent} = conn, _, _) do
conn
end
defp put_resp_header_if_not_sent(conn, key, value) do
conn |> put_resp_header(key, value)
end
defp put_resp_content_type_if_not_sent(%Plug.Conn{state: :sent} = conn, _) do
conn
end
defp put_resp_content_type_if_not_sent(conn, resp_content_type) do
conn |> put_resp_content_type(resp_content_type)
end
defp send_resp_if_not_sent(%Plug.Conn{state: :sent} = conn, _, _) do
conn
end
defp send_resp_if_not_sent(conn, status, body) do
conn |> send_resp(status, body)
end
end