Slide 1

Slide 1 text

More fun, less pain: a strategy for writing maintainable Rails admin backends @steffoz - RubyDay 2014

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

@steffoz stefano verna

Slide 4

Slide 4 text

# app/models/contact.rb ! class Contact < ActiveRecord::Base validates :first_name, presence: true validates :last_name, presence: true validates :twitter, presence: true, uniqueness: true ! def full_name [first_name, last_name].join(" ") end end

Slide 5

Slide 5 text

# config/routes.rb ! Rails.application.routes.draw do namespace :admin do resources :contacts, except: :show end end

Slide 6

Slide 6 text

class Admin::ContactsController < ApplicationController respond_to :html ! def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) ! if @contact.save flash[:notice] = "Contact was successfully created" else flash[:alert] = "Contact could not be created" end ! respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) ! if @contact.destroy flash[:notice] = "Contact was successfully destroyed" else flash[:alert] = "Contact could not be destroyed" end ! respond_with @contact, location: admin_contacts_path end ! private ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end

Slide 7

Slide 7 text

class Admin::ContactsController < ApplicationController respond_to :html ! def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) ! if @contact.save flash[:notice] = "Contact was successfully created" else flash[:alert] = "Contact could not be created" end ! respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) ! if @contact.destroy flash[:notice] = "Contact was successfully destroyed" else flash[:alert] = "Contact could not be destroyed" end ! respond_with @contact, location: admin_contacts_path end ! private ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end # app/controllers/admin/contacts_controller.rb class Admin::ContactsController < ApplicationController respond_to :html ! # ... ! end

Slide 8

Slide 8 text

class Admin::ContactsController < ApplicationController respond_to :html ! def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) ! if @contact.save flash[:notice] = "Contact was successfully created" else flash[:alert] = "Contact could not be created" end ! respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) ! if @contact.destroy flash[:notice] = "Contact was successfully destroyed" else flash[:alert] = "Contact could not be destroyed" end ! respond_with @contact, location: admin_contacts_path end ! private ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end # app/controllers/admin/contacts_controller.rb def index @contacts = Contact.all respond_with @contacts end

Slide 9

Slide 9 text

class Admin::ContactsController < ApplicationController respond_to :html ! def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) ! if @contact.save flash[:notice] = "Contact was successfully created" else flash[:alert] = "Contact could not be created" end ! respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) ! if @contact.destroy flash[:notice] = "Contact was successfully destroyed" else flash[:alert] = "Contact could not be destroyed" end ! respond_with @contact, location: admin_contacts_path end ! private ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end # app/controllers/admin/contacts_controller.rb ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) ! if @contact.save flash[:notice] = "Contact was successfully created" else flash[:alert] = "Contact could not be created" end ! respond_with @contact, location: admin_contacts_path end

Slide 10

Slide 10 text

class Admin::ContactsController < ApplicationController respond_to :html ! def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) ! if @contact.save flash[:notice] = "Contact was successfully created" else flash[:alert] = "Contact could not be created" end ! respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) ! if @contact.destroy flash[:notice] = "Contact was successfully destroyed" else flash[:alert] = "Contact could not be destroyed" end ! respond_with @contact, location: admin_contacts_path end ! private ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end # app/controllers/admin/contacts_controller.rb ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end

Slide 11

Slide 11 text

class Admin::ContactsController < ApplicationController respond_to :html ! def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) ! if @contact.save flash[:notice] = "Contact was successfully created" else flash[:alert] = "Contact could not be created" end ! respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) ! if @contact.destroy flash[:notice] = "Contact was successfully destroyed" else flash[:alert] = "Contact could not be destroyed" end ! respond_with @contact, location: admin_contacts_path end ! private ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end # app/controllers/admin/contacts_controller.rb def destroy @contact = Contact.find(params[:id]) ! if @contact.destroy flash[:notice] = "Contact was successfully destroyed" else flash[:alert] = "Contact could not be destroyed" end ! respond_with @contact, location: admin_contacts_path end

