Search code examples
ruby-on-railsruby-on-rails-3formsformtastic

Is there a better approach for this custom Formtastic input in Rails?


I've been using Formtastic for awhile now, and it's great for speeding up implementation of forms. However, I have a special case where I need some more customization in what's displayed in my form. Specifically, the field is a file upload form for uploading images, and on the edit form, I want to show a thumbnail of the current version of the image that has been uploaded.

Desired Form Output

I've got this working, but it required that I use custom HTML markup, which means that any time Formtastic changes the output format, I need to update my matching HTML. Here's what I've got right now:

<%= form.inputs do %>
    <% if form.object.new_record? -%>
        <%= form.input :image, :required => true, :hint => 'Maximum size of 3MB. JPG, GIF, PNG.' %>
    <% else -%>
        <li class="file input required" id="profile_image_input">
            <label class="label" for="profile_image">Image</label>
            <%= image_tag form.object.image.url(:thumb), :class => 'attachment' %>
            <%= form.file_field :image %>
            <p class="inline-hints">Maximum size of 3MB. JPG, GIF, PNG.</p>
        </li>
    <% end -%>
<% end %>

Ideally, it would be nice to do something more like the following, where input_html is assumed to be the generated HTML for the input, hint, etc.:

<%= form.inputs do %>
    <%= form.input :image, :required => true, :hint => 'Maximum size of 3MB. JPG, GIF, PNG.' do |input_html| %>
        <%= image_tag form.object.image.url(:thumb), :class => 'attachment' unless form.object.new_record? %>
        <%= input_html %>
    <% end %>
<% end %>

Does anything like this already exist? Or is there another similar option that will make my life easier?


Solution

  • Well, I solved this myself of course. As always happens when I post here. :P

    For anyone looking to do something similar, I created a custom input type derived from Formtastic's file input.

    class AttachmentInput < Formtastic::Inputs::FileInput
      def image_html_options
        {:class => 'attachment'}.merge(options[:image_html] || {})
      end
    
      def to_html
        input_wrapping do
          label_html <<
          image_html <<
          builder.file_field(method, input_html_options)
        end
      end
    
    protected
    
      def image_html
        return "".html_safe if builder.object.new_record?
    
        url = case options[:image]
        when Symbol
          builder.object.send(options[:image])
        when Proc
          options[:image].call(builder.object)
        else
          options[:image].to_s
        end
    
        builder.template.image_tag(url, image_html_options).html_safe
      end
    end
    

    Now I can just create an input of this type in the following manner:

    <%= form.input :image, :as => :attachment,
                           :required => true,
                           :hint => 'Maximum size of 3MB. JPG, GIF, PNG.',
                           :image => proc { |o| o.image.url(:thumb) } %>
    

    Optionally, the :image tag can accept one of:

    • a Proc, which passes the form's object param,
    • a Symbol, which is a method name on the object,
    • anything else, which is converted to a string and assumed to represent the URL.

    Additionally, I can utilize the :image_html option for specifying HTML classes, id's, etc.