- Editar Carrinho de Compras e Itens do Carrinho
- Dados de uma Pessoa e Informações de Contato
- Dados de um Usuário e Permissões.
- Post num blog e suas Tags.
- 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
Nested Models - One-to-One
Segue o modelo utilizado neste tutorial:- Person has one UserAccount
- UserAccount has many Permissions (parte 2)
rails g scaffold Person name:string
Neste ponto, pode-se rodar as migrations e iniciar o servidor de desenvolvimento:
rake db:migrate
rails s
E já estará disponível a tela - bastante rudimentar - para a edição de Person:
Como queremos editar UserAccount a partir do model Person, dispensaremos scaffold, sendo necessário criar o respectivo modelo:
rails g model UserAccount person_id:integer username:string password:string
rake db:migrate
Por fim, resta configurar as validações e relacionamentos nos respectivos models.
Nested One-to-One
A relação entre os modelos Person e UserAccount é de um-para-um, sendo que UserAccount possui a chave estrangeira person_id (lado belongs_to):
class Person < ActiveRecord::Base
attr_accessible :name
validates_presence_of :name
has_one :user_account
end
class UserAccount < ActiveRecord::Base
attr_accessible :password, :username
validates_presence_of :username, :password
belongs_to :person
end
Podemos usar o rails console para um teste inicial:
$ rails c
> p = Person.create name: "José da Silva"
(0.1ms) begin transaction
SQL (16.4ms) INSERT INTO "people" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", Tue, 31 Jul 2012 05:57:41 UTC +00:00], ["name", "José da Silva"], ["updated_at", Tue, 31 Jul 2012 05:57:41 UTC +00:00]]
(59.6ms) commit transaction
No exemplo acima, foi criado um objeto Person, passando para o método create um hash contendo apenas o atributo name.
O uso de nested models possibilita que a chamada create receba também os atributos username e password, para criar uma UserAccount simultaneamente ao objeto Person.
class Person < ActiveRecord::Base
attr_accessible :name, :user_account_attributes
validates_presence_of :name
has_one :user_account
accepts_nested_attributes_for :user_account
end
Note que é necessário tornar esses atributos acessíveis via mass-assignment, na lista attr_accessible.
> p = Person.create name: "José da Silva", user_account_attributes: { username: "jsilva", password: "abc123" }
(0.1ms) begin transaction
(0.1ms) commit transaction
(0.0ms) begin transaction
SQL (6.1ms) INSERT INTO "people" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", Tue, 31 Jul 2012 06:23:26 UTC +00:00], ["name", "José da Silva"], ["updated_at", Tue, 31 Jul 2012 06:23:26 UTC +00:00]]
SQL (0.4ms) INSERT INTO "user_accounts" ("created_at", "password", "person_id", "updated_at", "username") VALUES (?, ?, ?, ?, ?) [["created_at", Tue, 31 Jul 2012 06:23:26 UTC +00:00], ["password", "abc123"], ["person_id", 5], ["updated_at", Tue, 31 Jul 2012 06:23:26 UTC +00:00], ["username", "jsilva"]]
(56.6ms) commit transaction
Validação
O leitor mais atento pode ter notado que UserAccount não está validando a presença de Person. Isto deixa uma brecha no código, por permitir a criação de uma UserAccount desassociada de uma pessoa:
> UserAccount.create username: "joao", password: "abc123"
(0.2ms) begin transaction
SQL (1.4ms) INSERT INTO "user_accounts" ("created_at", "password", "person_id", "updated_at", "username") VALUES (?, ?, ?, ?, ?) [["created_at", Tue, 31 Jul 2012 06:25:34 UTC +00:00], ["password", "abc123"], ["person_id", nil], ["updated_at", Tue, 31 Jul 2012 06:25:34 UTC +00:00], ["username", "joao"]]
(33.3ms) commit transaction
Porém isto não faz sentido no nosso modelo. Façamos a correção:
class UserAccount < ActiveRecord::Base
attr_accessible :password, :username
validates_presence_of :person, :username, :password
belongs_to :person
end
Agora UserAccount passa se comportar conforme esperado:
> u = UserAccount.create username: "a", password: "b"
(0.2ms) begin transaction
(0.1ms) rollback transaction
> u.errors.messages
=> {:person=>["can't be blank"]}
Porém, isto tem um efeito colateral: ela afeta a criação da user_account via nested attributes de Person:
> p = Person.create name: "José da Silva", user_account_attributes: { username: "jsilva", password: "abc123" }
(0.1ms) begin transaction
(0.1ms) commit transaction
(0.1ms) begin transaction
(0.1ms) rollback transaction
> p.errors.messages
=> {:"user_account.person"=>["can't be blank"]}
Para evitar que ocorra a validação quando são usados nested attributes, deve-se informar para Person não validar o atributo person da associação user_account:
class Person < ActiveRecord::Base
attr_accessible :name, :user_account_attributes
validates_presence_of :name
has_one :user_account, inverse_of: :person
accepts_nested_attributes_for :user_account
end
Update
Para uma operação de atualização, é necessário passar em user_account_attributes o id da user_account previamente criada:
> p.update_attributes user_account_attributes: { id:6, username: "jsilva11", password: "123456" }
(0.1ms) begin transaction
(0.6ms) UPDATE "user_accounts" SET "username" = 'jsilva11', "updated_at" = '2012-07-31 07:43:20.649645' WHERE "user_accounts"."id" = 6
(49.9ms) commit transaction
CUIDADO: caso o id fosse omitido, seria feito um novo insert na tabela user_accounts, e o objeto Person seria reassociado à nova user_account.
Destroy
Para completar este tutorial, suponhamos que faça sentido remover uma user_account, sem remover os respectivos dados de person. Isto pode ser obtido marcando o nested_model com allow_destroy:
class Person < ActiveRecord::Base
attr_accessible :name, :user_account_attributes
validates_presence_of :name
has_one :user_account, inverse_of: :person
accepts_nested_attributes_for :user_account, allow_destroy: true
end
Agora, pode-se remover user_account passando _destroy: true no hash user_account_attributes:
> p.update_attributes user_account_attributes: { id:6, _destroy: true }
(0.2ms) begin transaction
UserAccount Load (0.1ms) SELECT "user_accounts".* FROM "user_accounts" WHERE "user_accounts"."person_id" = 7 LIMIT 1
SQL (4.9ms) DELETE FROM "user_accounts" WHERE "user_accounts"."id" = ? [["id", 6]]
(47.8ms) commit transaction
> p.reload.user_account
Person Load (0.3ms) SELECT "people".* FROM "people" WHERE "people"."id" = ? LIMIT 1 [["id", 7]]
UserAccount Load (1.9ms) SELECT "user_accounts".* FROM "user_accounts" WHERE "user_accounts"."person_id" = 7 LIMIT 1
=> nil
Referências
- http://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/
Nenhum comentário:
Postar um comentário