Pequenas idéias, grandes soluções.

Comunicação, Simplicidade, Feedback e Coragem.

Rails 2.3 – Nested Attributes (Atributos Aninhados)

com 12 comentários

Uma nova funcionalidade adicionada ao Rails 2.3 é a possibilidade de atualizar os atributos em modelos aninhados diretamente.

Para mostrar essa funcionalidade vamos trabalhar numa aplicação de gerenciamento de contatos, permitindo cadastrar pessoas, contatos e cidades. A estrutura é bastante simples: uma pessoa possui muitos contatos e pertence a uma cidade. O código foi baseado no exemplo de gerenciamento de formulários complexos criado pelo Eloy Duran.

Durante o desenvolvimento da aplicação vou criar também alguns testes, contudo eles não serão disponibilizados aqui para não estender demais o post. Quem tiver interesse poderá conferir o código fonte no github. Já aviso a todos que não sou um dos maiores peritos em testes, estou aprendendo, portanto qualquer dica sempre será bem-vinda =).

Bom, vamos lá! Utilizando o terminal, navegue até o diretório onde deseja criar a aplicação e execute os comandos:

rails nested_attributes
cd nested_attributes

Já sabemos que vamos precisar de um modelo pessoa, cidade e contato, então vamos criá-los utilizando os generators do Rails:

script/generate model City name:string
script/generate scaffold Person name:string city:references
script/generate model Contact kind:string description:string person:references

Olhos mais atentos devem ter percebido que o modelo de pessoa foi gerado com scaffold: apenas para facilitar a nossa vida já criando o controller e as views necessárias para a manutenção.

Agora vamos criar e migrar nossa base de dados:

rake db:create
rake db:migrate

Vamos trabalhar inicialmente nos modelos, criando as validações e relacionamentos. Primeiro o modelo de cidades (app/models/city.rb) deve ter muitas pessoas, e deve validar a presença do nome.

class City < ActiveRecord::Base
  has_many :people

  validates_presence_of :name
end

Continuando, nosso modelo de pessoas (app/models/person.rb) deve requerer o nome, possuir muitos contatos e pertencer a uma cidade.

class Person < ActiveRecord::Base
  belongs_to :city
  has_many :contacts

  validates_presence_of :name
end

E por fim temos o modelo de contatos (app/models/contact.rb), que além de pertencer a uma pessoa deve validar a presença do tipo e da descrição.

class Contact < ActiveRecord::Base
  KINDS = %w(home_phone work_phone email url)
  belongs_to :person

  validates_presence_of :kind, :description
  validates_inclusion_of :kind, :in => KINDS
end

Também criamos aqui uma constante para facilitar o trabalho de gerenciar os tipos de contatos possíveis: telefone residencial, telefone comercial, email e url, respectivamente. Se você desejar mais algum tipo de contato é só adicionar nesta lista.

Neste ponto a estrutura de nossos modelos está pronta. Todas as validações e relacionamentos necessários foram criados. Podemos então partir para o que realmente nos interessa: atributos aninhados.

Atributos Aninhados: Os Modelos

Para que um modelo possa criar e salvar dados de modelos relacionados, devemos dar esta permissão a ele explicitamente através do comando accepts_nested_attributes_for. Este comando permite dois parâmetros adicionais: allow_destroy, que quando verdadeiro permitirá a exclusão de registros aninhados através de uma flag :_delete (veja mais abaixo); e reject_if, uma proc que deve validar se um registro aninhado será ou não criado. Vamos fazer isto no nosso modelo de pessoas, adicionando as seguintes linhas:

  accepts_nested_attributes_for :contacts, :allow_destroy => true,
                                :reject_if => proc { |contact| contact['description'].blank? }

  accepts_nested_attributes_for :city,
                                :reject_if => proc { |city| city['name'].blank? }

Para o modelo de contatos estamos permitindo excluir registros, e também ignoramos registros sem o campo descrição. Para a cidade a opção :allow_destroy não foi informada, pois não pretendemos permitir a exclusão de uma cidade a partir da pessoa. Também ignoramos a cidade se o nome estiver em branco.

Para fazer alguns testes com essas novas opções abra um console no terminal (script/console) e vá digitando os comandos abaixo e analisando os resultados:


