domingo, 5 de agosto de 2012

MultiModel forms, parte 2: One-To-Many

Este post é o segundo de uma série de quatro artigos:
O código desta aplicação de exemplo está disponível neste repositório Github.

Nested Models - One-to-Many

Segue 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 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 < ActiveRecord::Base
  attr_accessible :password, :username, :permissions_attributes
  validates_presence_of :username, :password, :person
  belongs_to :person
  has_many :permissions, inverse_of: :user_account
  accepts_nested_attributes_for :permissions, allow_destroy: true
end

class Permission < ActiveRecord::Base
  attr_accessible :restricted_area
  validates_presence_of :user_account, :restricted_area, :grants
  belongs_to :user_account
end


Façamos alguns testes no console do Rails:

> u = UserAccount.first
  UserAccount Load (0.2ms)  SELECT "user_accounts".* FROM "user_accounts" LIMIT 1
 => #<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"> 


> u.update_attributes permissions_attributes: [ { restricted_area: :admin, grants: "Read-Write" }, { restricted_area: :backup, grants: "Read-Only" } ]
   (0.3ms)  begin transaction
  Person Load (0.3ms)  SELECT "people".* FROM "people" WHERE "people"."id" = 1 LIMIT 1
  UserAccount Load (0.2ms)  SELECT "user_accounts".* FROM "user_accounts" WHERE "user_accounts"."id" = 1 LIMIT 1
  UserAccount Load (0.1ms)  SELECT "user_accounts".* FROM "user_accounts" WHERE "user_accounts"."id" = 1 LIMIT 1
  SQL (12.6ms)  INSERT INTO "permissions" ("created_at", "restricted_area", "updated_at", "user_account_id") VALUES (?, ?, ?, ?)  [["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]]
  SQL (0.2ms)  INSERT INTO "permissions" ("created_at", "restricted_area", "updated_at", "user_account_id") VALUES (?, ?, ?, ?)  [["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]]
   (58.7ms)  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" } ] }
   (0.1ms)  begin transaction
   (0.0ms)  commit transaction
   (0.1ms)  begin transaction
  SQL (36.8ms)  INSERT INTO "people" ("created_at", "name", "updated_at") VALUES (?, ?, ?)  [["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]]
  SQL (0.6ms)  INSERT INTO "user_accounts" ("created_at", "password", "person_id", "updated_at", "username") VALUES (?, ?, ?, ?, ?)  [["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"]]
  SQL (0.4ms)  INSERT INTO "permissions" ("created_at", "grants", "restricted_area", "updated_at", "user_account_id") VALUES (?, ?, ?, ?)  [["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]]
  SQL (0.2ms)  INSERT INTO "permissions" ("created_at", "grants", "restricted_area", "updated_at", "user_account_id") VALUES (?, ?, ?, ?)  [["created_at", Sun, 05 Aug 2012 05:08:31 UTC +00:00], ["grants", "Read-Only"], ["restricted_area", "backups"], ["updated_at", Sun, 05 Aug 2012 05:08:31 UTC +00:00], ["user_account_id", 3]]
   (71.4ms)  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ências

Nenhum comentário:

Postar um comentário