Accessible Associations
{.document-title}

This chapter describes Hobo's support for nested models in forms.
This is mostly technical background -- beginners should not have to
read more than the introduction.

Contents
{.contents-heading}

- contents
{:toc}

    >> require 'rubygems'
    >> require 'active_support'
    >> require 'active_record'
    >> require 'action_pack'
    >> require 'action_view'
    >> require 'action_controller'
    >> mysql_adapter = defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql'
    >> mysql_user = 'root'; mysql_password = ''
    >> mysql_login = "-u #{mysql_user} --password='#{mysql_password}'"
    >> mysql_database = "hobofields_doctest"
    >> system "mysqladmin #{mysql_login} --force drop #{mysql_database} 2> /dev/null"
    >> system("mysqladmin #{mysql_login} create #{mysql_database}") or raise "could not create database"
    >> ActiveRecord::Base.establish_connection(:adapter => mysql_adapter,
                                               :database => mysql_database,
                                               :host => "localhost", 
                                               :username => mysql_user, 
                                               :password => mysql_password)
    >> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobofields/lib')
    >> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobosupport/lib')
    >> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobo/lib')
    >> require 'will_paginate'
    >> require 'will_paginate/finder'
    >> require 'hobosupport'
    >> require 'hobofields'
    >> require 'hobo'
    >> Hobo::Model.enable
    >> HoboFields.enable
    >> ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new(STDOUT)
    >>
     def migrate(renames={})
       up, down = HoboFields::MigrationGenerator.run(renames)
       ActiveRecord::Migration.class_eval(up)
       ActiveRecord::Base.send(:subclasses).each { |model| model.reset_column_information }
       [up, down]
     end
{.hidden}

# Introduction

Using multi-model forms in Hobo is very straightforward:

      has_many :posts, :accessible => true

Once you've done that, the default forms that Hobo builds will use the
[input-many](/api_tag_defs/input-many) tag.

`:accessible => true` works for `has_many`, `has_many :through` and
`belongs_to`, but does not work for `has_one` or
`has_and_belongs_to_many`.

It's quite common to also add the `:dependent => :destroy` flag to
accessible associations.  This also used to trigger magic in Hobo, but
this additional magic has been removed and replaced with [View
Hints](/manual/viewhints).  See the [Rails
rdoc](http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html)
for more information on `:dependent => :destroy`.

# Model Support

We'll use rubydoctest to provide our examples for this section.  Here
are the models:

    >> 
     class Foo < ActiveRecord::Base
       hobo_model
       fields { name :string }
       has_many :bars, :accessible => true
     end
    >> 
     class Bar < ActiveRecord::Base
       hobo_model
       fields { name :string }
       belongs_to :foo
     end
    >> migrate

The `:accessible => true` option patches in
`Hobo::AccessibleAssociations` to your ActiveRecord model.  It
modifies the `bars=` writer function to support assigning an array of
records, an array of hashes, an array of ids, or an empty string.

## Assigning an array of records

The whole array must be assigned -- any records that are not assigned
are deleted from your association.

    >> bar1 = Bar.new(:name => "bar1")
    >> bar2 = Bar.new(:name => "bar2")
    >> foo = Foo.new(:name => "foo1")
    >> foo.bars = [bar1, bar2]
    >> foo.bars.*.name
    => ["bar1", "bar2"]
    >> foo.save!

    >> foo.bars = [bar2]
    >> foo.bars.*.name
    => ["bar2"]
    >> foo.save!

    >> bar2.foo.name
    => "foo1"
    >> bar1.reload
    >> bar1.foo
    => nil    

If `:dependent => :destroy` had been set on `has_many :bars`, bar1
would now be deleted from the database.   Since it hasn't, it still
exists in the database but has become orphaned.

## Assigning an array of hashes

Assigning an array of hashes maps nicely with how Rails deconstructs
your URI encoded query string.  For example, your form can return

    foo[bars][0][name]=bar1&foo[bars][0][name]=bar2

which Rails will decode into your params hash as

    >> params = {"foo" => {"bars" => [ {"name" => "bar3"}, {"name" => "bar4"}]}}

