<formmethod="POST"action="/products"id="new_product"><div><labelfor="product_price_in_cents">Price in cents</label><inputtype="number"name="product[price_in_cents]"id="product_price_in_cents"></div></form>

Writing out forms like this is better than writing all the elements by hand, but it still leaves something to be desired. Let’s use Simple Form to make it suck less.

Generate consistent markup

Simple Form is implemented as a form builder, and it gives us a handy simple_form_for helper that mimics the regular form_for, but uses its own form builder. We can use like to so:

<%=simple_form_for@articledo|f|%><%=f.input:price_in_cents%><%end%>

Simple Form’s form builder gives us one new feature: the input method. This method does a couple of things. This is the output it generates:

Sensible HTML boilerplate

That’s mostly the same output as before. That’s because input uses all the same form helpers that we would otherwise use. But Simple Form can make a good guess at what type of input element to use, and gives us some helpful classes along with it. What’s more, it will automatically use I18n to translate labels and insert hint texts. If we edit our config/locales/en.yml to include this:

en:simple_form:labels:product:price_in_cents:Pricehints:product:price_in_cents:Enter the total price in cents.

…our output code changes to:

<divclass="input string product_price_in_cents field_with_hint"><labelclass="string"for="product_price_in_cents">Price</label><inputclass="string"type="text"name="product[price_in_cents]"id="product_price_in_cents"><spanclass="hint">Enter the total price in cents.</span></div>

Note how the contents of the <label> element was automatically changed, and how we got a new span.hint element below our input. The added field_with_hint class allows us to style our input to our liking.

Column-aware customizations

Since our database column is not null-able, we should add a presence validation to our model:

We’ve got an extra class on our input wrapper to indicate our field is required, there’s a new element in our label to indicate the field is required, and when we submit the form without entering anything, we even get inline validation errors (the span.error) and an extra class on our wrapper (field_with_errors) so we can style our input. That’s a lot of useful stuff we got for free there!

Benefits for markup

Let’s review how Simple Form helps us write sane, consistent markup:

automatically generated HTML boilerplate code ensures all our forms use the same structure and naming conventions;

Basically, Simple Form simply automates a lot tedious decisions away. This leaves us with clean input code and consistent output code. I find this to be equal parts reluctantly succinct and awkwardly auto-magical; since forms are such a tedious but well-defined problem, I can accept the added complexity.

Markup customization with wrappers

By generating our markup for us, Simple Form takes a lot of trivial decisions out of our hands — but that’s not to say we simply have to accept whatever defaults it gives us. We can configure Simple Form with wrappers to customize the components and HTML structure it generates. We have seen the output for Simple Form’s default wrapper; now let’s define our own. We’ll add a new wrapper for inputs with label and input elements arranged horizontally, rather than vertically. We’ll call it inline and we define it in config/initializers/simple_form.rb:

Notice how we’ve now ended up with some BEM-like classes, wrapping tags that allow styling with a CSS grid framework, and a new wrapper tag around the hint and error — and with the unless_blank option we even ensure this wrapper is omitted when it has no content. Now we’re getting a lot of mileage out of that one f.input :price_in_cents in our template!

With wrappers we can control the generated output apart from our label and input elements themselves. Next, we’ll see how we can customize those using custom inputs.

Custom input components

Apart from the markup around our input elements, we can also create custom input components for the input elements themselves. A custom input allows us to customize the <input> and <label> elements for a given attribute, taking the current form object into account.

Customize HTML output

Let’s create a simple custom input type that we can use for monetary values. Although Simple Form can infer input types from column types and names, we’ll stick with explicitly telling it which input type to use:

Tweaking output by overriding hook methods

Custom inputs are Ruby objects that know how to generate strings of HTML code. These objects can contain quite a bit of behavior, so we’re best off subclassing one of the built-in inputs — in this case, the regular StringInput. The one method we’ve tweaked is input, which should output the <input> element. We’ve prefixed the old output with a Euro-sign, resulting in output like this:

This demonstrates a pattern in custom inputs: for the most part, we can rely on built-in inputs and default behavior to build our output — but by overriding specific “hook” methods, we can achieve almost any result we want.

Using two input fields for monetary values