# City
person = Person.new(:name => 'Carlos')
#<Person id: nil, name: "Carlos", city_id: nil, created_at: nil, updated_at: nil>
person.city_attributes = { :name => 'Rio do Sul' }
#{:name=>"Rio do Sul"}
person.city
#<City id: nil, name: "Rio do Sul", created_at: nil, updated_at: nil>
person.city.new_record?
#true
person.save
#true
person.city.new_record?
#false

# Contacts
person.contacts_attributes = { 'new_1' => {
   :kind => 'email', :description => 'teste@example.com' }}
#{"new_1"=>{:kind=>"email", :description=>"teste@example.com"}}
person.contacts_attributes = { 'new_2' => {
  :kind => 'email', :description => 'teste2@example.com' }}
#{"new_2"=>{:kind=>"email", :description=>"teste2@example.com"}}
person.contacts.size
#2
person.contacts
#[#<Contact id: nil, kind: "email", description: "teste@example.com", person_id: nil, created_at: nil, updated_at: nil>, #<Contact id: nil, kind: "email", description: "teste2@example.com", person_id: nil, created_at: nil, updated_at: nil>]
person.save
#true
person.contacts
#[#<Contact id: 5, kind: "email", description: "teste@example.com", person_id: 6,
#    created_at: "2009-03-28 00:36:49", updated_at: "2009-03-28 00:36:49">,
#   <Contact id: 6, kind: "email", description: "teste2@example.com", person_id: 6,
#    created_at: "2009-03-28 00:36:49", updated_at: "2009-03-28 00:36:49">]

# Deleting a contact
contact_id = person.contacts.first.id
#5
person.contacts_attributes = { contact_id.to_s => { :id => contact_id.to_s, :_delete => true } }
#{"5"=>{:id=>"5", :_delete=>true}}
person.contacts.first.marked_for_destruction?
#true
person.save
#true
person.reload.contacts.size
#1
person.contacts
#[#<Contact id: 6, kind: "email", description: "teste2@example.com", person_id: 6,
#    created_at: "2009-03-28 00:36:49", updated_at: "2009-03-28 00:36:49">]

Se desejar ver mais exemplos de utilização das estruturas aninhadas dê uma olhada nos testes do projeto no github.

Apenas ativar a opção accepts_nested_attributes_for no modelo nos dá uma série de funcionalidades “de graça”: o Rails agora irá salvar automaticamente os registros atribuídos através desta opção. Além disso, o modelo verifica se existe erros nos modelos filhos e carrega quaisquer mensagens de erros para ele, também para facilitar a visualização de forma amigável na view. E o melhor é que tudo acontece dentro de uma transação única, o que significa que se algum dos registros gerar um erro no momento de salvar ou de algum callback, toda a transação será cancelada.

Atributos Aninhados: As Views

Toda essa funcionalidade que o Active Record disponibiliza não seria de tanta ajuda se não tivéssemos facilitadores para gerenciar isso nos formulários. Para isso ganhamos novas funcionalidades dentro de views: tudo acontece utilizando o método fields_for, de uma maneira um pouquinho diferente de como já o utilizávamos.

A primeira modificação que precisamos fazer é criar um partial com o formulário para inclusão/edição de pessoas. Como geramos esse modelo com o scaffold, a estrutura html inicial já está pronta, então crie um partial com o nome _form.html.erb dentro de app/views/people/ com o código abaixo:

<% form_for(@person) do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <%= f.submit 'Submit' %>
  </p>
<% end %>

Percebam que removi a parte gerada para o campo city, pois trabalharemos nele mais tarde. Outra mudança apenas estética: o submit ganhou um caption mais “genérico”, ao invés de “Create” ou “Update” como é gerado nos templates new e edit.

Devemos agora alterar estes últimos dois para gerar nosso partial:

# new.html.erb
<h1>New person</h1>
<%= render :partial => 'form' %>
<%= link_to 'Back', people_path %>

# edit.html.erb
<h1>Editing person</h1>
<%= render :partial => 'form' %>
<%= link_to 'Show', @person %> |
<%= link_to 'Back', people_path %>

Vamos rodar nossa aplicação para vermos como está ficando. Primeiro remova o arquivo index.html que está dentro da pasta public. Depois, abra o arquivo config/routes.rb e modifique-o conforme abaixo:

