Awesome ActiveAdmin

Про ActiveAdmin слышали все, но не все понимают насколько он хорош. Есть много мнений от сторонников до ненавистников, но суть в том что если в достаточной степени овладеть этим инструментом, то можно делать практически что угодно за очень сжатые сроки. Многих отпугивает то, что кривая обучения поначалу идёт резко вверх, но как только ты делаешь шаг в сторону, всё становится сложно. Я отношусь к этому также как и к инвестиции, уже около 5 лет я использую исключительно ActiveAdmin и наработал достаточную базу которая позволяет быстро решать большинство типовых задач. И мне есть с чем сравнить.

Админка — это та часть приложения, которой особо некому заниматься, поэтому на моём опыте ей в основном занимаются бекендщики, но был один забавный случай. Для написания админки одного проекта был выделен специально обученный JS-разработчик который пилил её как SPA, соответственно для этого понадобилось добавить в приложение ещё и специальный administrative API, поэтому была занята часть времени backend специалиста. Процесс шёл очень медленно, результат не внушал восхищения. После нескольких месяцев JS-разработчик то ли ушёл на другой проект, то ли совсем слился, получили сырую недоделку и фактически выброшенные на ветер человеко-месяцы времени. После, насколько я помню, админка была таки запилена на ActiveAdmin за пару недель. Может пример и не очень удачный, так как при должном умении и терпении можно было сделать неплохой фреймворк и переиспользовать его в будущем. С другой стороны, если можно исключить лишнее взаимодействие между JS и API и рендерить ответ прямо на сервере, это нужно сделать, это уменьшает сложность на порядок. Не для всего это правило годится, но для слабо нагруженной части приложения которую видит горстка избранных вполне. Keep it simple.

А теперь немного best practices

Используй templates

Например такой шаблон

# app/view/admin/shared/_customers.html.arb

relation = [
  defined?(through) ? through : nil, 
  owner.class.name.underscore
].compact.join('_')

path = [:admin, :customers, q: { "#{relation}_id_eq" => owner.id }]

panel link_to(owner.class.human_attribute_name(:customers), path) do
  scope = owner.customers
    .order(id: :desc)
    .preload(:profile)
    .limit(50)

  table_for scope do
    column :uid
    column :full_name
    column :email
    column :balance
    column :_ do |customer|
      link_to 'Show', [:admin, customer]
    end
  end
end

Можно переиспользовать следующим образом

# app/admin/resource.rb

ActiveAdmin.register SomeResource do
  # ...

  show do |resource|
    attributes_table do
      # ...
    end

    render partial: 'admin/shared/customers', locals: {
      owner: resource
    }
  end
end

Разбивай толстую страницу на независимые части

Если есть страница на которой очень много информации, возможно стоит переделать её, но если не получается можно сделать рендеринг её частей асинхронным через AJAX запросы. К тому же можно периодически обновлять эти кусочки если страница открыта. Для этого я сделал специальный gem activeadmin-async_panel. Использование выглядит примерно так:

# Место где вставляется панель
panel link_to('Status', [:admin, :statuses, 'q[resource_id_eq]' => resource.id]), {
  class: 'async-panel',
  'data-url' => status_admin_resource_path(resource),
  'data-period' => 10.seconds
}

# Контроллер
# app/admin/resource.rb
member_action :status do
  @resource = resource
  render layout: false
end

# View
# app/views/admin/resources/status.html.arb
if status = resource.last_status
  attributes_table_for status do
    row :created_at do
      link_to status.created_at, [:admin, status]
    end
    row(:param1)
    row(:param2)
    row(:param3)
  end
end

Пиши свои расширения

Однажды у нас возникла проблема из-за того что список пользователей которые подгружались в HTML тег select для фильтра на index странице стал настолько большим, что страница стала загружаться очень долго. Решено было сделать фильтр который запрашивает через AJAX пользователей по мере появления символов в поле ввода.

В результате появился gem activeadmin-ajax_filter. Который позволяет использовать AJAX-powered select в фильтрах и формах.

# Relation-resource
ActiveAdmin.register User do
  include ActiveAdmin::AjaxFilter
  # ...
end

# Main resource
# As a filter
ActiveAdmin.register Invoice do
  filter :user, as: :ajax_select, data: { 
    url: :filter_admin_users_path, 
    search_fields: [:email, :customer_uid], 
    limit: 7,
  }
  # ...
end

# As a form input
ActiveAdmin.register Invoice do
  form do |f|
    f.input :language # used by ajax_search_fields
    f.input :user, as: :ajax_select, data: { 
      url: filter_admin_users_path,
      search_fields: [:name], 
      static_ransack: { active_eq: true }, 
      ajax_search_fields: [:language_id],
    }
    # ...
  end
end

В последнее время люди пользуются админками с мобильных телефонов, и это большая проблема для ActiveAdmin, он совершенно не приспособлен для этого. Нужно как-то решать эту проблему, но это уже в другой раз.

Метки: