plataformatec/simple_form — pull request #997 by rafaelfranca (2014-03-11 21:03:43+00:00)
Title: Make possible to use the Wrappers API to define attributes for the components

(This is a alternative solution to https://github.com/plataformatec/simple_form/pull/622)

## Motivation

To integrate Simple Form with Bootstrap 3 we need to make possible to define classes to input only in some wrappers.

We added `input_class` some time ago but that [broke some components like checkboxes](https://github.com/rafaelfranca/simple_form-bootstrap/commit/7d76738a01d647d77447cc38afd6786d4f69af83) (see https://github.com/rafaelfranca/simple_form-bootstrap/pull/28).

Instead of changing a global class to be applicated in every input we allow users to choose in which wrappers they want to define a class.

## How to use

Now users can define attributes for the components in the component definition of the Wrappers API.

```ruby
config.wrappers tag: 'div', class: 'control-group', error_class: 'error' do |b|
b.use :label, class: 'form-label'
b.use :input, class: 'form-input'
end
```

The `class: 'form-label'` option will be used every time a label component is rendered.

## Implementation

To make this possible I had to change who the Wrappers API is defined and how components are rendered.

### Wrapper API storage change

Before the work on this branch, the API components were stored in a mix of Simple Form classes and symbols.

![simple_form_wrapper_before](https://f.cloud.github.com/assets/47848/2390499/8c4edad8-a95b-11e3-99f5-b4e38d29a773.png)

This added some conditionals in our rendering code and made harder to store state in our components. This create a lot of code in Simple Form that rely in the fact that Ruby hashes are mutables and we have a global state. The implementation in #622 had this problem were the changes in a global hash make the [hint and errors classes to getting duplicated](https://github.com/plataformatec/simple_form/pull/622#issuecomment-28395418).

I choose to avoid completely this problem storing the attributes in a class and making it implement the same protocol as `SimpleForm::Wrappers::Many` and `SimpleForm::Wrapper::Single`. This also make possible to remove some conditionals to deal with Symbols in the components tree.

![simple_form_wrapper_after](https://f.cloud.github.com/assets/47848/2390659/4bee55ac-a95d-11e3-9c11-eb034b76d2dd.png)

### Input rendering

Before this work, when rendering the input, there was no way to pass information about the context where it is being rendered. I changed the implementation to always pass the context for the component rendering method itself.

To understand better lets look how a custom input was implemented before:

```ruby
class CustomInput < SimpleForm::Inputs::Base
def input
@builder.text_field(attribute_name, input_html_options)
end
end
```

So when rendering a wrapper Simple Form will call the method `input` of that class. As you can see we don't have a clean way to get information about the wrapper that is being rendered inside that method.

Now, Simple Form will pass an argument `context` with an instance of `SimpleForm::Wrappers::Leaf` that hold the options defined in the Wrappers API. So, for our previous example, `context.options` will contain `class: 'form-input'`.

So, our custom input will look like:

```ruby
class CustomInput < SimpleForm::Inputs::Base
def input(context)
@builder.text_field(attribute_name, input_html_options)
end
end
```

Now we can use a new helper `merge_wrapper_options` to merge the options present in the `form.input` call and the options defined in the wrapper.

Finally, our custom input will look like:

```ruby
class CustomInput < SimpleForm::Inputs::Base
def input(context)
@builder.text_field(attribute_name, merge_wrapper_options(input_html_options, context))
end
end
```

## Deprecations

The custom input/custom components methods now have to accept one argument. The inputs/components defined using the old API will work but with a deprecation message.

## Next steps

* [ ] Add proper documentation
* [ ] Test this new feature writing the wrappers for Bootstrap 3

Closes #622