ActionController::Routing::Routes.draw do |map|
  map.resources :people
  map.root :people
end

Isto configura o root de nossa aplicação para a listagem de pessoas. Agora vá para o terminal e rode o servidor (script/server), depois acesse no navegador http://localhost:3000 e deverá ver a listagem com as pessoas que cadastrou anteriormente. Acesse a tela de edição e de inclusão de nova pessoa para verificar se tudo correu bem com nosso partial.

Pronto. Com tudo funcionando podemos nos concentrar no gerenciamento de contatos para a pessoa no mesmo formulário.

Contatos

A criação de um formulário aninhado utiliza o método fields_for, sendo chamado a partir da instância do formulário que está sendo criado, ou formulário “pai”, e identificando qual o modelo aninhado que será gerado. Acho que ficou um pouco complicado de entender não é? Para simplificar vamos olhar como fica nosso partial _form.html.erb:

<% form_for(@person) do |f| %>
  ........
  <fieldset id="contacts">
    <legend>Contacts</legend>
    <% f.fields_for :contacts do |contacts_form| -%>
      <%= render :partial => 'contact', :locals => { :f => contacts_form }  %>
    <% end -%>
  </fieldset>
  <p>
    <%= f.submit 'Submit' %>
  </p>
<% end %>

Adicionamos um fieldset para agrupar os contatos, e utilizamos o método fields_for a partir da instância do form pessoa (f.fields_for), informando que estamos gerenciando os contatos (:contacts). Para cada contato da pessoa será gerado o partial _contact.html.erb, que iremos criar agora. Perceba também que estamos passando para o partial a instância do formulário atual de contato (contacts_form). Crie um partial chamado _contact.html.erb em app/views/people/ e coloque o seguinte código (lembre-se que a variável f aqui se refere a instância contacts_form):

<div class="contact">
  <p>
    <%= f.label :kind %>
    <%= f.select :kind, Contact::KINDS.map { |kind| [kind.humanize, kind] } %>
    <%= f.label :description %>
    <%= f.text_field :description %>
    <% unless f.object.new_record? -%>
      <%= f.check_box :_delete %><%= f.label :_delete, 'Delete' %>
    <% end -%>
  </p>
</div>

Criamos aqui um select com opções do tipo de contato que estamos criando, e um input para o usuário entrar com o telefone ou e-mail por exemplo, além de uma checkbox para excluir um contato se o mesmo não for um novo registro (f.object nos dá acesso ao objeto ActiveRecord do formulário que está sendo processado, no nosso caso um contato). Isso já facilita bastante a manutenção: para excluir um contato é só marcar a checkbox e enviar o formulário, se tudo correr bem o contato deixará de existir.

Para testar essa funcionalidade crie algumas pessoas e contatos no console e abra a tela de edição: você verá os contatos no mesmo formulário para alterá-los, excluí-los, etc. Para permitir a inclusão de mais contatos nessa mesma tela, altere os métodos new e edit do controller e adicione a seguinte linha para criar alguns contatos em memória, gerando assim alguns formulários de contatos:

3.times { @person.contacts.build }

Até aqui tudo bem. Contudo esbarramos num pequeno problema: a melhor maneira de utilizar essa funcionalidade é permitir que o usuário possa criar quantos contatos ele queira, sem ter que incomodar o servidor a cada momento apenas para gerar uma nova linha no formulário. Da maneira que programamos até aqui, o usuário poderá criar apenas 3 contatos por vez, ou quantos nós decidirmos que seja ideal. Para conseguir isto de forma dinâmica temos que utilizar um pouco de javascript e alguns helpers.

Primeiro temos que modificar o nosso layout people.html.erb em app/views/layouts para que carregue os javascripts padrão, e também vamos adicionar um método para poder escrever algum código javascript manualmente a partir dos templates que usam esse layout. Localize a linha que adiciona o stylesheet do scaffold e coloque o seguinte código acima dela:

  <%= javascript_include_tag :defaults %>
  <% javascript_tag do %>
    <%= yield(:javascript) %>
  <% end -%>

Vamos adicionar um link para criar novos contatos, e teremos que programar este link para gerar um novo formulário com javascript.

  <fieldset id="contacts">
    ......
    <p><%= link_to 'Add contact', '#contacts', :class => 'add' %></p>
  </fieldset>

