Alkemist v1.0.1-rc Alkemist.Controller behaviour

Provides helper macros to use inside of CRUD controllers.

Example with minimal configuration:

defmodule MyAppWeb.MyController do
  use MyAppWeb, :controller

  # Specify the Ecto Schema as resource - it is important to call this above
  # use Alkemist.Controller!
  @resource MyApp.MySchema
  use Alkemist.Controller

  # use if you want to customize the menu
  menu "My Custom Label"

  def index(conn, params) do
    render_index(conn, params)
  end

  def show(conn, %{"id" => id}) do
    render_show(conn, id)
  end

  def edit(conn, %{"id" => id}) do
    render_edit(conn, id)
  end

  def new(conn, _params) do
    render_new(conn)
  end

  def create(conn, %{"my_schema" => params}) do
    do_create(conn, params)
  end

  def update(conn, %{"id" => id, "my_schema" => params}) do
    do_update(conn, id, params)
  end

  def delete(conn, %{"id" => id}) do
    do_delete(conn, id)
  end

  def export(conn, params) do
    csv(conn, params)
  end
end

The following methods can be implemented to set configuration on a global level:

  • repo - needs to return a valid Ecto.Repo
  • preload - return a keyword list of resources to preload in all controller actions
  • collection_actions - a list of actions to list in the collection action menu. They need to be implemented in your controller and a custom route to that function needs to be added to your router.
  • member_actions - a list of actions that is available for each individual resource. They need to be implemented in your controller and a custom router needs to be added to your router

Example for a custom member action:

In your router.ex:

scope "/admin", MyApp, do
  ...
  get "/my_resource/:id/my_func", MyController, :my_func
  alkemist_resources("/my_resource", MyController)
end

In your controller:

def member_actions do
  [:show, :edit, :delete, :my_func]
end

def my_func(conn, %{"id" => id}) do
  # do something with the resource
  conn
  |> put_layout({Alkemist.LayoutView, "app.html"})
  |> render("my_template.html", resource: my_resource)
end

Link to this section Summary

Types

Used to create custom filters in the filter form. Type can be in [:string, :boolean, :select, :date], default is :string. If the type is :select, a collection to build the select must be passed (see Phoenix.HTMl.Form.select/4)

Functions

Creates a csv export of all entries that match the current scope and filter. An export does not paginate

Creates a new resource TODO: document opts

Performs a delete of the current resource. When successful, it will redirect to index

Performs an update to the resource

Simple helper method to use link in callbacks

Loads the resource from the repo and adds any preloads

Customize the Menu item in the sidebar. You can call this function within the controller root after including Alkemist.Controller and setting the @resource

Renders the form for edit and create actions

Renders the default index view table

Renders the default show page

Link to this section Types

Link to this type column()
column() :: atom() | {String.t(), (%{} -> any())}
Link to this type field()
field() ::
  atom() | {atom(), map()} | %{title: string(), fields: [{atom(), map()}]}
Link to this type filter()
filter() :: atom() | keyword()

Used to create custom filters in the filter form. Type can be in [:string, :boolean, :select, :date], default is :string. If the type is :select, a collection to build the select must be passed (see Phoenix.HTMl.Form.select/4)

Link to this type scope()
scope() :: {atom(), keyword(), (%{} -> Ecto.Query.t())}

Link to this section Functions

Link to this function add_opt(opts, controller, key, atts \\ [])
Link to this macro csv(conn, params, opts \\ []) (macro)

Creates a csv export of all entries that match the current scope and filter. An export does not paginate.

For available_options see Alkemist.Controller.render_index/2

Link to this macro do_create(conn, params, opts \\ []) (macro)

Creates a new resource TODO: document opts

Link to this macro do_delete(conn, resource, opts \\ []) (macro)

Performs a delete of the current resource. When successful, it will redirect to index.

