
Cocoon: Crie facilmente Forms dinâmicos no Rails
Muitas vezes precisamos em um único formulário salvar registros em diferentes tabelas do nosso banco de dados (exp: criar um usuário com múltiplos telefones) e dentro do Rails podemos fazer isso usando os Nested Attributes.
Porém, quando queremos deixar o usuário escolher quantos registros ele vai querer gravar (exp: Um usuário que deseja salvar 3 telefones) nós precismos deixar a tela dinâmica (permitindo que o usuário adicione mais campos de telefone) e ai é que a gem Cocoon vem nos ajudar.
Essa gem facilita a criação de formulários dinâmicos via jQuery no Rails, ou seja, ao invés de precisarmos criar um arquivo javascript para incluir esses campos extras ela faz isso por nós com maestria.
Então neste post nós iremos entender a estrutura da gem Cocoon e veremos o quanto ela pode agilizar nosso desenvolvimento de formulários.
Ferramentas
- ruby 2.4.2
- rails 5.1
- cocoon (gem)
- yarn
O que vamos criar?
Nesse exemplo iremos criar um registro de usuários e suas experiências profissionais, onde você poderá inserir quantas experiências profissionais desejar para um usuário através do uso do Cocoon \o/
Prepara o ambiente ai para codar e vem com a gente nessa jornada \o/
Criando nosso App de exemplo
Criando o projeto e suas configurações iniciais
1- Primeiro vamos gerar nosso projeto, no seu terminal rode o comando:
1 |
rails _5.1.4_ new users_register |
2 – Entre no projeto:
1 |
cd users_register |
3- Vamos criar nosso banco de dados, rode:
1 |
rails db:create |
4- Agora vamos criar nosso scaffold de user e nosso model para salvar as experiências profissionais de cada User
a- Rode no seu terminal:
1 |
rails g scaffold User name |
b- Rode no seu terminal:
1 |
rails g model Experience company period occupation user:references |
4- Rode as migrations
1 |
rails db:migrate |
Instalando as dependências e o cocoon
1- Para instalar o jquery, vamos usar o yarn, rode no seu terminal:
1 |
yarn add jquery |
PS: Se você ainda não tem o yarn instalado, abra esse link e instale:
https://yarnpkg.com/lang/pt-br/docs/install/
2- No seu gemfile adicione a linha:
1 |
gem "cocoon" |
3- Rode no console:
1 |
bundle install |
4- Atualize seu app/assets/javascripts/application.js colocando:
1 2 3 4 5 |
//= require jquery //= require cocoon //= require rails-ujs //= require turbolinks //= require_tree . |
Criando nosso app \o/
Obs: Deixei comentários nos códigos e detalhes a baixo deles para você entender cada ponto da implementação, leia com atenção.
1- No seu Controller users_controller.rb substitua o conteúdo por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
class UsersController < ApplicationController before_action :set_user, only: [:show, :edit, :update, :destroy] def index @users = User.all end def show end def new @user = User.new # COMENTÁRIO: O build inicializa a página com 1 nested já renderizado @experience = @user.experiences.build end def edit end def create @user = User.new(user_params) respond_to do |format| if @user.save format.html { redirect_to @user, notice: 'User was successfully created.' } format.json { render :show, status: :created, location: @user } else format.html { render :new } format.json { render json: @user.errors, status: :unprocessable_entity } end end end def update respond_to do |format| if @user.update(user_params) format.html { redirect_to @user, notice: 'User was successfully updated.' } format.json { render :show, status: :ok, location: @user } else format.html { render :edit } format.json { render json: @user.errors, status: :unprocessable_entity } end end end def destroy @user.destroy respond_to do |format| format.html { redirect_to users_url, notice: 'User was successfully destroyed.' } format.json { head :no_content } end end private def set_user @user = User.find(params[:id]) end def user_params # COMENTÁRIO: Aqui estamos permitindo a passagem dos parâmetros do nested attribute "experiences" params.require(:user).permit(:name, experiences_attributes: [:id, :company, :period, :occupation, :_destroy]) end end |
Obs: Dessa forma estamos autorizando a request a aceitar parametros de experiences, o param _destroy é para que ele permita deletar o nested quando clicarmos no link de apagar uma experiência.
2- Substitua o conteúdo do seu model User para:
1 2 3 4 5 6 7 |
class User < ApplicationRecord has_many :experiences, inverse_of: :user, dependent: :destroy # COMENTÁRIOS: Aqui declaramos a associação entre os models e adicionamos o dependent: :destroy para eliminar as experiencias caso o usuário seja deletado accepts_nested_attributes_for :experiences, reject_if: :all_blank, allow_destroy: true # COMENTÁRIO: O reject_if: :all blank faz com que não seja salvo um record caso o usuário tente adicionar uma experiência com todos os campos vazios end |
3- Para incluir a adição dinâmica das experiencias vamos substituir o conteúdo de app/views/users/_form.html.erb por (atenção aos comentários no código 🙂 ):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<%= form_with(model: user, local: true) do |form| %> <% if user.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2> <ul> <% user.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= form.label :name %> <%= form.text_field :name, id: :user_name %> </div> <h4>Experiences</h4> <!-- COMENTÁRIO: O cocoon exige que seu nested_form esteja dentro de uma div com id "nome_do_seu_nested_no_plural" no nosso caso #experiences --> <div id="experiences"> <%= form.fields_for :experiences do |experience| %> <!-- COMENTÁRIO: é uma exigencia também que os campos do seu nested esteja numa partial "nome_do_seu_nested_no_singular_fields" no nosso caso _experience_fields.html.erb --> <%= render 'experience_fields', f: experience %> <% end %> <!-- COMENTÁRIO: O link para adicionar dinamicamente os fields deve estar dentro de uma div .links --> <div class="links"> <!-- COMENTÁRIO: O link_to_add_association é um helper provido pela gem --> <%= link_to_add_association 'add experience', form, :experiences %> <br><br> </div> </div> <div class="actions"> <%= form.submit %> </div> <% end %> |
4- Agora crie uma partial no seguinte patch /app/views/users/_experience_fields.html.erb e insira o código a baixo nela.
Obs: Essa partial será carregada pela gem cocoon quando o usuário quiser adicionar mais campos de experiencia:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<!-- COMENTÁRIO: Dentro da partial todo conteúdo deve estar dentro de uma div .nested-fields --> <div class="nested-fields"> <div class="field"> <%= f.label :company %> <%= f.text_field :company %> </div> <div class="field"> <%= f.label :occupation %> <%= f.text_field :occupation %> </div> <div class="field"> <%= f.label :period %> <%= f.text_field :period %> </div> <!-- COMENTÁRIO: link_to_remove_association é o helper que o cocoon nos fornece para deletar algum nested --> <%= link_to_remove_association "remove experience", f %> <br><br><br> </div> |
5 – Pronto \o/, agora para complementar vamos incluir alguns callbacks.
Usando os callbacks do cocoon
Para customizar ainda mais o comportamento dos nosso forms nós podemos usar alguns callbacks com o cocoon, esses callbacks são funções javascript que rodam após um evento (antes da inserção da partial, após a inserção e etc). Então bora:
1- Crie um arquivo app/assets/javascripts/cocoon_callbacks.coffee e insira o seguinte conteúdo:
1 2 3 4 5 6 7 8 9 10 11 |
jQuery(document).on 'turbolinks:load', -> experiences = $('#experiences') experiences.on 'cocoon:after-insert', (e, added_el) -> added_el.find("input").first().focus(); # COMENTÁRIO: Coloca o foco do cursor no primeiro input do novo objeto experiences.on 'cocoon:before-remove', (e, el_to_remove) -> $(this).data('remove-timeout', 1000) el_to_remove.fadeOut(1000) # COMENTÁRIO: Cria pequena animação ao apagar uma experiencia |
Nesse exemplo usamos os callbacks after-insert e o before-remove para criar pequenas animações na nossa tela, ainda existem os métodos before-insert e o método after-remove que você também pode utilizar. =)
2 – Agora que nós criamos nosso callback já podemos ver o resultado no browser, para subir o servidor rode:
1 |
rails s |
3 – Visite http://localhost:3000/users para ver como ficou seu form.
Bônus – Melhorando nosso layout com Materialize
1- No seu terminal rode o comando:
1 |
yarn add materialize-css |
2- No seu arquivo app/assets/stylesheets/application.css adicione:
1 |
*= require materialize-css/dist/css/materialize |
3- Atualize o conteúdo do seu arquivo app/assets/stylesheets/users.scss para:
1 2 3 4 5 6 7 8 9 10 11 |
.container { width: 70% !important; } .content { padding: 30px 100px 10px 100px; } input { align-items: center; } |
4- Atualize o código do seu arquivo app/views/users/_form.html.erb pelo código a baixo:
Obs: Preste bastante atenção nas classes css que usamos para entender as alterações de layout feitas, caso tenha alguma dúvida você pode ler sobre todas mais detalhadamente na documentação do materialize
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
<div class="row"> <div class="col m12"> <%= form_with(model: user, local: true) do |form| %> <% if user.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2> <ul> <% user.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= form.label :name %> <%= form.text_field :name, id: :user_name %> </div> <h4>Experiences</h4> <!-- COMENTÁRIO: O cocoon exige que seu nested_form esteja dentro de uma div com id "nome_do_seu_nested_no_plural" no nosso caso #experiences --> <div id="experiences"> <%= form.fields_for :experiences do |experience| %> <!-- COMENTÁRIO: É uma exigência também que os campos do seu nested esteja numa partial "nome_do_seu_nested_no_singular_fields" no nosso caso _experience_fields.html.erb --> <%= render 'experience_fields', f: experience %> <% end %> <!-- COMENTÁRIO: O link para adicionar dinamicamente os fields deve estar dentro de uma div .links --> <div class="links"> <!-- O link_to_add_association é um helper provido pela gem --> <%= link_to_add_association "+experience", form, :experiences, class: "btn waves-effect waves-light blue right white-text" %> <br><br> </div> </div> <div class="actions"> <%= form.submit 'Create User', class: 'btn btn-primary grey' %> </div> <hr> <% end %> </div> </div> |
5- Atualize seu arquivo views/layouts/application.html.erb para:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<!DOCTYPE html> <html> <head> <title>UsersRegister</title> <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <body> <div class="container"> <div class="card"> <div class="card-content"> <div class="row"> <div class="col m10 offset-m1"> <%= yield %> </div> </div> </div> </div> </div> </body> </html> |
6 – Pronto, nosso app está funcional, e com um layout apresentável, dá uma olhada como ficou rodando:
1 |
rails s |
7 – Visite http://localhost:3000/users para ver como ficou seu form.
Fixando o que você aprendeu
Para você exercitar os conhecimentos que adquiriu nesse post eu separei dois desafios pra você:
- Use o conhecimento aprendido para implementar formulários dinâmicos para o usuário adicionar telefones.
- Para praticar suas habilidades no front-end, atualize a view da action show para mostrar todos os dados referentes a um usuário(experiences e phones).
Depois que finalizar os desafios poste nos comentários o link para o repositório no Github onde seu projeto está para compararmos as diferentes implementações \o/