Antes de fazermos com que o o link adicione o formulário, vamos apenas programá-lo para adicionar algum texto no local onde o mesmo deveria ser posicionado: desta forma só precisamos gerar um novo formulário e colocá-lo no lugar do texto, e tudo estará funcionando. A classe 'add' será usada para identificarmos o link e manipularmos a função click dele. O segundo parâmetro do link_to, que seria o caminho para onde o link apontaria, vai ser utilizado mais tarde para identificar o template correto a ser renderizado (por enquanto vamos renderizar apenas um texto, ok?). Abra o application.js e adicione o seguinte código nele:

var NestedAttributesJs = {
  add : function(e) {
    element = Event.findElement(e);
    element.insert( { before: 'AQUI VAI O NOSSO FORMULARIO<br/>' } )
  }
}

Event.observe(window, 'load', function(){
  $$('.add').each(function(link){
    link.observe('click', NestedAttributesJs.add);
  });
});

O que acontece aqui: após todo o DOM da página ser carregado, usamos o método $$('.add') para encontrar todos os elementos com a classe add (lembram do nosso link?), percorrendo então o array de elementos encontrados (apenas um por enquanto) com o método each e adicionando um observador para o evento click. Em resumo, quando cada elemento com essa classe for clicado, irá disparar a função NestedAttributesJs.add. Dentro desta função buscamos o elemento atual passado como parâmetro (nesse caso o nosso link que está sendo clicado) e inserimos antes dele um texto e uma quebra de linha html. É exatamente no lugar deste texto que vamos renderizar nosso formulário, mas para fazermos isto temos que ter um template do formulário já gerado em alguma variável javascript, para apenas mudar o nosso texto temporário por esta variável. Vamos criar alguns helpers para nos ajudar nesta tarefa, mas antes faça alguns testes no seu navegador clicando no link e vendo onde o texto está sendo inserido. Após, abra o PeopleHelper e adicione o seguinte código nele:

  def nested_attributes_for(form_builder, *args)
    content_for :javascript do
      content = ""
      args.each do |association|
        content << "\nvar #{association}_template='#{generate_template(form_builder, association.to_sym)}';"
      end
      content
    end
  end

  def generate_html(form_builder, method, options = {})
    options[:object] ||= form_builder.object.class.reflect_on_association(method).klass.new
    options[:partial] ||= method.to_s.singularize
    options[:form_builder_local] ||= :f

    form_builder.fields_for(method, options[:object], :child_index => 'NEW_RECORD') do |f|
      render(:partial => options[:partial], :locals => { options[:form_builder_local] => f })
    end
  end

  def generate_template(form_builder, method, options = {})
    escape_javascript generate_html(form_builder, method, options = {})
  end

São três métodos para estudarmos aqui:

  • generate_html: cria um novo objeto da classe que estamos gerando (o mesmo que Contact.new no nosso caso), e o partial para esse novo objeto utilizando o fields_for. O resultado é um partial vazio para incluir um registro aninhado. Um pouco de atenção ao parâmetro :child_index => 'NEW_RECORD': ele será bastante importante. Este parâmetro diz ao fields_for para utilizar a string NEW_RECORD como índice do registro ao gerar este formulário, ao invés de utilizar um contador, como estávamos fazendo manualmente ao criar novos contatos aninhados ('new_1', 'new_2'). Se tiver dúvidas quanto a esta parte, volte um pouco até onde falamos sobre os testes no console e verifique como estávamos criando novos contatos;
  • generate_template: dispara o método generate_html, escapando caracteres javascript (lembre-se: tudo isto irá parar em uma variável javascript);
  • nested_attributes_for: cria uma ou mais variáveis javascript, cada uma contendo o respectivo template gerado para formulário, jogando o conteúdo no cabeçalho do documento (adicionamos esta possibilidade anteriormente com yield(:javascript) no layout).

Com estes novos métodos helpers podemos gerar agora nossa variável javascript contendo o formulário vazio para a criação de novos contatos. Vale lembrar que estes métodos funcionarão posteriormente em qualquer formulário aninhado que você precise, seguindo as mesmas convenções de nomes que seguimos aqui. Adicione ao partial _form.html.erb logo abaixo do form_for:

  <% nested_attributes_for f, :contacts -%>

