1
votes

I used a fake input field to get simple_form to render a field that doesn't correspond to a field in the model.

class FakeInput < SimpleForm::Inputs::StringInput
  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

In my form, the model I am working with is deeply nested. This means the input name is long and very specific. An Event has_many Positions which has_many Responsibilities. I want to add a fake field to the responsibilities called position_count.

As you can see in the markup below, the label gets the correct value for its for attribute but the input does not get the correct name attribute. What do I need to change (possibly in the fake_input.rb class?) to get the input to have the correct name?

The correct name for the input should be

event_positions_attributes_0_responsibilities_attributes_0_position_count

but the fake field is only rendered as

position_count

The markup being rendered is...

<div class="input fake optional event_positions_responsibilities_position_count">
    <label for="event_positions_attributes_0_responsibilities_attributes_0_position_count" class="fake optional control-label">Position count</label>
    <input type="text" name="position_count" id="position_count" class="fake optional">
</div>
1
Have you considered overriding name attribute input_html: { name: 'event_positions_attributes_0_responsibilities_attributes_0_position_count' }Petr Gazarov
Any specific reason that you don't just use a virtual attribute in the model instead?max
@max No specific reason. This method is what was recommended in the simple_form wiki.Jeff
I would try that first - just add attr_accessor :position_count to Responsibility and you can just create an inputs as if it was a regular database column backed attribute.max
Based on other SO questions, it seems like property getters/setters is the preferred way to handle virtual attributes in Rails 4. Immediate tests look promising as the name attribute is properly constructed. I'm still working on wiring up the controllerJeff

1 Answers

1
votes

Its because you are using text_field_tag - this is one of the "low-level" form helpers that is not bound to an object.

So when you call template.text_field_tag(attribute_name, nil, merged_input_options) it actually uses attribute_name for the name attribute.

This differs from SimpleForm::Inputs::StringInput that uses @builder.text_field(attribute_name, merged_input_options) which is bound to an object.

An easier way

A far simpler way to create an input that does not correspond to a database column is by using "virtual attributes" - in other words just a plain old instance variable with a getter and setter.

class Responsibility < ActiveRecord::Base
  # ...
  attr_accessor :position_count
end

<%= simple_form_for(@responsibity) do |f| %>
  <%= f.input :position_count %>
<% end %>

By default, Simple Form will look at the column type in the database and use an appropriate input for the column.

Thats not possible in the case - I do believe it will fall back to :string (<input type="text>") - you can use the as option if you want a specific type of input.