Unofficial Active Admin Guide

An article about Ruby in the DomClick company blog ! How did it happen that a dead language was brought to a young company ? The secret is that you can quickly write and test a business idea in Ruby. And this is done not without the help of Rails and Active Admin - a library that allows you to quickly create an admin panel with minimal effort and time.





, Active Admin 15- . ( ), .



, Active Admin.



Active Admin , arbre, formtastic, inherited_resources ransack. . β€” , Active Admin.



Arbre:



Active Admin β€” : , action', , , . - Β« single responsibility?Β» . , .



Arbre β€” Ruby. , DSL Arbre:



html do
  head do
    title('Welcome page')
  end
  body do
    para('Hello, world')
  end
end


DSL . , Active Admin tabs, table_for, paginated_collection . Arbre .



Arbre: hello world



Arbre, Admin::Components::HelloWorld Arbre::Component:



# app/admin/components/hello_world.rb
module Admin
  module Components
    class HelloWorld < Arbre::Component
      builder_method :hello_world

      def build(attributes = {})
        super(attributes)
        text_node('Hello world!')
        add_class('hello-world')
      end

      def tag_name
        'h1'
      end
    end
  end
end


: builder_method , DSL. , , #build.



Arbre β€” DOM- ( frontend-, 2012 ). div, , #tag_name. #add_class, , class DOM-.



. , app/admin/dashboard.rb



# app/admin/dashboard.rb
ActiveAdmin.register_page 'Dashboard' do
  menu priority: 1, label: proc { I18n.t('active_admin.dashboard') }

  content do
    hello_world
  end
end




.



Arbre: ()



, , Arbre , , . , (Article) (Comment) 1:M. 10 ( show).



# app/admin/articles.rb
ActiveAdmin.register Article do
  permit_params :title, :body

  show do
    attributes_table(:body, :created_at)

    panel I18n.t('active_admin.articles.new_comments') do
      table_for resource.comments.order(created_at: :desc).first(10) do
        column(:author)
        column(:text)
        column(:created_at)
      end
    end
  end
end




. ActiveAdmin::Views::Panel. ( hello_world ) panel, panel div, .



app/admin/components/articles/new_comments.rb, . , Active Admin , app/admin/**/*:



# app/admin/components/articles/new_comments.rb
module Admin
  module Components
    module Articles
      class NewComments < ActiveAdmin::Views::Panel
        builder_method :articles_new_comments

        def build(article)
          super(I18n.t('active_admin.articles.new_comments'))

          table_for last_comments(article) do
            column(:author)
            column(:text)
            column(:created_at)
          end
        end

        private

        def last_comments(article)
          article.comments
                 .order(created_at: :desc)
                 .first(10)
        end
      end
    end
  end
end


panel app/admin/articles.rb resource:



# app/admin/articles.rb
ActiveAdmin.register Article do
  permit_params :title, :body

  show do
    attributes_table(:body, :created_at)

    articles_new_comments(resource)
  end
end


! , resource , . , resource, , .



, show ( ) partial:



# app/admin/articles.rb
ActiveAdmin.register Article do
  show do
    render('show', article: resource)
  end
end


# app/views/admin/articles/_show.html.arb
panel(ActiveAdmin::Localizers.resource(active_admin_config).t(:details)) do
  attributes_table_for(article, :body, :created_at)
end

articles_new_comments(article)


, .erb , , , .



Arbre:



, Active Admin .



arbre activeadmin, . , gem activeadmin_addons, .



, , , .



Formtastic:



Formtastic β€” DSL. :



semantic_form_for object do |f|
  f.inputs
  f.actions
end


Formtastic object input' -. input' README. Arbre, Formtastic -. , , hello world .



Formtastic: hello world



Arbre, app/admin/inputs:



  # app/admin/inputs/hello_world_input.rb
  class HelloWorldInput
    include Formtastic::Inputs::Base

    def to_html
      "Input for ##{object.public_send(method)}"
    end
  end


input, :as, , :



# app/admin/articles.rb
ActiveAdmin.register Article do
  form do |f|
    f.inputs do
      f.input(:id, as: :hello_world)
      f.input(:title)
      f.input(:body)
    end
    f.actions
  end
end




