Este post e o terceiro de uma serie de quatro artigos:Parte 1: Nested Models - One-to-OneParte 2: Nested Models - One-to-ManyParte 3: Nested FormsParte 4: Nested Forms dinamicos usando Knockout.jsO codigo desta aplicacao de exemplo esta disponivel neste repositorio Github.Nested FormsNos dois primeiros posts desta serie, foi mostrado o poder do uso de Nested Models. O Rails fornece ainda helpers para a construcao de views com este recurso. Usamos estes helpers para reescrever o arquivo app/views/people/_form.html.erb (criado via scaffolding, no primeiro post):<%= form_for(@person) do |f| %> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name %> </div> <%= f.fields_for :user_account do |acc| %> <div class="field"> <%= acc.label :username %><br /> <%= acc.text_field :username %> </div> <div class="field"> <%= acc.label :password %><br /> <%= acc.password_field :password %> </div> <%= acc.fields_for :permissions do |p| %> <div class="field"> <%= p.label p.object.restricted_area %> <%= p.hidden_field :restricted_area %> <%= p.select :grants, [ "Read-Only", "Read-Write", "Read-Write-Delete" ], include_blank: true %> </div> <% end %> <% end %> <div class="actions"> <%= f.submit %> </div><% end %>Entre na tela de cadastro (http://localhost:3000/people/new), para visualizar um formulario contendo os campos de Person, UserAccount e Permission:Oops. Apenas os campos de Person estao visiveis. Nao e dificil conjecturar o porque. Ao criar um novo objeto Person, ele nao possui um user_account, como pode ser confirmado no console:> Person.new.user_account => nilE necessario sobrescrever o metodo Person/user_account, para que retorne um novo objeto quando Person nao tiver uma conta de usuario associada: def user_account @user_account = super @user_account = build_user_account if @user_account.nil? @user_account endIsto e suficiente para habilitar os campos de UserAccount:Para exibir os campos referentes aos atributos de Permission, criamos um metodo UserAccount/permissions_types, que pre-instancia as possiveis opcoes: def permissions_types hash = {} permissions.each { |p| hash[p.restricted_area] = p } [ "admin", "backups", "logs" ].each { |area| hash[area] = Permission.new restricted_area: area unless hash.has_key? area } hash.values endNote que as duas primeiras linhas "memorizam" associacoes previamente salvas, enquanto nas linhas seguintes, sao construidos novos objetos apenas para as associacoes que ainda nao estiverem presentes.E necessario modificar o formulario para construir os campos de permissoes utilizando este metodo: <%= acc.fields_for :permissions, acc.object.permissions_types do |p| %> <div class="field"> <%= p.label p.object.restricted_area %> <%= p.hidden_field :restricted_area %> <%= p.select :grants, [ "Read-Only", "Read-Write", "Read-Write-Delete" ], include_blank: true %> </div><% end %>Vejamos como ficou:Agora que os campos ja estao presentes no formulario, basta salvar um registro:Oops... estamos quase la... Vamos tentar entender o que aconteceu. Com auxilio do Chrome Developer Tools, podemos analisar os dados que sao submetidos com o formulario:Os view helpers do Rails criam campos com nomes que sao adequados a sua forma de processar nested models. Se analisarmos o log do controller, vemos que o request foi convertido para um hash, representando estes parametros de forma hierarquica (formatei o log para facilitar sua visualizacao):Started POST "/people" for 127.0.0.1 at 2012-08-05 22:36:11 -0300Connecting to database specified by database.ymlProcessing by PeopleController#create as HTMLParameters: { "utf8"=>"", "authenticity_token"=>"iTFVyyIwp0w2apXT02s+XN3z6PfOFEuUgZRaZWxPegU=", "person"=>{ "name"=>"Jose da Silva", "user_account_attributes"=>{ "username"=>"jsilva", "password"=>"[FILTERED]", "permissions_attributes"=>{ "0"=>{ "restricted_area"=>"admin", "grants"=>"Read-Only" }, "1"=>{ "restricted_area"=>"backups", "grants"=>"" }, "2"=>{ "restricted_area"=>"logs", "grants"=>"Read-Write" } } } }, "commit"=>"Create Person"}Faz sentido: o rails tentou criar