Options:

  • delete_func - use a custom method for deletion. Takes the resource as argument.
  • success_callback - custom function on success. Takes the deleted resource as argument
  • error_callback - custom function on error. Takes the resource as argument

Examples:

def delete(conn, %{"id" => id}) do
  opts = [
    delete_func: fn r ->
      MyApp.MyService.deactivate(r)
    end,
    error_callback: fn r ->
      my_custom_error_function(conn, r)
    end
  ]
  do_delete(conn, id, opts)
end
Link to this macro do_update(conn, resource, params, opts \\ []) (macro)

Performs an update to the resource

Options:

  • changeset - use a custom changeset. Example: changeset: :my_update_changeset
  • success_callback - custom function that will be performed on update success. Accepts the new resource as argument
  • error_callback - custom function that will be performed on failure. Takes the changeset as argument.

Examples:

def update(conn, %{"id" => id, "resource" => resource_params}) do
  do_update(conn, id, resource_params, changeset: :my_update_changeset)
end

Or use a custom success or error function:

def update(conn, %{"id" => id, "resource" => resource_params}) do
  opts = [
    changeset: :my_udpate_changeset
    success_callback: fn my_resource ->
      conn
      |> put_flash(:info, "Resource was successfully updated")
      |> redirect(to: my_resource_path(conn, :index))
    end
  ]
  do_update(conn, id, resource_params, opts)
end
Link to this function forbidden(conn)
Link to this function link(label, path, opts \\ [])

Simple helper method to use link in callbacks

Link to this function load_resource(resource, mod, opts)

Loads the resource from the repo and adds any preloads

Link to this function not_found(conn)
Link to this function opts_or_function(opts, mod, keys)
Link to this macro render_edit(conn, resource, opts \\ []) (macro)

Renders the “edit action” See Alkemist.Controller.render_form/3

Link to this macro render_form(conn, action, opts \\ []) (macro)

Renders the form for edit and create actions.

Options

  • preload - Resources to preload. See Ecto.Query
  • repo - Define a custom repo to execute the query
  • form_partial - a tuple in the format {MyViewModule, "template.html"} or {MyViewModule, "template.html", assigns}
  • fields - a list of either atoms representing the resource fields, maps with field groups or a keyword list in the format [field_name: %{type: :type, other opts...}]
  • changeset - use a custom changeset
  • success_callback - use a custom callback function that takes the newly updated/created resource as an argument
  • error_callback - use a custom callback function on error, takes changeset as argument

Examples:

def edit(conn, %{"id" => id}) do
  opts = [
    preload: [:my_relationship]
    form_partial: {MyView, "edit.html"},
    changeset: :my_changeset,
    error_callback: fn(changeset) -> ... end
  ]
  render_edit(conn, id, opts)
end

fields and form_partial also can be defined as custom methods in the controller.

Example with methods:

def edit(conn, %{"id" => id}) do
  render_edit(conn, id)
end

# resource will be nil on create
def fields(_conn, _resource) do
  [
    :title,
    :body
  ]

  # or with custom types:
  # [title: %{type: :string, placeholder: "Enter title"}, body: %{type: :text}]

  # or use some custom form groups
  # [
  #   %{title: "My Model details", fields: [:title, :body]},
  #   %{title: "Next panel header", fields: [:category]}
  # ]
end
Link to this macro render_index(conn, params, opts \\ []) (macro)

Renders the default index view table.

Params:

  • conn - the conn from the controller action
  • params - the params that are passed to the controller action
  • opts - a Keyword.t/0 with options

Options:

  • repo - use a custom Ecto.Repo
  • member_actions - customize the actions that will display for each resource
  • collection_actions - customize the global actions that are available for a collection of resources (e. g. new)
  • batch_actions - add custom batch actions to be performed on a number of selected resources. When set, a selectable_column will be added to the index table and a batch menu will display above the table.
  • columns - List of column/0 customize the columns that will display in the index table
  • scopes - List of scope/0 to define custom filter scopes
  • filters - List of filter/0 to define filters for the search form
  • preload - resources to preload along with each resource (see Ecto.Query)
  • search_provider - define a custom library for building the search query

