-
-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
improvement: ash_phoenix.gen.html generator (#112)
- Loading branch information
1 parent
8935ad2
commit 18fa992
Showing
8 changed files
with
277 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
defmodule Mix.Tasks.AshPhoenix.Gen.Html do | ||
use Mix.Task | ||
|
||
@shortdoc "Generates a controller and HTML views for an existing Ash resource." | ||
|
||
@moduledoc """ | ||
This task renders .ex and .heex templates and copies them to specified directories. | ||
## Arguments | ||
api The API (e.g. "Shop"). | ||
resource The resource (e.g. "Product"). | ||
plural The plural schema name (e.g. "products"). | ||
## Example | ||
mix ash_phoenix.gen.html Shop Product products | ||
""" | ||
|
||
def run([]) do | ||
Mix.shell().info(""" | ||
#{Mix.Task.shortdoc(__MODULE__)} | ||
#{Mix.Task.moduledoc(__MODULE__)} | ||
""") | ||
end | ||
|
||
def run(args) when length(args) == 3 do | ||
Mix.Task.run("compile") | ||
|
||
[api, resource, plural] = args | ||
singular = String.downcase(resource) | ||
|
||
opts = %{ | ||
api: api, | ||
resource: resource, | ||
singular: singular, | ||
plural: plural | ||
} | ||
|
||
if Code.ensure_loaded?(resource_module(opts)) do | ||
source_path = Application.app_dir(:ash_phoenix, "priv/templates/ash_phoenix.gen.html") | ||
resource_html_dir = Macro.underscore(opts[:resource]) <> "_html" | ||
|
||
template_files(resource_html_dir, opts) | ||
|> generate_files(assigns([:api, :resource, :singular, :plural], opts), source_path) | ||
|
||
print_shell_instructions(opts[:resource], opts[:plural]) | ||
else | ||
Mix.shell().info( | ||
"The resource #{app_name()}.#{opts[:api]}.#{opts[:resource]} does not exist." | ||
) | ||
end | ||
end | ||
|
||
defp assigns(keys, opts) do | ||
binding = Enum.map(keys, fn key -> {key, opts[key]} end) | ||
binding = [{:route_prefix, Macro.underscore(opts[:plural])} | binding] | ||
binding = [{:app_name, app_name()} | binding] | ||
binding = [{:attributes, attributes(opts)} | binding] | ||
Enum.into(binding, %{}) | ||
end | ||
|
||
defp template_files(resource_html_dir, opts) do | ||
app_web_path = "lib/#{Macro.underscore(app_name())}_web" | ||
|
||
%{ | ||
"index.html.heex" => "#{app_web_path}/controllers/#{resource_html_dir}/index.html.heex", | ||
"show.html.heex" => "#{app_web_path}/controllers/#{resource_html_dir}/show.html.heex", | ||
"resource_form.html.heex" => | ||
"#{app_web_path}/controllers/#{resource_html_dir}/#{Macro.underscore(opts[:resource])}_form.html.heex", | ||
"new.html.heex" => "#{app_web_path}/controllers/#{resource_html_dir}/new.html.heex", | ||
"edit.html.heex" => "#{app_web_path}/controllers/#{resource_html_dir}/edit.html.heex", | ||
"controller.ex" => | ||
"#{app_web_path}/controllers/#{Macro.underscore(opts[:resource])}_controller.ex", | ||
"html.ex" => "#{app_web_path}/controllers/#{Macro.underscore(opts[:resource])}_html.ex" | ||
} | ||
end | ||
|
||
defp generate_files(template_files, assigns, source_path) do | ||
Enum.each(template_files, fn {source_file, dest_file} -> | ||
Mix.Generator.create_file( | ||
dest_file, | ||
EEx.eval_file("#{source_path}/#{source_file}", assigns: assigns) | ||
) | ||
end) | ||
end | ||
|
||
defp app_name do | ||
app_name_atom = Mix.Project.config()[:app] | ||
Macro.camelize(Atom.to_string(app_name_atom)) | ||
end | ||
|
||
defp print_shell_instructions(resource, plural) do | ||
Mix.shell().info(""" | ||
Add the resource to your browser scope in lib/#{Macro.underscore(resource)}_web/router.ex: | ||
resources "/#{plural}", #{resource}Controller | ||
""") | ||
end | ||
|
||
defp resource_module(opts) do | ||
Module.concat(["#{app_name()}.#{opts[:api]}.#{opts[:resource]}"]) | ||
end | ||
|
||
defp attributes(opts) do | ||
resource_module(opts) | ||
|> Ash.Resource.Info.attributes() | ||
|> Enum.map(&attribute_map/1) | ||
|> Enum.reject(&reject_attribute?/1) | ||
end | ||
|
||
defp attribute_map(attr) do | ||
%{name: attr.name, type: attr.type, writable?: attr.writable?, private?: attr.private?} | ||
end | ||
|
||
defp reject_attribute?(%{name: :id, type: Ash.Type.UUID}), do: true | ||
defp reject_attribute?(%{private?: true}), do: true | ||
defp reject_attribute?(_), do: false | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
defmodule <%= @app_name %>Web.<%= @resource %>Controller do | ||
use <%= @app_name %>Web, :controller | ||
|
||
alias <%= @app_name %>.<%= @api %>.<%= @resource %> | ||
|
||
def index(conn, _params) do | ||
<%= @plural %> = <%= @resource %>.read!() | ||
render(conn, :index, <%= @plural %>: <%= @plural %>) | ||
end | ||
|
||
def new(conn, _params) do | ||
render(conn, :new, form: create_form()) | ||
end | ||
|
||
def create(conn, %{"<%= @singular %>" => <%= @singular %>_params}) do | ||
<%= @singular %>_params | ||
|> create_form() | ||
|> AshPhoenix.Form.submit() | ||
|> case do | ||
{:ok, <%= @singular %>} -> | ||
conn | ||
|> put_flash(:info, "<%= @resource %> created successfully.") | ||
|> redirect(to: ~p"/<%= @plural %>/#{<%= @singular %>}") | ||
|
||
{:error, form} -> | ||
conn | ||
|> put_flash(:error, "<%= @resource %> could not be created.") | ||
|> render(:new, form: form) | ||
end | ||
end | ||
|
||
def show(conn, %{"id" => id}) do | ||
<%= @singular %> = <%= @resource %>.by_id!(id) | ||
render(conn, :show, <%= @singular %>: <%= @singular %>) | ||
end | ||
|
||
def edit(conn, %{"id" => id}) do | ||
<%= @singular %> = <%= @resource %>.by_id!(id) | ||
|
||
render(conn, :edit, <%= @singular %>: <%= @singular %>, form: update_form(<%= @singular %>)) | ||
end | ||
|
||
def update(conn, %{"<%= @singular %>" => <%= @singular %>_params, "id" => id}) do | ||
<%= @singular %> = <%= @resource %>.by_id!(id) | ||
|
||
<%= @singular %> | ||
|> update_form(<%= @singular %>_params) | ||
|> AshPhoenix.Form.submit() | ||
|> case do | ||
{:ok, <%= @singular %>} -> | ||
conn | ||
|> put_flash(:info, "<%= @resource %> updated successfully.") | ||
|> redirect(to: ~p"/<%= @plural %>/#{<%= @singular %>}") | ||
|
||
{:error, form} -> | ||
conn | ||
|> put_flash(:error, "<%= @resource %> could not be updated.") | ||
|> render(:edit, <%= @singular %>: <%= @singular %>, form: form) | ||
end | ||
end | ||
|
||
def delete(conn, %{"id" => id}) do | ||
<%= @singular %> = <%= @resource %>.by_id!(id) | ||
:ok = <%= @resource %>.destroy(<%= @singular %>) | ||
|
||
conn | ||
|> put_flash(:info, "<%= @resource %> deleted successfully.") | ||
|> redirect(to: ~p"/<%= @plural %>") | ||
end | ||
|
||
defp create_form(params \\ nil) do | ||
AshPhoenix.Form.for_create(<%= @resource %>, :create, as: "<%= @singular %>", api: <%= @app_name %>.<%= @api %>, params: params) | ||
end | ||
|
||
defp update_form(<%= @singular %>, params \\ nil) do | ||
AshPhoenix.Form.for_update(<%= @singular %>, :update, as: "<%= @singular %>", api: <%= @app_name %>.<%= @api %>, params: params) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<.header> | ||
Edit <%= @resource %> <%%= @<%= @singular %>.id %> | ||
<:subtitle>Use this form to manage <%= @singular %> records in your database.</:subtitle> | ||
</.header> | ||
|
||
<.<%= @singular %>_form <%= @singular %>={@<%= @singular %>} form={@form} action={~p"/<%= @plural %>/#{@<%= @singular %>}"} /> | ||
|
||
<.back navigate={~p"/<%= @plural %>"}>Back to <%= @plural %></.back> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
defmodule <%= @app_name %>Web.<%= @resource %>HTML do | ||
use <%= @app_name %>Web, :html | ||
|
||
embed_templates "<%= @singular %>_html/*" | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<.header> | ||
<%= @resource %> Listing | ||
<:actions> | ||
<.link href={~p"/<%= @route_prefix %>/new"}> | ||
<.button>New <%= @resource %></.button> | ||
</.link> | ||
</:actions> | ||
</.header> | ||
|
||
<.table id="<%= @plural %>" rows={@<%= @plural %>} row_click={&JS.navigate(~p"/<%= @route_prefix %>/#{&1}")}> | ||
<%= for attribute <- @attributes do %> | ||
<:col :let={<%= @singular %>} label="<%= attribute.name %>"><%%= <%= @singular %>.<%= attribute.name %> %></:col> | ||
<% end %> | ||
<:action :let={<%= @singular %>}> | ||
<div class="sr-only"> | ||
<.link navigate={~p"/<%= @route_prefix %>/#{<%= @singular %>}"}>Show</.link> | ||
</div> | ||
<.link navigate={~p"/<%= @route_prefix %>/#{<%= @singular %>}/edit"}>Edit</.link> | ||
</:action> | ||
<:action :let={<%= @singular %>}> | ||
<.link href={~p"/<%= @route_prefix %>/#{<%= @singular %>}"} method="delete" data-confirm="Are you sure?"> | ||
Delete | ||
</.link> | ||
</:action> | ||
</.table> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<.header> | ||
New <%= @resource %> | ||
<:subtitle>Use this form to manage <%= @singular %> records in your database.</:subtitle> | ||
</.header> | ||
|
||
<.<%= @singular %>_form form={@form} action={~p"/<%= @plural %>/"} /> | ||
|
||
<.back navigate={~p"/products"}>Back to <%= @plural %></.back> |
15 changes: 15 additions & 0 deletions
15
priv/templates/ash_phoenix.gen.html/resource_form.html.heex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<.simple_form :let={f} for={@form} action={@action}> | ||
<.error :if={@form.submitted_once?}> | ||
Oops, something went wrong! Please check the errors below. | ||
</.error> | ||
<%= for attribute <- @attributes do %> | ||
<%= if attribute.type in [Ash.Type.Integer] do %> | ||
<.input field={f[:<%= attribute.name %>]} type="number" label="<%= attribute.name %>" /> | ||
<% else %> | ||
<.input field={f[:<%= attribute.name %>]} type="text" label="<%= attribute.name %>" /> | ||
<% end %> | ||
<% end %> | ||
<:actions> | ||
<.button>Save Product</.button> | ||
</:actions> | ||
</.simple_form> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<.header> | ||
<%= @resource %> <%%= @<%= @singular %>.id %> | ||
<:subtitle>This is a <%= @singular %> record from your database.</:subtitle> | ||
<:actions> | ||
<.link href={~p"/<%= @plural %>/#{@<%= @singular %>}/edit"}> | ||
<.button>Edit <%= @singular %></.button> | ||
</.link> | ||
</:actions> | ||
</.header> | ||
|
||
<.list> | ||
<%= for attribute <- @attributes do %> | ||
<:item title="<%= attribute.name %>"><%%= @<%= @singular %>.<%= attribute.name %> %></:item> | ||
<% end %> | ||
</.list> | ||
|
||
<.back navigate={~p"/<%= @plural %>"}>Back to <%= @plural %></.back> |