Slide 12

Slide 12 text

# app/views/admin/contacts/index.html.erb !

Contacts

! ! Name Twitter Actions ! <% @contacts.each do |contact| %> <%= contact.full_name %> <%= contact.twitter %> <%= link_to "Edit", edit_admin_contact_path(contact) %> <%= link_to "Delete", admin_contact_path(contact), data: { method: :delete } %> <% end %> ! !

<%= link_to "Create new Contact", new_admin_contact_path %>

Slide 13

Slide 13 text

# app/views/admin/contacts/edit.html.erb !

Edit Contact

! <%= form_for(@contact, url: admin_contact_path(@contact)) do |f| %> <%= render "form", form: f %> <%= f.button %> <% end %> # app/views/admin/contacts/new.html.erb !

New Contact

! <%= form_for(@contact, url: admin_contacts_path) do |f| %> <%= render "form", form: f %> <%= f.button %> <% end %>

Slide 14

Slide 14 text

# app/views/admin/contacts/_form.html.erb !
<%= form.label :first_name %> <%= form.text_field :first_name %>

<%= form.object.errors.full_messages_for(:first_name).first %>

!
<%= form.label :last_name %> <%= form.text_field :last_name %>

<%= form.object.errors.full_messages_for(:last_name).first %>

!
<%= form.label :twitter %> <%= form.text_field :twitter %>

<%= form.object.errors.full_messages_for(:twitter).first %>

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

Contact could not be created

Slide 19

Slide 19 text

Contact could not be created

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

5 files, ~100 lines of code

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

gem "activeadmin" https://github.com/activeadmin/activeadmin

Slide 24

Slide 24 text

# app/admin/contact.rb ! ActiveAdmin.register Contact do permit_params :first_name, :last_name, :twitter ! index do column :full_name column :twitter actions end ! form do |f| f.inputs "Details" do f.input :first_name f.input :last_name f.input :twitter end f.actions end end

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

well, no.

Slide 34

Slide 34 text

mixed responsabilities

Slide 35

Slide 35 text

authentication authorization controller routes views css

Slide 36

Slide 36 text

monolithic approach

Slide 37

Slide 37 text

80% - 20%

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

can we make something better?

Slide 40

Slide 40 text

reduce ripetitive code

Slide 41

Slide 41 text

monolithic approach

Slide 42

Slide 42 text

modular testable opt-out

Slide 43

Slide 43 text

authentication authorization routes css ! controller views

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

# app/views/admin/contacts/_form.html.erb !
<%= form.label :first_name %> <%= form.text_field :first_name %>

<%= form.object.errors.full_messages_for(:first_name).first %>

!
<%= form.label :last_name %> <%= form.text_field :last_name %>

<%= form.object.errors.full_messages_for(:last_name).first %>

!
<%= form.label :twitter %> <%= form.text_field :twitter %>

<%= form.object.errors.full_messages_for(:twitter).first %>

Slide 46

Slide 46 text

gem "simple_form" https://github.com/plataformatec/simple_form

Slide 47

Slide 47 text

# app/views/admin/contacts/new.html.erb !

New Contact

! <%= form_for(@contact, url: admin_contacts_path) do |f| %> <%= render "form", form: f, contact: @contact %> <%= f.button %> <% end %>

Slide 48

Slide 48 text

# app/views/admin/contacts/new.html.erb !

New Contact

! <%= simple_form_for(@contact, url: admin_contacts_path) do |f| %> <%= render "form", form: f, contact: @contact %> <%= f.button %> <% end %>

Slide 49

Slide 49 text

# app/views/admin/contacts/_form.html.erb !
<%= form.label :first_name %> <%= form.text_field :first_name %>

<%= form.object.errors.full_messages_for(:first_name).first %>

!
<%= form.label :last_name %> <%= form.text_field :last_name %>

