Create reusable form partials in Rails

If you’ve got an admin part in your rails app and some settings to take care of, chances are that you have a lot of similar data. And what can be more wearying, than creating all those forms, index/edit/new views over and over again? Of course you could use the scaffold, but I personally strongly dislike it.

So I figured out I try to make my partials variable as possible. This post shows you how you can create highly reusable form partials.

My structure is as following:

There is an admin panel. Inside admin panel there are settings (which get repetitive) and inside settings you can manage models like „colors“, „patterns“, „fabric“ (you can guess it – it’s a clothing online shop). All of them contain identical data, but have some differences as well.

I’ve got only 3 views – index, edit and new, because there is no sense for having a show view, as it should be the edit view right from the get go. This said, we need only two partials – one, for listing items inside our index view and one for creating and editing existing data.

Let’s start with routes.rb:

namespace :admin do
  	get '', to: 'dashboard#index', as: "/"

  	namespace :settings do
  		resources :borders
  		resources :colors
  		resources :patterns
  		resources :categories
      resources :fabrics

Simple dashboard when you visit plain old „/admin“ and then resources inside another namespace „settings“.

My index view – index.html.haml:

=render partial: 'admin/shared/list_items', locals: {name_ru: 'ткань', current_objects: @fabrics, name_en: "fabric"}

As you can see, I pass 3 local variable to my partial (this is the only thing you need to edit for each model. But hey, it’s only 3 words!) – the russian name, so I can have some customized buttons and links. The current collection and the name in english.

Listing items partial (a.k.a. index)

            %th.text-center Select
            %th Наименование
            %th.text-right Статус
          -unless current_objects.nil?
            -current_objects.each do |object|
                    %input{:name => "mobileos", :type => "checkbox", :value => "FR"}
                  =link_to, send("edit_admin_settings_#{single}_path", object)
                    %button.btn.btn-success.br2.btn-xs.fs12.dropdown-toggle{"aria-expanded" => "false", "data-toggle" => "dropdown", :type => "button"}
                    %ul.dropdown-menu{:role => "menu"}
                        =link_to 'Редактировать', send("edit_admin_settings_#{single}_path", object)
                        =link_to 'Удалить', send("admin_settings_#{single}_path",, method: :delete, data: {confirm: 'Действительно удалить?'}
                =link_to "Новая/ый #{name}", send("new_admin_settings_#{single}_path"), class: 'btn btn-primary'

Well, apart from some html, the important things are:

  • to check if there is at least one item in the collection (remember, we pass the collection under the name „current_objects“)
  • for each object in current_objects we access it using the variable „object“
  • since our partial is variable we can’t use hardcoded paths, like „admin_settings_colors_path“. Meaning we must make use of the „send“ method, which basically makes a method from a string. To pass parameters to a method created by „send“ you simply put this parameter as a parameter to the send-method. send(„hello_there“, caramba) is equivalent to calling method hello_there(caramba)
  • Because „object“ is actually an object (wow!) we can’t use it in the path generation. This is why I pass „single“ as a local variable. „Single“ is simply the singular form of the model (category, color, pattern etc.)

My edit/new view:

They are absolutely identical.

  =render 'admin/navigation/flash'
  =render partial: 'admin/shared/form', locals: {current_object: @pattern}

I render some flash messages to tell users about successful or failed actions. Then I render my second partial using again a local variable to transmit the collection.

Form partial

The core 🙂

=form_for [:admin, :settings, current_objects] do |f|
      %span.panel-title.hidden-xs Добавить новую сущность
          %a{"data-toggle" => "tab", :href => "#tab1_1"} Описание сущности
          -if current_objects.respond_to?(:picture)

              .fileupload.fileupload-new.admin-form{"data-provides" => "fileupload"}
                  -unless current_objects.picture.nil?
                    =image_tag current_objects.picture
                    %img{:alt => "holder", "data-src" => "holder.js/100%x140"}
          {:name => "name2", :placeholder => "Img Keywords", :type => "text"}
                      %label.field-icon{:for => "name2"}
                      %span.fileupload-new Select
                      %span.fileupload-exists Change
                      =f.file_field :picture, id: 'fileupload', type: :file
                      / %input{:type => "file", name: 'picture'}
              %label.field.prepend-icon{:for => "name2"}
                =f.text_field :name, id: "price", class: "event-name gui-input br-light light", placeholder: 'Название сущности'
                %label.field-icon{:for => "name2"}
            -if current_objects.respond_to?(:price)
                  =f.number_field :price, id: "price", class: "event-name gui-input br-light light", placeholder: 'Стоимость'
                  %label.field-icon{:for => "comment"}
                    %strong> Hint:
                    Don't be negative or off topic! just be awesome...
            -if current_objects.respond_to?(:hex)
                  =f.text_field :hex, id: "hex", class: "event-name gui-input br-light light", placeholder: 'Hex-значение цвета'
                  %label.field-icon{:for => "comment"}

        / end section row section
                %input{:checked => "", :name => "info", :type => "checkbox"}
                  Save Customer
                  %em.small-text.text-muted - A Random Unique ID will be generated
                =f.submit "Сохранить", class: "btn btn-primary"
          / end section
  -if current_objects.respond_to?(:image)
        %span.panel-title Загруженные фотографии рецепта
 фото можно удалить
        =f.fields_for :pictures do |builder|
          =image_tag builder.object.image.mini_thumb.url, class: "img-responsive thumbnail mr25 uzhin_doma_mini_thumb"
          =builder.label :_destroy, 'Удалить фотографию?'
          =builder.check_box :_destroy

Well, again a lot of HTML, but I will summarize the core points for you:

  • First of all we build our form using triple nesting =form_for [:admin, :settings, current_objects] do |f|
  • Now comes the most interesting part – surely your models will differ a bit. Maybe on has images to upload, the other doesn’t. One has a price tag, other don’t. Etc. and so on. Thus, we need to create an all-purpose-form, disabling fields when not required. This is done by respond_to? method. You can see it in this line  -if current_objects.respond_to?(:picture) This basically means, that if there is no :picture method for current object, the block below won’t be executed. Nifty, eh?
  • In my example every model has a field, called „name“, this is why I don’t check for it. All other fields are encapsulated in a respond_to? check.


With just a few methods – send and respond_to? you can create highly reusable partials where your maintain effort goes towards zero. Only one file for each action (index and edit/new) – truly amazing!