Não perca nenhum conteúdo
Receba nosso resumo semanal com os novos posts, cursos, talks e vagas o/
Conclusão
Aprendemos como implementar forms dinâmicos facilmente usando o cocoon, como usar callbacks para reagir as mudanças no form e como customizar tudo isso para deixar seu form ainda mais incrível.
Se você gostou desse conteúdo compartilha com outros programadores que assim como você estão buscando desenvolver suas habilidades, será de grande ajuda \o/
Caso você tenha se interessado pelo Yarn durante esse tutorial e ainda não conhece a fundo ele, veja esse nosso post sobre ele: Instalando pacotes no Rails com Yarn (Jquery, Bootstrap e Materialize)
Até breve 🙂
Você é novo por aqui?
Primeira vez no OneBitCode? Curtiu esse conteúdo?
O OneBitCode tem muito mais para você!
O OneBitCode traz conteúdos de qualidade e em português sobre programação com foco em Ruby on Rails e outras tecnologias como Angular, Ionic, React, desenvolvimento de Chatbots e etc.
Se você deseja aprender mais, de uma forma natural e dentro de uma comunidade ativa, visite nosso Facebook e nosso Twitter, veja os screencasts e talks no Youtube, alguns acontecimentos no Instagram, ouça os free dating sites lou ky e faça parte de nossa Newsletter.
Além disso, também estamos com alguns e-Books muito interessantes para quem deseja se aprimorar como programador e também como freelancer (os e-Books são gratuitos!):
- los angeles dating by smell
- Desenvolvendo seus projetos como um profissional
- chelsea handler dating 5o cent
- PDF com links fundamentais para quem quer ser um freelancer de sucesso
- speed dating in fort worth texas
- Baixe gratuitamente seu e-Book com 60 Gems separadas por categorias
Espero que curta nossos conteúdos e sempre que precisar de ajuda com os tutoriais, fala com a gente!
Seja por Facebook ou e-mail, estamos aqui para você 🙂
Bem-vindo à família OneBitCode \o/
Feito: https://github.com/tenorio/rails5_cocoon_gem_example
Boaaaa \o/
Como eu faço caso meu User seja gerado pelo Devise ?
Fitflops Sale Clearance UK
Thank you for another excellent post. Where else could anyone get that type of info in such an ideal way of writing? I have a presentation next week, and I am on the look for such information.
Não acontece nada quando clico em “link_to_add_association” ou “link_to_remove_association”. Tem ideia do que pode ser?
No rails 5.2.2 . Está com esse erro
ActiveRecord Inverse Of Association Not Found Error in
Talvez você tenha esquecido de instalar o jquery e add no application.js
Alguém poderia ajudar como resolver o problema no Rails 5.2.2… Não acontece nada quando clica em “link_to_add_association” ou “link_to_remove_association”. Tem ideia do que pode ser?
Google
Here are some of the web-sites we advocate for our visitors.
우리카지노
[…]below you will locate the link to some web pages that we think you’ll want to visit[…]
코인카지노
[…]that is the end of this report. Right here you will find some sites that we feel youll value, just click the hyperlinks over[…]
예스카지노
[…]very few web sites that occur to be detailed below, from our point of view are undoubtedly well worth checking out[…]
cheap last minute flights
[…]Here are a few of the web sites we suggest for our visitors[…]
sex toys
[…]Here is a great Blog You might Discover Exciting that we Encourage You[…]
PORN VIDEO
[…]Every when inside a even though we decide on blogs that we read. Listed beneath would be the newest websites that we choose […]
web designing
[…]Here is a superb Blog You may Discover Exciting that we Encourage You[…]
cozaar
[…]below you will find the link to some web pages that we feel you’ll want to visit[…]
remote dildo
[…]just beneath, are several totally not associated web sites to ours, nonetheless, they may be certainly really worth going over[…]
cbd
[…]Sites of interest we’ve a link to[…]
bank / bankowa
[…]Sites of interest we have a link to[…]
clitoris vibrator
[…]always a massive fan of linking to bloggers that I really like but dont get lots of link adore from[…]
fingo nubby
[…]The information and facts mentioned within the post are several of the most effective accessible […]
orange air freshener
[…]Wonderful story, reckoned we could combine a few unrelated information, nevertheless seriously worth taking a look, whoa did one particular learn about Mid East has got extra problerms as well […]
https://audiomack.com/artist/jerolewi5sd
[…]here are some links to web pages that we link to because we think they are worth visiting[…]
first timers strap on set
[…]below you will find the link to some sites that we feel you ought to visit[…]
digital marketing agency hk
[…]although web sites we backlink to below are considerably not connected to ours, we really feel they are in fact worth a go by, so have a look[…]