<%= form.object.errors.full_messages_for(:last_name).first %>

!
<%= form.label :twitter %> <%= form.text_field :twitter %>

<%= form.object.errors.full_messages_for(:twitter).first %>

Slide 50

Slide 50 text

# app/views/admin/contacts/_form.html.erb ! <%= form.input :first_name %> <%= form.input :last_name %> <%= form.input :twitter %>

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

# app/controllers/admin/contacts_controller.rb ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end class Admin::ContactsController < ApplicationController respond_to :html ! def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) ! if @contact.save flash[:notice] = "Contact was successfully created" else flash[:alert] = "Contact could not be created" end ! respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) ! if @contact.destroy flash[:notice] = "Contact was successfully destroyed" else flash[:alert] = "Contact could not be destroyed" end ! respond_with @contact, location: admin_contacts_path end ! private ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end

Slide 54

Slide 54 text

gem "responders" https://github.com/plataformatec/responders

Slide 55

Slide 55 text

# app/controllers/admin/contacts_controller.rb class Admin::ContactsController < ApplicationController respond_to :html ! # ... ! end class Admin::ContactsController < ApplicationController respond_to :html ! def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) ! if @contact.save flash[:notice] = "Contact was successfully created" else flash[:alert] = "Contact could not be created" end ! respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) ! if @contact.destroy flash[:notice] = "Contact was successfully destroyed" else flash[:alert] = "Contact could not be destroyed" end ! respond_with @contact, location: admin_contacts_path end ! private ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end

Slide 56

Slide 56 text

class Admin::ContactsController < ApplicationController respond_to :html ! def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) ! if @contact.save flash[:notice] = "Contact was successfully created" else flash[:alert] = "Contact could not be created" end ! respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) ! if @contact.destroy flash[:notice] = "Contact was successfully destroyed" else flash[:alert] = "Contact could not be destroyed" end ! respond_with @contact, location: admin_contacts_path end ! private ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end # app/controllers/admin/contacts_controller.rb class Admin::ContactsController < ApplicationController respond_to :html responders :flash ! # ... ! end

Slide 57

Slide 57 text

# app/controllers/admin/contacts_controller.rb ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end class Admin::ContactsController < ApplicationController respond_to :html ! def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) ! if @contact.save flash[:notice] = "Contact was successfully created" else flash[:alert] = "Contact could not be created" end ! respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) ! if @contact.destroy flash[:notice] = "Contact was successfully destroyed" else flash[:alert] = "Contact could not be destroyed" end ! respond_with @contact, location: admin_contacts_path end ! private ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end

Slide 58

Slide 58 text

# app/controllers/admin/contacts_controller.rb ! def update @contact = Contact.find(params[:id]) @contact.update_attributes(permitted_params) respond_with @contact, location: admin_contacts_path end class Admin::ContactsController < ApplicationController respond_to :html ! def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) ! if @contact.save flash[:notice] = "Contact was successfully created" else flash[:alert] = "Contact could not be created" end ! respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) ! if @contact.destroy flash[:notice] = "Contact was successfully destroyed" else flash[:alert] = "Contact could not be destroyed" end ! respond_with @contact, location: admin_contacts_path end ! private ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end

Slide 59

Slide 59 text

class Admin::ContactsController < ApplicationController respond_to :html ! def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) ! if @contact.save flash[:notice] = "Contact was successfully created" else flash[:alert] = "Contact could not be created" end ! respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) ! if @contact.destroy flash[:notice] = "Contact was successfully destroyed" else flash[:alert] = "Contact could not be destroyed" end ! respond_with @contact, location: admin_contacts_path end ! private ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end # app/controllers/admin/contacts_controller.rb ! def update @contact = Contact.find(params[:id]) @contact.update_attributes(permitted_params) respond_with @contact, location: admin_contacts_path end # config/locales/en.yml ! en: flash: actions: create: notice: '%{resource_name} created.' alert: '%{resource_name} could not be created.' update: notice: '%{resource_name} was updated.' alert: '%{resource_name} could not be updated.' destroy: notice: '%{resource_name} was destroyed.' alert: '%{resource_name} could not be destroyed.'

