Skip to content

An alternative to accepts_nested_attributes_for that doesn't tightly couple your view to your model


Notifications You must be signed in to change notification settings


Folders and files

Last commit message
Last commit date

Latest commit


Repository files navigation

Redtape Build Status

Redtape provides an alternative to ActiveRecord::NestedAttributes#accepts_nested_attributes_for in the form of, well, a Form! The initial implementation was heavily inspired by "7 Ways to Decompose Fat Activerecord Models" by Bryan Helmkamp.

In a nutshell, accepts_nested_attributes_for tightly couples your View to your Model. This is highly undesirable as it makes both harder to maintain. Instead, the Form provides a Controller delegate that mediates between the two, acting like an ActiveModel from the View and Controller's perspective but acting a proxy to the Model layer.


  • Automatically converting nested form data into the appropriate ActiveRecord object graph
  • Optional dependency injection of a data mapper to map form fields to ActiveRecord object fields
  • Optional form data whitelisting


Add this line to your application's Gemfile:

gem 'redtape'

And then execute:

$ bundle

Or install it yourself as:

$ gem install redtape


To use Redtape, you use a Redtape::Form in your controller and your nested form_fors where you would supply an ActiveRecord object.

A Redtape::Form is simply an ActiveModel. So you just call #save, #valid?, and #errors on it like any other ActiveModel.

Redtape will use your model's/models' validations to determine if the form data is correct. That is, you validate and save the same way you would with any ActiveModel. If any of the models are invalid, errors are added to the Form for handling within the View/Controller.

Using a Redtape::Form goes something like this:

<%= form_for @form, :as => :whatever %>
class SomethingController
  def new
    @form =, params)

  def create # should support update as well...
    @form =, params)
      # ...
      # ...

If you want to get to the AR object directory...

Call #model thusly on your Redtape::Form instance:

  @form =, params)

If your controller name doesn't map directly to the form's ActiveRecord class...

You just add an argument:

class SomethingController
  def create
    @form =, params, :top_level_name => :user)
    # ...

(Optional) Custom form field mapping to ActiveRecord objects

A Redtape "data mapper" is just a class that implements a #populate_individual_record method such as:

module NestedFormRedtape
  def populate_individual_record(record, attrs)
    if record.is_a?(User) = "#{attrs[:first_name]} #{attrs[:last_name]}"
    elsif record.is_a?(Address)
      record.attributes = record.attributes.merge(attrs)

Yes, we are branching on classes. Yes, this usually is a smell to use polymorphism. In this case, the average data mapper is going to be pretty simple. As such, I didn't find this to be onerous.

To use this custom data mapper, just mix it into your controller. Redtape detects the presence of your method and uses it instead of the default implementation.

I tend to implement these as modules to simplify testing. I create an object that I nominally call a "*Controller", mix in the module, and stub out a #params method. This gives me something close enough to a controller for testing while not requiring instantiating a real Rails Controller. For examples, see the spec directory.

Optional whitelisting

This should like familiar to anyone who has used the :include option on an ActiveRecord finder., params, :whitelisted_attrs => {
    :user => [
      { :phone_number => [ :country_code, :area_code, :number ] },
      { :addresses => [:address1, :address2, :city, :state, :zipcode] }

Currently, if a whitelist validation occurs, a Redtape::WhitelistValidationError is raised containing a detailed error message of violating parameters. I figured you'd like to know

What's left

We'd really like to add the following to make Redtape even easier for folks to plug n' play:

  • A Rails generator to add the app/forms and (test/spec)/forms directories


  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Finally, we'd really like your feedback


An alternative to accepts_nested_attributes_for that doesn't tightly couple your view to your model