Ao atualizar a página agora e visualizar o código fonte, poderá ver que no cabeçalho será criada uma variável javascript chamada contacts_template que possui o html necessário para gerar o novo formulário. Aproveite para analisar também como a string NEW_RECORD foi posicionada dentro do nosso template gerado.

  <script type="text/javascript">
//<![CDATA[
var contacts_template='<div class=\"contact\">\n  <p>\n    <label for=\"person_contacts_attributes_NEW_RECORD_kind\">Kind<\/label>\n    <select id=\"person_contacts_attributes_NEW_RECORD_kind\" name=\"person[contacts_attributes][NEW_RECORD][kind]\"><option value=\"home_phone\">Home phone<\/option>\n<option value=\"work_phone\">Work phone<\/option>\n<option value=\"email\">Email<\/option>\n<option value=\"url\">Url<\/option><\/select>\n    <label for=\"person_contacts_attributes_NEW_RECORD_description\">Description<\/label>\n    <input id=\"person_contacts_attributes_NEW_RECORD_description\" name=\"person[contacts_attributes][NEW_RECORD][description]\" size=\"30\" type=\"text\" />\n\n      <\/p>\n<\/div>\n\n';
//]]>
</script>

Vamos alterar agora nosso javascript para utilizar o template. Você se lembra quando criamos o link para adicionar um novo contato, passamos o parâmetro #contacts como se fosse o caminho para o link? Vamos utilizar a propriedade href do link, que possui esse texto, remover o # e obter nosso template que se chama contacts_template. Por isso a importância da convenção =). Substitua o código do application.js pelo abaixo:

var NestedAttributesJs = {
  add : function(e) {
    element = Event.findElement(e);
    template = eval(element.href.replace(/.*#/, '') + '_template');
    element.insert( { before: NestedAttributesJs.replace_ids(template) } );
  },
  replace_ids : function(template){
    var new_id = new Date().getTime();
    return template.replace(/NEW_RECORD/g, new_id);
  }
}

Event.observe(window, 'load', function(){
  $$('.add').each(function(link){
    link.observe('click', NestedAttributesJs.add);
  });
});

O que mudou: dentro do método NestedAttributesJs.add estamos atribuindo à variável template o conteúdo da nossa variável contacts_template através do método eval. Agora um ponto importante: adicionamos um método chamado replace_ids, que substitui no nosso template todas as ocorrências de NEW_RECORD pelo número de milisegundos desde a meia-noite de primeiro de janeiro de 1970, gerados pela função Date().getTime(). A idéia é que seja sempre gerado um número novo a cada registro adicionado ao formulário. Se você verificar o código gerado através da extensão Web Developer do Firefox, verá que ao invés de person[contacts_attributes][NEW_RECORD] temos algo como person[contacts_attributes][1238198708416] nos campos do novo formulário. Desta forma o Rails saberá identificar que este é um novo registro. Caso isto não seja feito, se você criar 3 contatos no seu formulário e enviá-lo para o servidor, o Rails receberá três contatos com NEW_RECORD e salvará apenas um deles.

Bom, neste ponto já devemos ter nossa estrutura funcionando para adicionar e remover contatos aninhados. Lembre-se que estamos ignorando contatos sem descrição, portanto você pode criar 10 contatos no formulário sem preencher sua descrição, que ao postar o Rails ignorará esses contatos.

Uma outra funcionalidade interessante seria a possibilidade de excluir um contato adicionado, mas não temos como fazer isto através de checkboxes pois o formulário está sendo criado dinamicante. Vamos criar um link para remover o html gerado para o novo registro. Para facilitar vamos mover o código que cria ou um link ou uma checkbox para remoção do registro aninhado, se o registro for ou não novo, permitindo assim reutilizar este código em outros templates posteriormente. Adicione ao PeopleHelper:

  def remove_link_unless_new_record(form_builder)
    unless form_builder.object.new_record?
      form_builder.check_box(:_delete) + form_builder.label(:_delete, 'Delete')
    else
      link_to_function('Delete', "$(this).up('.#{form_builder.object.class.name.underscore}').remove();")
    end
  end

E altere o partial _contact.html.erb conforme abaixo:

    # remova estas linhas
    #<% unless f.object.new_record? -%>
    #  <%= f.check_box :_delete %><%= f.label :_delete, 'Delete' %>
    #<% end -%>

    # e adicione esta no lugar
    <%= remove_link_unless_new_record(f) %>

Isto já deve ser suficiente para utilizarmos este cadastro incluindo e excluindo contatos dinamicamente. Podemos partir para a seleção de uma cidade para a pessoa.

Cidade

Quando trabalhamos com este tipo de relação entre pessoa e cidade, normalmente criamos um select com as cidades disponíveis para que o usuário selecione qual a cidade que a pessoa pertence. Muitas vezes gostaríamos de permitir que uma cidade possa ser criada junto com a pessoa, caso ela não exista na lista. E o Rails agora permite fazer isto de maneira simples utilizando atributos aninhados. Para que isto funcione precisamos apenas fazer uma alteração na view, adicionando abaixo do campo nome:

  <p>
    <%= f.label :city_id %>
    <%= f.collection_select :city_id, City.all(:order => 'name'), :id, :name %>
    or
    <% f.fields_for :city, City.new do |city_form| -%>
      <%= city_form.label :name, 'Create a new' %><%= city_form.text_field :name %>
    <% end -%>
  </p>

O código acima cria um select com as cidades disponíveis ordenadas por nome (ok eu sei, a chamada ao City.all(:order => 'name') não deveria estar aqui, mas estou apenas simplificando pois o post já está bastante extenso), e também um input para a criação de uma nova cidade. Desta forma, o usuário tem a possibilidade de ou selecionar uma cidade pelo select ou entrar com o nome de uma nova cidade.

Para terminar

Ufa! Foi um pouco extensivo mas é isso aí. Após um pouco de trabalho temos uma manutenção totalmente funcional que trabalha com atributos aninhados.

O projeto que criamos aqui está disponível no github.

Espero ter ajudado a quem estava com dúvidas sobre a utilização desta nova funcionalidade.

Qualquer dúvida, dificuldade ou crítica por favor poste um comentário que terei prazer em responder assim que possível.

Escrito por Carlos

Março 28, 2009 às 8:55 pm

Publicado em Ruby on Rails

Etiquetado com , ,

12 Respostas

Subscreva aos comentários comRSS.

  1. [...] blog do Carlos Antonio: Uma nova funcionalidade adicionada ao Rails 2.3 é a possibilidade de atualizar os atributos em [...]

  2. Cara, gostei desse helper, dá pra fazer bastante coisa com esses métodos.

    Acho que a forma de reaproveitar isso é colocar no application_helper.

    No geral, achei que você falou muita coisa pra pouca coisa.

    Esse Javascript aí escrito em jquery ficou bem menor e mais fácil de ser entendido :)

    Daniel Koch

    Abril 8, 2009 em 11:12 pm

  3. Olá Daniel,

    sim, concordo que a melhor opção seria mover os helpers para o application_helper, para que ficassem disponíveis em outros locais, valeu pela dica.

    Quando ao post, o objetivo era mais para ser uma espécie de ‘tutorial’, e realmente ficou bastante extenso, tentei explicar mais a fundo alguns detalhes do que acontecia ao criar essa estrutura, me baseando no trabalho do Eloy. Mas é isso mesmo, vou tentar melhorar na próxima. Obrigado.

    Ah, também gosto do jquery =)..

    Abraços

    Carlos

    Abril 9, 2009 em 1:21 am

  4. Olá,
    ótimo tutorial. Parabéns!

    Tenho uma dúvida: existe verificação de existência de contato ao criar um novo registro. Mas se, ao alterar um contato, eu deixá-lo em branco, o sistema o salvará em branco mesmo, certo?

    Existe algo a incluir no model que verifique isso, assim como na criação? Ou é necessário verificar no update se ele foi enviado em branco e destrui-lo?

    Obrigado.
    Abraços

    Habib

    Maio 19, 2009 em 2:39 pm

    • Olá Habib,

      ao alterar um contato, se você deixar os dados em branco serão retornados os erros dizendo que este contato está inválido. O Rails verifica a existência do :id no hash de campos (que é incluído automaticamente como hidden no seu form), dessa forma ele sabe que você está editando um registro e não incluindo um novo. Assim, deixando os campos em branco não passará na validação, nem no seu método :reject_if.

      Para excluir um registro você tem que marcar o atributo :_delete para verdadeiro (no caso usamos um checkbox para marcar um registro a ser excluído).

      Se você quiser verificar se um registro está marcado para ser excluído, use o método marked_for_destruction?.

      Obrigado =).
      Abraços

      Carlos

      Maio 19, 2009 em 4:41 pm

      • Olá Carlos,

        obrigado pelo esclarecimento. Gostaria de tirar mais uma dúvida: é possível aplicar a mesma técnica, mas com modelos que possuem uma relação many-to-many (utilizando has_many, :through)?

        Tentei seguir os mesmos passos, mas o formulário renderizado utilizando o fields_for não inclui campos hidden para os ids das duas tabelas, apenas uma delas. Assim, sempre que se edita um campo, o Rails cria uma nova entrada (por falta do outro id).

        Obrigado novamente.
        Abraços,
        Habib

        Habib

        Maio 20, 2009 em 6:06 pm

  5. Hi

    I just found this tutorial after searching for a while. It looks quite comprehensive and complete – it looks like you did a great job. . I haven’t had a chance to review it completely – although I did download and run it, and thought in advance of doing so – I’d ask a quick question.

    My question is do you have any advice on how to code a single form capable of managing 3 one-to-many relationships. For example …

    Quiz -> Questions -> Answers

    A Quiz has_many Questions
    A Question has_many Answers

    This form would have all the dynamic capabilities shown in your sample.

    Thanks
    Dave

    Dave

    Julho 14, 2009 em 5:45 am

    • Hello Dave,

      To create a form capable of managing complex relationships like this you can follow the same pattern of this example, it’ll work like a charm.

      I also recommend you to take a look on complex form examples from Elloy Duran, which is also linked on my post. He has the exact example you need, with 2 levels nesting.
      http://github.com/alloy/complex-form-examples/tree/master

      Any doubts please comment or send me an email.
      Thanks,
      Carlos.

      Carlos

      Julho 14, 2009 em 11:36 am

      • Hi Carlos

        I tried recreating a 3 model form by modifying the Elloy code – but what I ended up with was a form with a link for adding the 3rd model (Answers in my case) which doesn’t work. I click on it and nothing happens.

        I tried to create my app using the methods described in your post above (by the way, I liked your method as it appears a bit cleaner). I was able to create an app that easily supported Quiz -> Questions – but when I tried to add the Questions -> Answer, it wasn’t clear to me exactly where I should add this statement …

        and furthermore – which form ‘f’ should reference.

        Perhaps you can help me resolve this issue ?

        I wasn’t able to find your email, and I’d prefer to continue this conversation via email. My email is dekhaus@(mac dot com).

        Thanks
        Dave

        Dave

        Julho 17, 2009 em 8:24 pm

  6. olá pessoal

    sou novo no rails, e tenho uma duvida muito louco, eu sou programador PHP, mas não entendo isso.

    Veja bem, você criou o modelo Person e como o rails sabe quando você coloca has_many :people ????

    o rails sabe o que é person, mas people, como ele sabe?

    Elias Farah

    Outubro 2, 2009 em 8:47 pm

    • Olá Elias tudo bem?

      Acontece o seguinte: o Rails tem um módulo chamado Inflector onde são definidas expressões para que ele saiba que ‘person’ no singular significa ‘people’ no plural. Da mesma forma para palavras comuns ele sabe que apenas adicionar um ’s’ já basta.. Essas regras são definidas usando expressões regulares, e ainda é possível adicionar quantas regras você quiser.

      Se você observar ao criar uma nova aplicação rails, dentro do diretório config/initializers existe um arquivo chamado inflections.rb onde você pode criar suas regras personalizadas e ver alguns exemplos (dá uma olhada, o ‘person’ > ‘people’ está lá).

      Se tiver mais alguma dúvida basta postar um novo comentário ok.
      Um abraço.
      Carlos

      Carlos

      Outubro 7, 2009 em 11:39 pm

  7. Habib,

    não tenho conhecimento sobre a manutenção de um has_many :through através de nested attributes, não sei como o Rails se comportaria.

    Vou procurar fazer alguns testes aqui e posto algo a respeito..
    Caso você tenha alguma novidade ficaria grato se postasse aqui nos comentários.

    Obrigado e abraços.

    Carlos

    Maio 20, 2009 em 7:40 pm


Deixe uma resposta