Example with options:

def index(conn, params) do
  opts = [
    # Columns can either be an atom, or a `tuple` with a label and a custom modifier function
    columns: [
      :id,
      :title,
      :body,
      {"Author", fn i -> i.author.name end}
      ],
    batch_actions: [:delete_batch],
    member_actions: [:show, :edit, :my_custom_action],
    scopes: [
      {:published, [], fn(q) -> where(q, [p], p.published == true) end}
    ],
    preload: [:author]
  ]
  render_index(conn, params, opts)
end

Example with custom functions:

def index(conn, params) do
  render_index(conn, params)
end

def my_custom_action(conn, %{"id" => id}) do
  ...
end

def delete_all(conn, %{"batch_ids" => ids}) do
  # implement custom batch action
end

def columns do
  [
    :id,
    :title,
    :body,
    {"Author", fn i -> i.author.name end}
  ]
end

def scopes do
  [published: [], fn i -> i.published == true end]
end

def preload do
  [:author]
end

def member_actions do
  [:show, :edit, :my_custom_action]
end

def batch_actions do
  [:delete_batch]
end
Link to this macro render_new(conn, opts \\ []) (macro)

Renders the “new” action see Alkemist.Controller.render_form/3

Link to this macro render_show(conn, resource, opts \\ []) (macro)

Renders the default show page.

Options:

  • preload - Resources to preload. See Ecto.Query
  • repo - Define a custom repo to execute the query
  • rows - The values to display. Same syntax as column/0
  • show_panels - define custom panels that are shown underneath the resource table. Each panel consists of a tuple in the form {"Panel Heading", content: my_html_content} Each of the above options can also be specified as controller functions, whereas rows and show_panels take to argumentsconnandresource. Theresource` is the current resource (e. g. a specific user) ## Example with options: elixir def show(conn, %{"id" => id}) do post = Repo.get(Post, id) |> Repo.preload(:author) opts = [ rows: [ :id, :title, :body, {"Author", fn p -> unless p.author != nil do p.author.name end end} ], show_panels: [ {"Author", content: Phoenix.View.render( MyApp.PostView, "author.html", author: post.author )}} ] ] render_show(conn, post, opts) ## Example with custom functions: elixir def show(conn, %{"id" => id}) do render_show(conn, id, preload: [:author]) end def show_panels(_conn, post) do if post.author do [{ "Author", content: Phoenix.View.render( MyApp.PostView, "author.html", author: post.author ) }] else [] end end def rows(_conn, post) do [:id, :title, :body] end

Link to this section Callbacks

Link to this callback batch_actions() (optional)
batch_actions() :: keyword()
Link to this callback columns(arg0) (optional)
columns(Plug.Conn.t()) :: [column()]
Link to this callback csv_columns(arg0) (optional)
csv_columns(Plug.Conn.t()) :: [column()]
Link to this callback fields(arg0, arg1) (optional)
fields(Plug.Conn.t(), struct() | nil) :: [field() | map()]
Link to this callback filters(arg0) (optional)
filters(Plug.Conn.t()) :: keyword()
Link to this callback form_partial(arg0, arg1) (optional)
form_partial(Plug.Conn.t(), struct() | nil) :: tuple()
Link to this callback plural_name() (optional)
plural_name() :: String.t()
Link to this callback preload() (optional)
preload() :: keyword()
Link to this callback repo() (optional)
repo() :: module()
Link to this callback rows(arg0, arg1) (optional)
rows(Plug.Conn.t(), struct() | nil) :: list()
Link to this callback scopes(arg0) (optional)
scopes(Plug.Conn.t()) :: [scope()]
Link to this callback singular_name() (optional)
singular_name() :: String.t()