Let’s set ourselves a challenge: rather than asking our users to enter a monetary value in cents, we can present them with two input fields instead: one for euros and one for cents. A custom input allows us to DRYly do this. This is roughly the output we want to achieve:

Note the special naming convention of the attributes: the (1i) and (2i) suffices will trigger Rails to parse these two parameters into a single, composite value of two integers. In a minute, we’ll deal with these values on the server side. This is how we want to implement the input method in our MoneyInput class:

Our (as of yet undefined) input_major and input_minor methods should output strings of HTML code. To do so, we can use any helper method we want, since we can access our view via the template attribute. Also, we can access the form builder (and though it the underlying model) via @builder. Here’s the simplest possible implementation for input_major:

we use @builder.object to access the @product object in simple_form_for(@product);

we use @builder.object_name to get the parameter key for this type of object, which in this case is "product";

we use attribute_name to get the name of the current attribute (i.e. :price_in_cents in f.input :price_in_cents);

Now we can use this custom MoneyInput for any model and any attribute we want!

Connecting the label

Our custom input is also responsible for generating the <label> tag for our input element. Since we’ve used a custom name for our input elements, we should tweak the for attribute of our <label> tag. We can override the label_html_options method to do that:

The label_html_options returns a hash of options to be sent to Rails’ label_tag helper method. We use super to take the original output and merge in our custom for option to connect it to the the first of our two <input> tags.

Putting it all together

Although there’s lots more we could do, we’ve got a working input element now. Here’s the complete code of our MoneyInput:

You might have noticed that our MoneyInput is now responsible for extracting euros and cents from a single integer value in our model. This is business logic that should definitely not belong in an input type — which solidly belongs in the presentation layer. Let’s fix that, and the issue of how to deal with two parameters for a single attribute, next.

Models composed of complex values

ActiveRecord models can be composed of complex values, such as Date or DateTime objects. A Date object consists of three integers for year, month and day; to assign a date value, Rails’ date_select gives us a <select> element for each of these. Special parameter name suffices indicate how these values should be parsed.

Rails takes the suffices and reads both how to parse the values (i for integer) and how to order them in the Date.new call (1, 2, 3). We can also use this feature for our own complex attribute types, such as Money.

Mapping our own complex types

To do so, we can use composed_of to let Rails know how it should transform our database values to a Ruby object, and back again:

We’ve indicated that our model has a price attribute that returns a Money object. The constructor property indicates how price values can be read from the database price_in_cents column, while the regular new method takes input values from our params. Our Money class, then, can look like this:

classMoneyincludeComparable# Create a new `Money` from a single database value integer,# which is the total amount in cents.defself.from_cents(cents)new(*cents.divmod(100))end# Build new `Money` value using a major and minor value,# so we don't have to use floats.definitialize(major,minor)@cents=major*100+minorfreezeenddefmajor@cents/100enddefminorformat'%02d',@cents%100enddefinspect"#<Money #{major}.#{minor}>"enddefeql?(other)other.is_a?(self.class)&&@cents==other.centsenddef<=>(other)@cents<=>other.centsendend

Instances of our new Money object are compared and sorted by their cents value. We can use it like so:

Combining custom inputs and complex types

We can now link our MoneyInput and Money value class together. Rather than calculating euros and cents in the custom input, we can use our new value object. We’ll point our input at our “virtual” price attribute:

Now we’ll post two ordered integer parameters to the price attribute, which will construct a new Money object in our model. This will, in turn, be persisted into the price_in_cents column. We’ve got a nicely re-usable but non-trivial input type and a pretty value object on the server side to go along with it. Nice!

Conclusion

Simple Form helps us write consistent markup that is easy to style, and nudges us into the right direction towards value objects and away from primitive obsession. The wrapper DSL and structure of custom input classes has a bit of a learning curve, but it is worth your while to get to know them. Simple Form is, when used correctly, a real time saver in Rails projects.

Ruby

Simple Form

Arjan van der Gaag

A thirtysomething software developer, historian and all-round geek. This is his blog about Ruby, Rails, Javascript, Git, CSS, software and the web. Back to all talks and articles?

Discuss

You cannot leave comments on my site, but you can always tweet questions or comments at me: @avdgaag.