MultiModel forms, parte 2: One-To-Many
Autor: fabiolnm | Publicado: 2012-08-05T02:16:00-03:00

Este post é o segundo de uma série de quatro artigos:
Parte 1: Nested Models - One-to-One
Parte 2: Nested Models - One-to-Many
Parte 3: Nested Forms
Parte 4: Nested Forms dinâmicos usando Knockout.js
O código desta aplicação de exemplo está disponível neste repositório Github.
Nested Models - One-to-ManySegue o modelo utilizado neste tutorial:
Person has one UserAccount (parte 1)
UserAccount has many Permissions (parte 2)

Conforme estabelecido na parte 1 desta série, queremos editar UserAccount a partir do model Person. Nesta segunda parte, veremos como editar também a partir do model Person a associação one-to-many&nbsp;UserAccount / Permissions. O modelo de permissões será bem simples:

rails g model Permission user_account_id:integer restricted_area:string grants:string

rake db:migrate

Por fim, resta configurar as validações e relacionamentos nos respectivos models.

Nested One-to-ManyÉ necessário adicionar uma cláusula has_many em UserAccount, para que esta possa navegar na associação um-para-muitos. Permission possui a chave estrangeira user_account_id (lado belongs_to). Por fim, configuramos UserAccount para aceitar também os atributos da associação Permission.

class UserAccount &lt; ActiveRecord::Base
&nbsp; attr_accessible :password, :username, :permissions_attributes
&nbsp; validates_presence_of :username, :password, :person
&nbsp; belongs_to :person
&nbsp; has_many :permissions, inverse_of: :user_account
&nbsp; accepts_nested_attributes_for :permissions, allow_destroy: true
end

class Permission &lt; ActiveRecord::Base
&nbsp; attr_accessible :restricted_area
&nbsp; validates_presence_of :user_account, :restricted_area, :grants
&nbsp; belongs_to :user_account
end


Façamos alguns testes no console do Rails:

&gt; u = UserAccount.first
&nbsp; UserAccount Load (0.2ms) &nbsp;SELECT "user_accounts".* FROM "user_accounts" LIMIT 1
&nbsp;=&gt; #&lt;UserAccount id: 1, person_id: 1, username: "jsilva", password: "abc123", created_at: "2012-08-05 04:02:34", updated_at: "2012-08-05 04:02:34"&gt;&nbsp;


&gt; u.update_attributes permissions_attributes: [ { restricted_area: :admin, grants: "Read-Write" }, { restricted_area: :backup, grants: "Read-Only"&nbsp;} ]
&nbsp; &nbsp;(0.3ms) &nbsp;begin transaction
&nbsp; Person Load (0.3ms) &nbsp;SELECT "people".* FROM "people" WHERE "people"."id" = 1 LIMIT 1
&nbsp; UserAccount Load (0.2ms) &nbsp;SELECT "user_accounts".* FROM "user_accounts" WHERE "user_accounts"."id" = 1 LIMIT 1
&nbsp; UserAccount Load (0.1ms) &nbsp;SELECT "user_accounts".* FROM "user_accounts" WHERE "user_accounts"."id" = 1 LIMIT 1
&nbsp; SQL (12.6ms) &nbsp;INSERT INTO "permissions" ("created_at", "restricted_area", "updated_at", "user_account_id") VALUES (?, ?, ?, ?) &nbsp;[["created_at", Sun, 05 Aug 2012 04:54:31 UTC +00:00], ["grants", "Read-Write"], ["restricted_area", :admin], ["updated_at", Sun, 05 Aug 2012 04:54:31 UTC +00:00], ["user_account_id", 1]]
&nbsp; SQL (0.2ms) &nbsp;INSERT INTO "permissions" ("created_at", "restricted_area", "updated_at", "user_account_id") VALUES (?, ?, ?, ?) &nbsp;[["created_at", Sun, 05 Aug 2012 04:54:31 UTC +00:00], ["grants", "Read-Only"], ["restricted_area", :backup], ["updated_at", Sun, 05 Aug 2012 04:54:31 UTC +00:00], ["user_account_id", 1]]
&nbsp; &nbsp;(58.7ms) &nbsp;commit transaction


No exemplo acima, permissões foram adicionadas a uma conta existente de usuário. É possível ir além: todos os dados de UserAccount e Permission podem ser diretamente manipulados um nível acima, numa chamada de Person:

p = Person.create name: "João Oliveira", user_account_attributes: { username: "joliv", password: "secret", permissions_attributes: [ { restricted_area: "admin", grants: "Read-Write" }, { restricted_area: "backups", grants: "Read-Only" } ] }&nbsp; &nbsp;(0.1ms) &nbsp;begin transaction&nbsp; &nbsp;(0.0ms) &nbsp;commit transaction&nbsp; &nbsp;(0.1ms) &nbsp;begin transaction&nbsp; SQL (36.8ms) &nbsp;INSERT INTO "people" ("created_at", "name", "updated_at") VALUES (?, ?, ?) &nbsp;[["created_at", Sun, 05 Aug 2012 05:08:31 UTC +00:00], ["name", "João Oliveira"], ["updated_at", Sun, 05 Aug 2012 05:08:31 UTC +00:00]]&nbsp; SQL (0.6ms) &nbsp;INSERT INTO "user_accounts" ("created_at", "password", "person_id", "updated_at", "username") VALUES (?, ?, ?, ?, ?) &nbsp;[["created_at", Sun, 05 Aug 2012 05:08:31 UTC +00:00], ["password", "secret"], ["person_id", 3], ["updated_at", Sun, 05 Aug 2012 05:08:31 UTC +00:00], ["username", "joliv"]]&nbsp; SQL (0.4ms) &nbsp;INSERT INTO "permissions" ("created_at", "grants", "restricted_area", "updated_at", "user_account_id") VALUES (?, ?, ?, ?) &nbsp;[["created_at", Sun, 05 Aug 2012 05:08:31 UTC +00:00], ["grants", "Read-Write"], ["restricted_area", "admin"], ["updated_at", Sun, 05 Aug 2012 05:08:31 UTC +00:00], ["user_account_id", 3]]&nbsp; SQL (0.2ms) &nbsp;INSERT INTO "permissions" ("created_at", "grants", "restricted_area", "updated_at", "user_account_id") VALUES (?, ?, ?, ?) &nbsp;[["created_at", Sun, 05 Aug 2012 05:08:31 UTC +00:00],&nbsp;["grants", "Read-Only"],&nbsp;["restricted_area", "backups"], ["updated_at", Sun, 05 Aug 2012 05:08:31 UTC +00:00], ["user_account_id", 3]]&nbsp; &nbsp;(71.4ms) &nbsp;commit transaction
Aprecie o poder deste recurso: uma simples linha de comando, e numa única transação são persistidos 4 objetos: Person, UserAccount e duas Permission. Não pára por aí. Este poder é ainda mais amplo, visto que Rails fornece helpers para construção de formulários com nested models - tema que será abordado na parte 3 desta série.
Referênciashttp://guides.rubyonrails.org/getting_started.html#building-a-multi-model-form
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
http://erikonrails.snowedin.net/?p=267
http://archives.ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes
http://weblog.rubyonrails.org/2009/1/26/nested-model-forms/