With Hobo's accessible associations, the params hash may be directly
assigned.

    >> foo.attributes = params["foo"]
    >> foo.bars.*.name
    => ["bar3", "bar4"]
    >> foo.save!

Because these parameters did not include an ID, Hobo created new bar
models.   If you include an ID, Hobo looks up the existing record in
the database and modifies it with the parameters assigned.

    >> params = {"foo" => {"bars" => [ {:name => "bar3_mod", :id => "#{foo.bars[0].id}"}]}}

    >> old_bar3_id = foo.bars[0].id
    >> foo.attributes = params["foo"]
    >> foo.save!
    >> foo.bars.*.name
    => ["bar3_mod"]
    >> foo.bars[0].id == old_bar3_id
    => true

## Assigning an array of IDs

While [input-many](/api_tag_defs/input-many) returns an array of
hashes, [select-many](/api_tag_defs/select-many) returns an array of
ids.  These ids must have an "@" prepended.

    >> params = {"foo" => {"bars" => ["@#{bar1.id}", "@#{bar2.id}"]}}
    >> foo.attributes = params["foo"]
    >> foo.save!
    >> foo.bars.*.name
    => ["bar1", "bar2"]

## Assigning an empty string

You can remove all elements from the association by assigning an empty
array:

    >> foo.bars = []
    >> foo.bars
    => []

However, there is no way to format a URI query string to make Rails
construct an empty array in its params hash, so Hobo adds a useful
shortcut to it's accessible associations:

    >> foo.bars = ""
    >> foo.bars
    => []

# View Support

The Rapid tags [input-many](/api_tag_defs/input-many),
[input-all](/api_tag_defs/input-all),
[select-many](/api_tag_defs/select-many) and
[check-many](/api_tag_defs/check-many) all require accessible
associations.

`input-many` may even be used in a nested fashion:

    <form>
      <field-list:>
        <foos-view:>
          <input-many>
            <field-list:>
              <bars-view:>
                <input-many>
                </input-many>
              </bars-view:>
            </field-list:>
          </input-many>
        </foos-view:>
      </field-list:>
    </form>

You do not need to use the accessible association tags -- standard
inputs acquire the correct `name` for use with accessible associations
when called from the appropriate context.  Here's an example form that
will work with the example given above in *Model Support*

    <form>
      <field-list:>
        <bars-view:>
          <repeat>
            <input:name/>
          </repeat>
        </bars-view:>
      <field-list:>
    </form>

# Controller Support

No special code is required in your controllers to support accessible
associations, even if you aren't using a Hobo controller.

# Validations

Validations simply work as you'd expect.  The only thing to note is
that validation errors in a child object will cause the parent
object to receive an error message of "..." on the association.

# Transactions

Hobo's accessible associations do not do any explicit saves so any new
child objects are not saved until the parent object is saved.   Rails
wraps this save in a transaction, so any save is an all or nothing
deal even though parents and children are saved via different SQL
statements.

# Rails 2.3 nested models

Rails 2.3 includes a functionality similar to Hobo's accessible
associations.  The Hobo version is based on an early version of the
Rails functionality, but unfortunately the Rails version changed
significantly between the time that the Hobo version was released and
when Rails 2.3 was released.

For more information on Rails 2.3's `accept_nested_attributes_for`,
see [the Ruby on Rails
blog](http://weblog.rubyonrails.org/2009/1/26/nested-model-forms) or
[Ryan Daigle's blog](http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes).

The two versions use a different model:  in Hobo the whole array is
assigned, allowing add, delete or update via a single mechanism.   In
rails, there's a different mechanism for adding or deleting objects
from the association, and there's no method for update.

Each version have their pluses and minuses.  The Hobo version is
conceptually simpler, but it starts to get unwieldy if there are a
large number of elements in the association.

Both mechanisms are compatible and may be enabled simultaneously.

It's certainly possible that Rapid >=1.1 will acquire tags that will
require `accept_nested_attributes_for`.   However, it's unlikely that
Hobo will drop support for accessible associations unless ActiveRecord
itself changes significantly.