Es una gema que utilizamos como back office.
La utilizamos porque resuelve rápidamente los típicos CRUD de admin. Con ActiveAdmin en unos minutos puedes tener para un recurso (modelo ActiveRecord): el menu para acceder, las vistas para crear/editar, el listado paginado con filtros y varias cosas más.
La gema viene instalada si el proyecto se generó usando Potassium. Si no es así, igual puedes agregarlo luego ejecutando potassium install admin
.
Supongamos que tenemos el modelo Blog
con los atributos title
, body
y user
(owner del Blog). Si agregamos bajo /app/admin/blogs.rb
el siguiente código:
ActiveAdmin.register Blog do
end
Al acceder a http://localhost:3000/admin/blogs
veremos el listado de blogs:
y si hacemos clic en el link "Editar" de alguno de los blogs veremos el formulario:
Listo! Eso es todo lo que necesitas hacer para tener algo funcionando. De todos modos, en un proyecto Platanus comúnmente verás algo como esto:
ActiveAdmin.register Blog do
permit_params :title, :body, :user_id
filter :title
index do
selectable_column
id_column
column :title
column :user
actions
end
show do
attributes_table do
row :title
row :body
row :user
end
end
form do |f|
f.semantic_errors
f.inputs do
f.input :title
f.input :body
f.input :user
end
f.actions
end
end
para tener más control de lo que se quiere mostrar. Por ejemplo, así se ve el index con la configuración custom:
Active Admin toma la configuración de locales de Rails para traducir los nombres de las columnas.
Por ejemplo, para traducir los atributos de Blog
deberías tener la siguiente configuración:
/config/locales/es-CL.yml
es-CL:
activerecord:
attributes:
blog:
title: Título
body: Texto
user: Dueño
De esta manera al entrar por ejemplo la vista de un Blog
verás los atributos traducidos:
Ten en cuenta que también funciona con métodos (getters) custom. Por ejemplo, podrías tener:
/app/models/blog.rb
class Blog < ApplicationRecord
def my_method
"Hola!"
end
end
/config/locales/es-CL.yml
es-CL:
activerecord:
attributes:
blog:
my_method: Mi método
/app/admin/blogs.rb
ActiveAdmin.register Blog do
index do
selectable_column
id_column
column :my_method
actions
end
show do
attributes_table do
row :my_method
end
end
end
y funcionará.
Agrupar recursos
Para agrupar varios items dentro de un mismo menú, debes:
-
Definir el menú en el initializer de active admin:
/config/initializers/active_admin.rb
ActiveAdmin.setup do |config| config.namespace :admin do |admin| admin.build_menu do |menu| menu.add id: :some_menu_id, label: "Grupo" end end end
-
Definir en el recurso su menú padre:
/app/admin/blogs.rb
ActiveAdmin.register Blog do menu parent: :some_menu_id end
Ocultar menú
Se hace de la siguiente manera:
/app/admin/blogs.rb
ActiveAdmin.register Blog do
menu false
end
A simple vista parece no tener mucha utilidad pero lo importante aquí es que aunque no exista el menú, igual existen los endpoints. Algo que puede ser conveniente si queremos usar alguna ruta del admin como una API.
Menú condicional
Puede ser útil mostrar u ocultar un menú dependiendo de una condición. El caso más típico es el de roles. Por ejemplo:
/app/admin/blogs.rb
ActiveAdmin.register Blog do
menu if: -> { current_admin_user.super_admin? }
end
En Platanus usamos Active Admin con el adapter de Pundit para autorizar recursos. Si al registrar un nuevo recurso en AA, no tienes creado el policy de ese recurso, observarás un error así:
Si esto ocurre, agrega el policy correspondiente y define los permisos para cada una de las acciones del CRUD:
class BlogPolicy < ApplicationPolicy
def index?
true
end
def show?
true
end
def create?
true
end
def new?
create?
end
def update?
true
end
def edit?
update?
end
def destroy?
true
end
end
Si todavía no estás en la instancia del proyecto en la cual tienes que preocuparte por permisos deja todas las acciones en true.
Es importante mencionar que si una acción no tiene permisos, esta desaparecerá del menú y links, etc. Por ejemplo:
class BlogPolicy < ApplicationPolicy
def show?
false
end
end
Al tratar de acceder a http://localhost:3000/admin/blogs/303
Se puede ver además como el link a "Ver" desapareció:
Los action_item
s son botones que se pueden agregar a las vistas del recurso. Por ejemplo, el siguiente código mostrará un botón (link), solo en la vista index
, para ir al listado de administradores.
action_item :go_to_admins, only: [:index] do
link_to("Ver Administradores", admin_admin_users_path)
end
Ten en cuenta que puedes decidir en qué vistas aparecerá el botón usando la opción only.
Las member_action
s son acciones extra que se pueden agregar al controller del recurso. Por ejemplo, el siguiente código:
member_action :send_mail, method: :post do
# Ejecuta algún código. Por ejemplo enviar el blog por mail a n usuarios.
redirect_to admin_blogs_path
end
sumará el endpoint /admin/blogs/:id/send_mail
a los endpoints del CRUD.
Ten en cuenta que se puede utilizar action_item
s para ejecutar estas nuevas acciones. Por ejemplo, el siguiente código agregará un botón en la vista del blog (show
) desde el que se llamará a la acción send_mail
.
action_item :send_mail, only: [:show] do
link_to("Enviar a Admin Users", send_mail_admin_blog_path(resource), method: :post)
end
Una alternativa a los action_item
s es agregar la acción al listado del index así:
index do
# ...
actions do |blog|
link_to("Enviar", send_mail_admin_blog_path(blog), method: :post)
end
end
Es lo mismo que una member_action
pero sin apuntar a un recurso en particular sino a la colección. Por ejemplo, la siguiente member_action
:
member_action :send_mail, method: :post do
# ...
end
creará el siguiente endpoint: POST /admin/blogs/:id/send_mail
. En cambio, esta collection_action
:
collection_action :send_mails, method: :post do
# ...
end
generará este: POST /admin/blogs/send_mails
La idea entonces con esto es que las member_action
s se usen junto a action_item
s en show
, new
y update
y las collection_action
con action_item
s en el index
.
Ten en cuenta que dentro de una member_action podrás acceder al recurso actual (en nuestro ejemplo un Blog de id x) usando el método resource. En cambio, en una collection_action, podrás acceder al listado de recursos (lista de Blogs en el ejemplo), que está mostrando el index en ese momento, usando el método collection.
Si prestaste atención a la imagen del formulario de la sección "Uso básico", seguro notaste que el selector de usuarios no muestra correctamente el nombre de los mismos:
esto se debe a que Active Admin espera que los recursos tengan definido el método: :display_name
para que puedan ser representados como "String". Si el recurso no lo implementa, simplemente llamará a to_s
mostrando como resultado lo que vemos en el selector. Entonces, para solucionar esto, podemos hacer lo siguiente:
class User < ApplicationRecord
def display_name
email
end
end
Ten en cuenta que display_name (o cualquiera de las otras opciones) se utilizará en varios lugares: en el título de un recurso, en los links de las rows/columns y, como vimos, en los selectores.
Hay veces que necesitamos agregar nuevos endpoints con HTML a medida. Para hacer esto hacer lo siguiente:
-
Agregar la
member_action
:member_action :preview, method: :get do @blog = resource end
-
Agregar el
action_item
:action_item :preview, only: [:show] do link_to("Previsualizar", preview_admin_blog_path(resource)) end
-
Agregar la vista custom:
Agregamos el HTML para nuestra nueva
member_action
en/app/views/admin/blogs/preview.html.erb
<h1><%= @blog.title %></h1> <p><%= @blog.body %></p>
Ten en cuenta que puedes agregar el archivo con extensión
.arb
en vez de.erb
y usar la gema Arbre que es el DSL que Active Admin utiliza para dibujar sus vistas. El siguiente código:/app/views/admin/blogs/preview.html.arb
h1 { resource.title } para { resource.body }
sería equivalente a lo de
/app/views/admin/blogs/preview.html.erb
en Arbre.
Para manejo de archivos se supone el uso de Shrine
Suponiendo que el blog tiene un archivo image
:
-
Agregar la
member_action
:member_action :download, method: :get do blog = Blog.find(params[:id]) send_file blog.image.download end
-
Agregar el
action_item
:action_item :download, only: :show, if: -> { blog.image.present? } do link_to 'Download', download_admin_blog_path(blog) end
-
Agregar link a las
actions
en el indexindex do column :id . . . actions defaults: true do |blog| link_to 'Download Image', download_admin_blog_path(blog) end end
Si necesitas agregar alguna pequeña inteligencia en una vista de Active Admin revisa nuestra guía sobre AlpineJS.
Es una gema construida en Platanus sobre Active Admin y la utilizamos para facilitar algunas features comunes. Por ejemplo: inputs con select2, selector de booleanos en index/show, selector de colores, etc.
Muchas veces en un formulario de Active Admin necesitamos un input custom. Por ejemplo: selectores de moneda, de colores, con búsqueda ajax, etc. Por esto, Active Admin ofrece una forma sencilla de agregar estos controles. Para mostrarte cómo funciona te mostraré en pasos como agregar un input que escribe con console.info
lo que se escribió en input en el evento focus out.
-
Agregar el archivo con mi input en
/app/inputs/logger_input.rb
.Ten en cuenta que el nombre del archivo debe tener la forma: [nombre_control]_input y lo mismo para el nombre de la clase, pero en CamelCase.
class LoggerInput < ActiveAdminAddons::InputBase def render_custom_input concat(label_html) concat(builder.text_field(method, input_html_options)) end end
-
Agregar el js con la lógica del focus out en
/app/assets/javascripts/admin/inputs/logger_input.js
$(document).ready(function(){ $('.logger-input').each(function(i, el) { $(el).on( "focusout", function(event) { console.info(event.currentTarget.value); }); }); });
-
Incluir el js en
/app/assets/javascripts/active_admin.js
://= require active_admin/base //= require admin/inputs/logger_input
-
Usar en el form:
ActiveAdmin.register Blog do form do |f| f.inputs do f.input :title, as: :logger end f.actions end end
Para manejar la búsqueda de los filtros ActiveAdmin por debajo usa Ransack. Esta gema usa distintos sufijos para indicar distintos tipos de búsqueda. Además, nos permite hacer búsquedas con respecto a atributos de asociaciones.
Por ejemplo, si queremos buscar blogs por nombre de usuario:
filter :user_name_cont
Aquí _cont
indica que se entregarán los resultados que contengan el valor dado. Podríamos haber usado _eq
si quisieramos un match perfecto, por ejemplo.
Muchas veces lo que se puede hacer con AA es un poco riguroso en cuanto a las necesidades del cliente, por lo que pueden haber situaciones en las cuales necesitemos incorporar Vue.js en AA. A continuación se explica una guía paso a paso de cómo agregar este en AA.
Para esto tenemos 2 opciones:
-
Hacer una vista custom como se mencionó anteriormente y ahí usar Vue.
-
Usar directamente un componente en la vista de admin o en un vista html.arb.
-
Se crea el componente vue que queremos utilizar, por ejemplo
massive-edit.vue
. -
Luego debemos registrar el componente globalmente. Acá necesitamos usar un archivo diferente al
application.js
que utilizamos normalmente, para este caso usaremos un archivo llamadoadmin_application.js
que se debe encontrar en la misma carpeta que elapplication.js
, si no se encuentra debe crearlo, la convención para registrar los componentes que se usan en admin es desnake_case
, siguiendo con el ejemplo anterior el archivo se vería así:import Vue from 'vue/dist/vue.esm'; import MassiveEdit from '../views/products/massive-edit.vue'; Vue.component('massive_edit', MassiveEdit); document.addEventListener('DOMContentLoaded', () => { if (document.getElementById('wrapper') !== null) { return new Vue({ el: '#wrapper', }); } return null; });
-
Si queremos utilizar filtros, i18n o tailwind en los archivos Vue que utilizaremos en AA, debemos importarlos en este archivo también, tal como lo hacemos normalmente. Cabe destacar que si tenemos algo importado en
application.js
no va a funcionar en los componentes que se utilicen en AA, si no que debemos importarlos nuevamente enadmin_application.js
, acá un ejemplo del archivo completo:import { camelizeKeys } from 'humps'; // filtro camelize import Vue from 'vue/dist/vue.esm'; import MassiveEdit from '../views/products/massive-edit.vue'; import i18n from '../plugins/i18n'; // i18n import '../css/application.css'; // tailwind Vue.component('massive_edit', MassiveEdit); Vue.filter('camelizeKeys', camelizeKeys); document.addEventListener('DOMContentLoaded', () => { if (document.getElementById('wrapper') !== null) { return new Vue({ el: '#wrapper', i18n, }); } return null; });
-
Luego podemos ir a cualquier vista dentro de
app/views/admin
y usar el componente como lo hacemos normalmente:<massive_edit :props="@props.to_json"> </massive_edit>
-
Hacer los mismos pasos anteriores.
-
Instalar la clase
vue_component
desde nuestra gema potassium. Esto agrega un par de inicializadores a nuestra app. Se debe escribirpotassium install
y escogervue_component
. -
Luego en el archivo
initializers/active_admin.rb
debemos importarvue_componment
y hacer build del componente registrado anteriormente:require "vue_component.rb" AUTO_BUILD_ELEMENTS = %i{ massive_edit } component_creator(AUTO_BUILD_ELEMENTS)
La función component_creator recibe los nombres de los componentes y los convierte a html que puede procesar AA mediante la gema Arbre. Lo anterior se debe poner fuera del
ActiveAdmin.setup
. -
Utilizar el componente en la vista, puede ser de las siguientes formas:
a. html.arb:
massive_edit(prop1: prop1, prop2: prop2)
b. admin
index do massive_edit(prop1: prop1, prop2: prop2) end