SimpleForm without for (non model form)

Ruby on-RailsRuby on-Rails-3Simple Form

Ruby on-Rails Problem Overview


Is it possible to use Simple Form (by: Plataformatec) without a model?

https://github.com/plataformatec/simple_form

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

You can use :symbol as the first argument.

<%= simple_form_for :user, url: users_path do |f| %>
  <%= f.input :name, as: :string %>
  ...
<% end %>

It will output something like this:

<form novalidate="novalidate" class="simple_form user" action="/users" accept-charset="UTF-8" method="post">
  ...
  <div class="input string required user_name">
    <label class="string required" for="user_name">
      <abbr title="required">*</abbr> Name
    </label>
    <input class="string required" type="text" name="user[name]" id="user_name" />
  </div>
  ...
</form>

Solution 2 - Ruby on-Rails

Unfortunately simple_form relies on using a model. Essentially it would be nice to have something like simple_form_tag and input_tag methods equivalent to their rails *_tag helpers. Until then, there's an easy work around.

Use a symbol instead of the class in the form and pass the value explicitly to prevent simple_form from trying to access the model properties.

<%= simple_form_for :user, :url => '/users' do |f| %>
  <%= f.text_field :name, input_html: { value: nil } %>
<% end %>

This will avoid the undefined method 'name' for User error.

Solution 3 - Ruby on-Rails

You can also use fields outside the model within a form model, with simple_fields_for like this:

<%= simple_form_for @user do |f| %>
  <%= f.input :name %>

  <%= simple_fields_for :no_model_fields do |n| %>
    <%= n.input :other_field %>
  <% end %>
<% end %>

This is simple and practical solution, because you can create different kind of fields from different models or without using models

Solution 4 - Ruby on-Rails

You could also pass a :symbol instead of @object as argument for simple_form_for.

<%= simple_form_for :email, :url => '/post_email' do |f| %>
  <%= f.input :subject, :as => :string %>
<% end %>

Which would output:

<form method="post" class="simple_form email" action="/post_email" accept-charset="UTF-8">
  ...
  <input type="text" size="30" name="email[subject]" id="email_subject">
</form>

Please be aware of following draw-backs:

  • You won't be able to take advantage of automatic model validation
  • Need to explicitly define :url and the type of each input

Solution 5 - Ruby on-Rails

All of the methods above still leave you with form data nested inside of "user" or whatever symbol that you pass as the first argument. That's annoying.

To mimic simple_form's style/benefits, but remove the object/symbol dependency and the forced data nesting, you can create a partial.

HAML examples:

form view:

= form_tag("path/to/action", method: "POST") do
    = render "path/to/partial/field", type: "string", required: true, item: "first_name"

field partial:

- required_string = required ? "required" : ""
%div{class: "input #{type} #{required_string} #{item}"}
  %label{class: "#{type} #{required_string}", for: "#{item}"}
    - if required
      %abbr{title: "required"}
        *
    = t("application.#{item}")
  %input{name: "#{item}",                                                     |
    placeholder: t("application.#{item}"),                                    |
    type: "#{type}",                                                          |
    required: required,                                                       |
    "aria-required" => "#{required}" }

Solution 6 - Ruby on-Rails

In the case that you want to add a field that is not part of your model in your form, so that you don't want to read attributes, simple_form propose to use what they call a fake input in their wiki.

String Input

app/inputs/fake_input.rb:

class FakeInput < SimpleForm::Inputs::StringInput
  # This method only create a basic input without reading any value from object
  def input(wrapper_options = nil)
    merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
    template.text_field_tag(attribute_name, nil, merged_input_options)
  end
end

Then you can do <%= f.input :thing, as: :fake %>

Boolean Input

app/inputs/fake_checkbox_input.rb:

class FakeCheckboxInput < SimpleForm::Inputs::StringInput
  # This method only create a basic input without reading any value from object
  def input(wrapper_options = nil)
    merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
    tag_name = "#{@builder.object_name}[#{attribute_name}]"
    template.check_box_tag(tag_name, options['value'] || 1, options['checked'], merged_input_options)
  end
end

Then you can do <%= form.input :remove_avatar, as: :fake_checkbox, wrapper: :vertical_boolean %>

Select

app/inputs/fake_select_input.rb:

class FakeSelectInput < SimpleForm::Inputs::CollectionSelectInput
  def input(wrapper_options = nil)
    label_method, value_method = detect_collection_methods

    merged_input_options = merge_wrapper_options(input_html_options, wrapper_options).merge(input_options.slice(:multiple, :include_blank, :disabled, :prompt))

    template.select_tag(
      attribute_name, 
      template.options_from_collection_for_select(collection, value_method, label_method, selected: input_options[:selected], disabled: input_options[:disabled]), 
      merged_input_options
    )
  end
end

Then you can do <%= f.input :thing, as: :fake_select, collection: my_collection, selected: params[:thing] %>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEdward FordView Question on Stackoverflow
Solution 1 - Ruby on-RailshtanataView Answer on Stackoverflow
Solution 2 - Ruby on-RailsMiguel MaderoView Answer on Stackoverflow
Solution 3 - Ruby on-Railsjacr1614View Answer on Stackoverflow
Solution 4 - Ruby on-RailsTrung LêView Answer on Stackoverflow
Solution 5 - Ruby on-RailsFreePenderView Answer on Stackoverflow
Solution 6 - Ruby on-RailsEmilien BaudetView Answer on Stackoverflow