Slide 60

Slide 60 text

class Admin::ContactsController < ApplicationController respond_to :html ! def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) ! if @contact.save flash[:notice] = "Contact was successfully created" else flash[:alert] = "Contact could not be created" end ! respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) ! if @contact.update_attributes(permitted_params) flash[:notice] = "Contact was successfully updated" else flash[:alert] = "Contact could not be updated" end ! respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) ! if @contact.destroy responders :flash # app/controllers/admin/contacts_controller ! def update @contact = Contact.find(params[:id]) @contact.update_attributes(permitted_para respond_with @contact, location: admin_co end # config/locales/en.yml ! en: flash: actions: create: notice: '%{resource_name} created.' alert: '%{resource_name} could not update: notice: '%{resource_name} was updat alert: '%{resource_name} could not destroy: notice: '%{resource_name} was destr alert: '%{resource_name} could not

Slide 61

Slide 61 text

class Admin::ContactsController < ApplicationController respond_to :html responders :flash ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! private ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) @contact.save respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) @contact.update_attributes(permitted_params) respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) @contact.destroy respond_with @contact, location: admin_contacts_path end # app/controllers/admin/contacts_controller ! def update @contact = Contact.find(params[:id]) @contact.update_attributes(permitted_para respond_with @contact, location: admin_co end # config/locales/en.yml ! en: flash: actions: create: notice: '%{resource_name} created.' alert: '%{resource_name} could not update: notice: '%{resource_name} was updat alert: '%{resource_name} could not destroy: notice: '%{resource_name} was destr alert: '%{resource_name} could not

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

class Admin::ContactsController < ApplicationController respond_to :html responders :flash ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! private ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) @contact.save respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) @contact.update_attributes(permitted_params) respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) @contact.destroy respond_with @contact, location: admin_contacts_path end

Slide 64

Slide 64 text

def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) @contact.save respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) @contact.update_attributes(permitted_params) respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) @contact.destroy respond_with @contact, location: admin_contacts_path end Contact @contacts @contact admin_contacts_path new_admin_contact_path edit_admin_contact_path admin_contact_path

Slide 65

Slide 65 text

Contact @contacts @contact admin_contacts_path new_admin_contact_path edit_admin_contact_path admin_contact_path resource_class ! @collection @resource ! collection_path new_resource_path edit_resource_path resource_path

Slide 66

Slide 66 text

def index @contacts = Contact.all respond_with @contacts end ! def new @contact = Contact.new respond_with @contact end ! def create @contact = Contact.new(permitted_params) @contact.save respond_with @contact, location: admin_contacts_path end ! def edit @contact = Contact.find(params[:id]) respond_with @contact end ! def update @contact = Contact.find(params[:id]) @contact.update_attributes(permitted_params) respond_with @contact, location: admin_contacts_path end ! def destroy @contact = Contact.find(params[:id]) @contact.destroy respond_with @contact, location: admin_contacts_path end resource_class ! @collection @resource ! collection_path new_resource_path edit_resource_path resource_path

Slide 67

Slide 67 text

def index @collection = resource_class.all respond_with @collection end ! def new @resource = resource_class.new respond_with @resource end ! def create @resource = resource_class.new(permitted_params) @resource.save respond_with @resource, location: collection_url end ! def edit @resource = resource_class.find(params[:id]) respond_with @resource end ! def update @resource = resource_class.find(params[:id]) @resource.update_attributes(permitted_params) respond_with @resource, location: collection_url end ! def destroy @resource = resource_class.find(params[:id]) @resource.destroy respond_with @resource, location: collection_url end resource_class ! @collection @resource ! collection_path new_resource_path edit_resource_path resource_path

