General

Es una gema que utilizamos como back office.

¿Por qué la usamos?

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.

¿Cómo la usamos?

Instalación

La gema viene instalada si el proyecto se generó usando Potassium. Si no es así, igual puedes agregarlo luego ejecutando potassium install admin.

Uso básico

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:

I18n

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:

  1. 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
  2. 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

Permisos (Pundit)

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ó:

Action Items

Los action_items 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.

Member Action

Las member_actions 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_items 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_items 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

Collection Action

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_actions se usen junto a action_items en show, new y update y las collection_action con action_items 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.

Display Name

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.

Vistas custom

Hay veces que necesitamos agregar nuevos endpoints con HTML a medida. Para hacer esto hacer lo siguiente:

  1. Agregar la member_action:

    member_action :preview, method: :get do
      @blog = resource
    end
  2. Agregar el action_item:

    action_item :preview, only: [:show] do
      link_to("Previsualizar", preview_admin_blog_path(resource))
    end
  3. 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:

  1. Agregar la member_action:

    member_action :download, method: :get do
      blog = Blog.find(params[:id])
      send_file blog.image.download
    end
  2. Agregar el action_item:

    action_item :download, only: :show, if: -> { blog.image.present? } do
      link_to 'Download', download_admin_blog_path(blog)
    end
  3. Agregar link a las actions en el index

    index do
      column :id
      .
      .
      .
      actions defaults: true do |blog|
        link_to 'Download Image', download_admin_blog_path(blog)
      end
    end

JavaScript en una vista

Si necesitas agregar alguna pequeña inteligencia en una vista de Active Admin revisa nuestra guía sobre AlpineJS.

Active Admin Addons

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.

Inputs custom

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.

  1. 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
  2. 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);
        });
      });
    });
  3. Incluir el js en /app/assets/javascripts/active_admin.js:

    //= require active_admin/base
    //= require admin/inputs/logger_input
  4. Usar en el form:

    ActiveAdmin.register Blog do
      form do |f|
        f.inputs do
          f.input :title, as: :logger
        end
    
        f.actions
      end
    end

Filtros custom

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.

Utilizando Vue

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:

  1. Hacer una vista custom como se mencionó anteriormente y ahí usar Vue.

  2. Usar directamente un componente en la vista de admin o en un vista html.arb.

Primer caso.

  1. Se crea el componente vue que queremos utilizar, por ejemplo massive-edit.vue.

  2. Luego debemos registrar el componente globalmente. Acá necesitamos usar un archivo diferente al application.js que utilizamos normalmente, para este caso usaremos un archivo llamado admin_application.js que se debe encontrar en la misma carpeta que el application.js, si no se encuentra debe crearlo, la convención para registrar los componentes que se usan en admin es de snake_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;
    });
  3. 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 en admin_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;
    });
  4. 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>

Segundo caso.

  1. Hacer los mismos pasos anteriores.

  2. Instalar la clase vue_component desde nuestra gema potassium. Esto agrega un par de inicializadores a nuestra app. Se debe escribir potassium install y escoger vue_component.

  3. Luego en el archivo initializers/active_admin.rb debemos importar vue_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.

  4. 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

Recursos útiles

Última actualización