Accessible Associations

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

Introduction

Using multi-model forms in Hobo is very straightforward:

class Blog < ActiveRecord::Base
  hobo_model
  has_many :posts, :accessible => true, :inverse_of => :blog

class Post < ActiveRecord::Base
  hobo_model
  belongs_to :blog, :inverse_of => :posts

The :accessible flag is a Hobo addition, the :inverse_of flag is standard Rails. :inverse_of is optional but highly recommended in Rails; Hobo requires it.

Once you’ve done that, the default forms that Hobo builds will use the 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. See the Rails rdoc 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
   attr_accessible :name, :bars
 end
>>
 class Bar < ActiveRecord::Base
   hobo_model
   fields { name :string }
   belongs_to :foo
   attr_accessible :name, :foo, :foo_id
 end
>> migrate

The :accessible => true option patches in Hobo::Model::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 returns an array of hashes, 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, input-all, select-many and 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 or Ryan Daigle’s blog.

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 later versions of Rapid 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.


Edit this page