Slide 68

Slide 68 text

The template method pattern is a design pattern that defines the skeleton of an algorithm in a method, called template method, which defers some steps to subclasses. ! It lets one redefine certain steps of an algorithm without changing the algorithm's structure.

Slide 69

Slide 69 text

def index @collection = resource_class.all respond_with @collection end ! def new @resource = resource_class.new respond_with @resource end ! def create @resource = resource_class.new(permitted_params) @resource.save respond_with @resource, location: collection_url end ! def edit @resource = resource_class.find(params[:id]) respond_with @resource end ! def update @resource = resource_class.find(params[:id]) @resource.update_attributes(permitted_params) respond_with @resource, location: collection_url end ! def destroy @resource = resource_class.find(params[:id]) @resource.destroy respond_with @resource, location: collection_url end resource_class ! @collection @resource ! collection_path new_resource_path edit_resource_path resource_path

Slide 70

Slide 70 text

def @collection respond_with @collection end ! def @resource respond_with @resource end ! def @resource @resource respond_with @resource, end ! def @resource respond_with @resource end ! def @resource @resource respond_with @resource, end ! def @resource @resource respond_with @resource, end # controllers/concerns/rest_actions_concern.rb ! module RestActionsConcern extend ActiveSupport::Concern ! included do respond_to :html responders :flash end ! # actions here! ! end resource_class ! @collection @resource ! collection_path new_resource_path edit_resource_path resource_path

Slide 71

Slide 71 text

# controllers/concerns/rest_actions_concern.rb ! module RestActionsConcern extend ActiveSupport::Concern ! included do respond_to :html responders :flash end ! def index @collection = resource_class.all respond_with @collection end ! def new @resource = resource_class.new respond_with @resource end ! def create @resource = resource_class.new(permitted_params) @resource.save respond_with @resource, location: collection_url end ! def edit @resource = resource_class.find(params[:id]) respond_with @resource end ! def update @resource = resource_class.find(params[:id]) @resource.update_attributes(permitted_params) respond_with @resource, location: collection_url end ! def destroy @resource = resource_class.find(params[:id]) @resource.destroy respond_with @resource, location: collection_url end end

Slide 72

Slide 72 text

class Admin::ContactsController < ApplicationController include RestActionsConcern ! private ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! def resource_class Contact end ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end def collection_path admin_contacts_path end ! def new_resource_path new_admin_contact_path end ! def edit_resource_path(resource) edit_admin_contact_path end ! def resource_path(resource) admin_contact_path(resource) end

Slide 73

Slide 73 text

def collection_path admin_contacts_path end ! def new_resource_path new_admin_contact_path end ! def edit_resource_path(resource) edit_admin_contact_path end ! def resource_path(resource) admin_contact_path(resource) end

Slide 74

Slide 74 text

def collection_path url_for(controller: controller_path, action: :index) end ! def new_resource_path url_for(controller: controller_path, action: :new) end ! def edit_resource_path(resource) url_for(controller: controller_path, action: :edit, id: resource) end ! def resource_path(resource) url_for(controller: controller_path, action: :show, id: resource) end

Slide 75

Slide 75 text

# controllers/concerns/resource_urls_concern.rb ! module ResourceUrlsConcern extend ActiveSupport::Concern ! def collection_path url_for(controller: controller_path, action: :index) end ! def new_resource_path url_for(controller: controller_path, action: :new) end ! def edit_resource_path(resource) url_for(controller: controller_path, action: :edit, id: resource) end ! def resource_path(resource) url_for(controller: controller_path, action: :show, id: resource) end end

Slide 76

Slide 76 text

class Admin::ContactsController < ApplicationController include RestActionsConcern include ResourceUrlsConcern ! private def resource_class Contact end ! def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end

Slide 77

Slide 77 text

class Admin::ContactsController < ApplicationController include RestActionsConcern include ResourceUrlsConcern ! private def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end def resource_class controller_name.classify.constantize # => Contacts end