( object method) #initialize, Formtastic::Inputs::Base. input' #to_html.



, read-only . , Formtastic, hello world read-only input. :



  # app/admin/inputs/hello_world_input.rb
class HelloWorldInput
  include Formtastic::Inputs::Base

  def to_html
    input_wrapping do
      label_html <<
        object.public_send(method).to_s
    end
  end
end




, β€” . input_wrapping Formtastic::Inputs::Base::Wrapping input'. , . label_html Formtastic::Inputs::Base::Labelling input'. hello world input ( ).



, , JS-.



Formtastic: ()



, , HTML, CSS JS. input'.



, : . , JavaScript' , : Countable.js. input , .



, input':



  • input div ;
  • CSS- div;
  • Countable.js div.


Formtastic::Inputs::TextInput. class="countable-input" textarea, div class="countable-content":



# app/admin/inputs/countable_input.rb
class CountableInput < Formtastic::Inputs::TextInput
  def to_html
    input_wrapping do
      label_html <<
        builder.text_area(method, input_html_options.merge(class: 'countable-input')) <<
        template.content_tag(:div, '', class: 'countable-content')
    end
  end
end


, . input_html_optionsβ€” . builder β€” ActiveAdmin::FormBuilder, ActionView::Helpers::FormBuilder. template β€” , ( view-helper'). , , builder. - link_to, template.



Countable.js : app/assets/javascripts/inputs/countable_input .js , Countable.js div.countable-content ( JS-):



// app/assets/javascripts/inputs/countable_input.js
//= require ./countable_input/countable.min.js

const countable_initializer = function () {
  $('.countable-input').each(function (i, e) {
    Countable.on(e, function (counter) {
      $(e).parent().find('.countable-content').html('words: ' + counter['words']);
    });
  });
}

$(countable_initializer);
$(document).on('turbolinks:load', countable_initializer);


app/assets/javascripts/active_admin.js:



// app/assets/javascripts/active_admin.js
// ...

//= require inputs/countable_input


β€” CSS- app/assets/stylesheets/active_admin.scss:



// app/assets/stylesheets/inputs/countable_input.scss
.countable-content {
  float: right;
  font-weight: bold;
}


// app/assets/stylesheets/active_admin.scss
// ...

@import "inputs/countable_input";


, input . :



# app/admin/articles.rb
ActiveAdmin.register Article do
  form do |f|
    f.inputs do
      f.input(:id, as: :hello_world)
      f.input(:title)
      f.input(:body, as: :countable)
    end
    f.actions
  end
end




. , input' . , .



Formtastic:



Arbre, partial', :



# app/admin/articles.rb
ActiveAdmin.register Article do
  form(partial: 'form')
end


# app/views/admin/articles/_form.html.arb
active_admin_form_for resource do
  inputs(:title, :body)
  actions
end


, - views. , , , .



Formtastic:



Formtastic β€” , README, . activeadmin_addons. gem' input', .



, Formtastic Arbre , , Arbre-.



Inherited Resources β€”



. resource, . , gem'.



Inherited Resources β€” , CRUD-.



, , , . :



class ArticlesController < InheritedResources::Base
  respond_to :html
  respond_to :json, only: :index
  actions :index, :new, :create

  def update
    resource.updated_by = current_user
    update! { articles_path }
  end
end


, .respond_to . .respond_to «», . , .clear_respond_to.



.actions CRUD- (index, show, new, edit, create, update destroy).



resource β€” , :



resource        #=> @article
collection      #=> @articles
resource_class  #=> Article


, #update! β€” alias #update, super.



.has_scope. , Article scope :published:



class Article < ApplicationRecord
  scope :published, -> { where(published: true) }
end


.has_scope:



class ArticlesController < InheritedResources::Base
  has_scope :published, type: :boolean
end


.has_scope query-. scope :published, URL /articles?published=true.



README. , , , , Active Admin.



Inherited Resources:



Active Admin InheritedResources::Base, , , .



, action' :



# app/admin/articles.rb
ActiveAdmin.register Article do
  actions :all, :except => [:destroy]
end


, action . , : Active Admin . .



Active Admin HTML, JSON XML ( index CSV). XML- :



# app/admin/articles.rb
ActiveAdmin.register Article do
  clear_respond_to
  respond_to :html, :json
  respond_to :csv, only: :index
end




, undefined method 'clear_respond_to' for #<ActiveAdmin::ResourceDSL>.



, -, ActiveAdmin::ResourceDSL, . , ActiveAdmin::ResourceDSL #actions.



, , - #controller:



# app/admin/articles.rb
ActiveAdmin.register Article do
  controller do
    clear_respond_to
    respond_to :html, :json
    respond_to :csv, only: :index
  end
end


, localhost:3000/admin/articles.xml . action'?



Inherited Resources:



, Article#created_by_admin. #create:



# app/admin/articles.rb
ActiveAdmin.register Article do
  controller do
    def create
      build_resource
      @article.created_by_admin = true
      create!
    end
  end
end


, build_resource β€” , @article. created_by_admin create! ( super), @article.



: . Inherited Resources instance- . , , ( ).



, , XML- . , XML ? ?





! , ActiveAdmin::ResourceController:



# lib/active_admin/remove_xml_rendering_extension.rb
module ActiveAdmin
  module RemoveXmlRenderingExtension
    def self.included(base)
      base.send(:clear_respond_to)
      base.send(:respond_to, :html, :json)
      base.send(:respond_to, :csv, only: :index)
    end
  end
end


.included , . Active Admin ActiveAdmin::ResourceController:



# config/initializers/active_admin.rb
require 'lib/active_admin/remove_xml_rendering_extension'

ActiveAdmin::ResourceController.send(
  :include,
  ActiveAdmin::RemoveXmlRenderingExtension
)
# ...


#include #included, ! .xml.



, , #prepend, #include #extend β€” , , . , .



Inherited Resources:



README. , Active Admin, , .



Ransack:



Active Admin index- , , - . Ransack.



Ransack β€” , SQL-, . , , . .



, (Article) (title). Ransack :



Article.ransack(title_cont: '').result


_cont β€” , Ransack. , SQL- . wiki.



: , , (body). Ransack :



Article.ransack(title_or_body_cont: 'active admin').result


, Ransack , . , (Comment#text):



Article.ransack(comments_text_cont: 'I hate type annotations!').result


, . . Ransack #ransack_alias. alias: comments, :



# app/models/article.rb
class Article < ActiveRecord::Base
  has_many :comments

  ransack_alias :comments, :comments_text_or_comments_author
end

Article.ransack(comments_cont: 'Matz').result


, Ransack , , , , Active Admin.



Ransack:



Active Admin:



# app/admin/articles.rb
ActiveAdmin.register Article do
  preserve_default_filters!
  filter :title_or_body_cont,
         as: :string, 
         label: I18n.t('active_admin.filters.title_or_body_cont')
  filter :comments, 
         as: :string
end




, . #preserve_default_filters!, .



Ransack: scope-



Ransack . , ransackable_attributes, ransackable_associations ransackable_scopes. ( , Active Admin ), ransackable_scopes.



, ransackable_scopes scope'. , scope ( ), .ransackable_scopes.



, scope:



# app/models/article.rb
class Article < ActiveRecord::Base
  has_many :comments

  scope :comments_count_gt, (lambda do |comments_count|
     joins(:comments)
       .group('articles.id')
       .having('count(comments.id) > ?', comments_count)
  end)

  def self.ransackable_scopes(auth_object = nil)
    [:comments_count_gt]
  end
end


auth_object: , . , current_user, Active Admin .



scope .ransackable_scopes, Active Admin:



# app/admin/articles.rb
ActiveAdmin.register Article do
  filter :comments_count_gt, 
         as: :number,
         label: I18n.t('active_admin.filters.comments_count_gt')




: β€” , , :





«» , Ransack. , sanitize_custom_scope_booleans:



# /config/initializers/ransack.rb
Ransack.configure do |config|
  config.sanitize_custom_scope_booleans = false
end


, , 1 , scope'.



Ransack:



, Active Admin . README wiki, , , view- .



, , Ransackers β€” , Arel ( ActiveRecord, SQL-).





, Active Admin , , - . Active Admin frontend- .



Active Admin, , , .



activeadmin_addons, , , Active Admin. , , ( ).




All Articles