Backoffice Demo
A simple demo of how to use Mandarin to implement a simple backoffice area for your Phoenix web application.
Introduction
Mandarin is a set of generators inspired by the Phoenix CRUD generators
which can help in writing admin interface for you web application.
The generators are inspired by the default phoenix.gen.*
generators,
and follow almost exactly the same structures.
These generators generate normal phoenix conrollers, views and templates, which you can customize to your liking. The generated code may be quite verbose, but because it's normal elixir code, it's also trivial to customize to your liking (it would be pretty impossible to customize if it were based on metaprogramming).
This approach is different from the one found in admin frameworks from other languages, which use class reflection or metaprogramming, such as some Python frameworks (Django, Flask-Admin, Flask-AppBuilder) and Ruby frameworks (ActiveAdmin).
This application we'll develop in this guide is inspired by the small example in the Flask-Appbuider docs. The main difference is that we'll write ir using the Mandarin generators, while the AppBuilder version will uses class reflection.
Application Layout
We'll generate an admin interface that manages 3 different kinds of resources:
- Employees
- Departments, where each employee belongs to a single department
- Functions, where each employee has a single function
This structure will show how Mandarin generators handle one-to-many relations (a future version will show how they handle many-to-many relations).
Add the Dependencies
# mix.exs
defp deps do
[
# ...
# Mandarin
{:mandarin, path: "../mandarin"},
{:forage, path: "../forage", override: true},
# Utilities to generate fake data
{:faker, "~> 0.16", only: :dev}
]
end
"Install" Mandarin Into Your Application
You can have as many admin interfaces in your application as you want. An admin interface is just a new context with schemas, views, controllers and templates generated by Mandarin.
You "install" mandarin into your application by giving it a new context name. Mandarin will then generate everything under that context.
mix mandarin.install Backoffice
Add the Resources to the Mandarin Context
Mandarin provides generators, which are similar to the default Phoenix generators. The goal is to build your CRUD interface with the generators, and maybe customize the interface later if you feel the need to. Mandarin will generate and application structure which is quite similar to the default phoenix structure. The main difference is the use of "vertical slicing" or "feature folders".
You can now generate the pages for our resources with the generators.
Department
Type the following (and answer Yes to the prompts as needed):
mix mandarin.gen.html Backoffice Department departments \
name:string description:text --binary-id
Function
mix mandarin.gen.html Backoffice Function functions name:string --binary-id
Employee
mix mandarin.gen.html Backoffice Employee employees \
full_name:string address:string fiscal_number:string \
department:references:departments function:references:functions \
begin_date:date end_date:date --binary-id
Testing
Mandarin has generated some tests for your context and controllers. The tests are pretty basic, but they at least test that the relevant pages load without errors and invoke the appropriate actions in your Repo.
You can run the automatically generated tests with:
mix test
Generate Some Fake Data
Alias the context module, which we will use to create our resources:
alias MandarinDemo.Backoffice
Create some WH40k-inspired departments for our company:
department_names = [
"Inquisition",
"Oficia Censorum",
"Ordo Xenos",
"Ordo Malleus",
"Sororitas",
"Adeptus Astartes"
]
# Gather the departments after inserting them into the database
# because we'll be using them later.
departments =
for name <- department_names do
{:ok, department} =
Backoffice.create_department(%{
name: name,
# Generate a random description using Faker
description: Faker.Lorem.paragraph(1..2)
})
department
end
A above, create some functions for our employees:
function_names = [
"Janitor",
"Office Clerk",
"Lector",
"Servitor",
"Adeptus",
"Maestrus",
"Techpriest",
"Cook",
"Custodes"
]
# Gather the functions after inserting them into the database
# because we'll be using them later.
functions =
for name <- function_names do
{:ok, function} = Backoffice.create_function(%{name: name})
function
end
Add some employees belonging to random functions and departments:
_employees =
for i <- 1..500 do
{:ok, employee} =
Backoffice.create_employee(%{
address: Faker.Address.En.street_address(),
begin_date: Faker.Date.backward(_days = 365 * 100),
end_date: Faker.Date.forward(_days = 365 * 100),
fiscal_number: "SSN-#{Enum.random(0..1000_000_000)}",
full_name: Faker.Person.En.name(),
function_id: Enum.random(functions).id,
department_id: Enum.random(departments).id
})
end
Playing with The Application
...