Slide 78

Slide 78 text

class Admin::ContactsController < ApplicationController include RestActionsConcern include ResourceUrlsConcern ! private def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end

Slide 79

Slide 79 text

class Admin::ContactsController < ApplicationController include RestActionsConcern ! private def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end

Slide 80

Slide 80 text

class Admin::ContactsController < ApplicationController include RestActionsConcern ! private def permitted_params params. require(:contact). permit(:first_name, :last_name, :twitter) end end # app/admin/contact.rb ! ActiveAdmin.register Contact do permit_params :first_name, :last_name, :twitter ! # … end

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

controller views

Slide 83

Slide 83 text

# app/views/admin/contacts/edit.html.erb !

Edit Contact

! <%= simple_form_for(@resource, url: resource_path(@resource)) do |f| %> <%= render "form", form: f %> <%= f.button :submit %> <% end %> # app/views/admin/contacts/new.html.erb !

New Contact

! <%= simple_form_for(@resource, url: collection_path) do |f| %> <%= render "form", form: f %> <%= f.button :submit %> <% end %>

Slide 84

Slide 84 text

resource_class.model_name.human(count: 2) # => "Contatti" I18n.locale = :it

Slide 85

Slide 85 text

module ResourceInflectionsConcern extend ActiveSupport::Concern ! included do helper_method :inflections end ! private ! def inflections { resource_name: , collection_name: } end end resource_class.model_name.human(count: 1) resource_class.model_name.human(count: 2)

Slide 86

Slide 86 text

# app/views/admin/contacts/edit.html.erb !

Edit Contact

! <%= simple_form_for(@resource, url: resource_path(@resource)) do |f| %> <%= render "form", form: f %> <%= f.button :submit %> <% end %> # app/views/admin/contacts/new.html.erb !

New Contact

! <%= simple_form_for(@resource, url: collection_path) do |f| %> <%= render "form", form: f %> <%= f.button :submit %> <% end %>

Slide 87

Slide 87 text

# app/views/admin/contacts/edit.html.erb !

<%= t('admin.resource.edit.title', inflections) %>

! <%= simple_form_for(@resource, url: resource_path(@resource)) do |f| %> <%= render "form", form: f %> <%= f.button :submit %> <% end %> # app/views/admin/contacts/new.html.erb !

<%= t('admin.resource.new.title', inflections) %>

! <%= simple_form_for(@resource, url: collection_path) do |f| %> <%= render "form", form: f %> <%= f.button :submit %> <% end %>

Slide 88

Slide 88 text

view inheritance

Slide 89

Slide 89 text

# app/controllers/admin/contacts_controller.rb class Admin::ContactsController < ApplicationController include RestActionsConcern ! # ... ! end

Slide 90

Slide 90 text

# app/controllers/admin/contacts_controller.rb class Admin::ContactsController < Admin::BaseController include RestActionsConcern ! # ... ! end # app/controllers/admin/base_controller.rb class Admin::BaseController < ApplicationController end

Slide 91

Slide 91 text

contacts app views/admin base new.html.erb edit.html.erb _form.html.erb

Slide 92

Slide 92 text

contacts app base new.html.erb edit.html.erb _form.html.erb views/admin

Slide 93

Slide 93 text

# app/views/admin/contacts/_form.html.erb ! <%= form.input :first_name %> <%= form.input :last_name %> <%= form.input :twitter %>

Slide 94

Slide 94 text

# app/views/admin/contacts/_form.html.erb ! <%= form.input :first_name %> <%= form.input :last_name %> <%= form.input :twitter %> # app/admin/contact.rb ! ActiveAdmin.register Contact do form do |f| f.inputs "Details" do f.input :first_name f.input :last_name f.input :twitter end end end

Slide 95

Slide 95 text

No content

Slide 96

Slide 96 text

# app/views/admin/contacts/index.html.erb !

