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:
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. , , ( ).