<%= t("admin.resource.index.title", inflections) %>

Name Twitter Actions <% @collection.each do |contact| %> <%= contact.full_name %> <%= contact.twitter %> <%= link_to "Edit", edit_admin_contact_path(contact) %> <%= link_to "Delete", admin_contact_path(contact), data: { method: :delete } %> <% end %> <% new_label = t('admin.resource.index.actions.new', inflections) %>

<%= link_to(new_label, new_resource_url) %>

Slide 97

Slide 97 text

contacts app base new.html.erb edit.html.erb _form.html.erb views/admin

Slide 98

Slide 98 text

views/admin contacts app base new.html.erb edit.html.erb _form.html.erb _table.html.erb index.html.erb

Slide 99

Slide 99 text

gem "admino" https://github.com/cantierecreativo/admino

Slide 100

Slide 100 text

# app/views/admin/base/index.html.erb !

<%= t('admin.resource.index.title', inflections) %>

! <%= table_for(@collection) do |table, record| %> <%= render 'table', table: table %> ! <%= table.actions do %> <%= table.action :edit, edit_resource_path(record) %> <%= table.action :destroy, resource_path(record), method: :delete %> <% end %> <% end %> ! <% new_label = t('admin.resource.index.actions.new', inflections) %>

<%= link_to(new_label, new_resource_url) %>

Slide 101

Slide 101 text

# app/views/admin/base/index.html.erb !

<%= t('admin.resource.index.title', inflections) %>

! <%= table_for(@collection) do |table, record| %> <%= render 'table', table: table %> ! <%= table.actions do %> <%= table.action :edit, edit_resource_path(record) %> <%= table.action :destroy, resource_path(record), method: :delete %> <% end %> <% end %> ! <% new_label = t('admin.resource.index.actions.new', inflections) %>

<%= link_to(new_label, new_resource_url) %>

Slide 102

Slide 102 text

# app/views/admin/base/index.html.erb !

<%= t('admin.resource.index.title', inflections) %>

! <%= table_for(@collection) do |table, record| %> <%= render 'table', table: table %> ! <%= table.actions do %> <%= table.action :edit, edit_resource_path(record) %> <%= table.action :destroy, resource_path(record), method: :delete %> <% end %> <% end %> ! <% new_label = t('admin.resource.index.actions.new', inflections) %>

<%= link_to(new_label, new_resource_url) %>

Slide 103

Slide 103 text

# app/views/admin/contacts/_table.html.erb ! <%= table.column :full_name %> <%= table.column :twitter %> # app/admin/contact.rb ! ActiveAdmin.register Contact do index do column :full_name column :twitter actions end end

Slide 104

Slide 104 text

# app/controllers/admin/contacts_controller.rb ! class Admin::ContactsController < ApplicationController include RestActionsConcern ! private ! def permitted_params params.require(:contact).permit(:first_name, :last_name, :twitter) end end ! # app/views/admin/contacts/_form.html.erb ! <%= form.input :first_name %> <%= form.input :last_name %> <%= form.input :twitter %> # app/views/admin/contacts/_table.html.erb ! <%= table.column :full_name %> <%= table.column :twitter %>

Slide 105

Slide 105 text

controller views

Slide 106

Slide 106 text

No content

Slide 107

Slide 107 text

No content

Slide 108

Slide 108 text

No content

Slide 109

Slide 109 text

No content

Slide 110

Slide 110 text

No content

Slide 111

Slide 111 text

No content

Slide 112

Slide 112 text

gem "admino" table builder search forms filter groups sortable columns query object

Slide 113

Slide 113 text

https://github.com/cantierecreativo/admino-example

Slide 114

Slide 114 text

recap

Slide 115

Slide 115 text

recap avoid monoliths concerns view inheritance "unix" gems keep it testable trust OOP decomposition single responsibility object composition

Slide 116

Slide 116 text

thanks! @steffoz stefano verna