DEV Community

Rutvik Patel
Rutvik Patel

Posted on • Originally published at rutikkpatel.Medium

Cocoon Gem in Ruby on Rails 7

Cocoon Gem in Ruby on Rails 7

In this article, we are going to learn how to create a complex form or nested form, and add a dynamic field for any field in our application using the cocoon gem in **Rails 7. **Complex or Nested forms which mean we will handle nested models and attributes in one single form.

So, let’s get begin

Created By [Author](https://medium.com/@rutikkpatel) ([Rutik Patel](https://medium.com/@rutikkpatel))

 
→ Rails ~> 7.0.4 and Ruby ~> 3.1.0 are utilized for this application.

 

Points to be discussed

  1. What is Cocoon Gem?

  2. Implementation of the cocoon in rails 7

  3. Preview

  4. References

 

1) What is Cocoon Gem?

As we discussed Essentially, this gem is used for nested forms, multiple models, and many field attributes in a single form. For example, we have an address field, so any person can have multiple addresses; hence, storing that data and rendering that data in the cocoon gem is useful.

Cocoon makes it easier to handle nested forms. — Cocoon Github

 

2) Implementation of the cocoon in rails 7

We are going to add the cocoon gem to our employee CRUD application.

Recently, we developed Employee CRUD in Rails 7 with Bootstrap styling.
Thus, using the cocoon gem, we will add the “Address Field” to that CRUD and make it dynamic.

Here is a reference to my previously published blog for creating CRUD in Rails 7.
CRUD in Rails 7

 
So let’s begin by adding the cocoon gem to the gemfile. With the cocoon gem, we also need to add the jquery-rails gem as well, so just add these two gems in your Gemfile

gem 'jquery-rails'
gem 'cocoon'
Enter fullscreen mode Exit fullscreen mode

Then, Run bundle install for installing these gems.

 
Now add jquery.js to **assets.rb **file for precompiling.

Rails.application.config.assets.precompile += %w( jquery.js )
Enter fullscreen mode Exit fullscreen mode

After doing this just Remember to run rails assets:precompile

 
Let's include jquery in the application.html.erb

<%= javascript_include_tag "jquery" %>
Enter fullscreen mode Exit fullscreen mode

→ *When the main page is loaded, this line includes jQuery first in our application.

This entire setup is for jQuery only.

 
Let’s use importmap (a new feature in Rails 7) to add the cocoon gem:

bin/importmap pin @nathanvda/cocoon
Enter fullscreen mode Exit fullscreen mode

→ “@nathanvda/cocoon” will be pinned in the importmap.rb file.

 
Finally, for the cocoon gem, instead of importing cocoon, you need to import “@nathanvda/cocoon” in application.js.

Here is the syntax for that.

import "@nathanvda/cocoon"
Enter fullscreen mode Exit fullscreen mode

Now that we’ve finished with Cocoon and jQuery, let’s add the code for dynamic nested fields.


As I mentioned about employee CRUD, in our CRUD we have these fields, as you can see in the below snapshot.

schema.rb of E[mployee CRUD](https://rutikkpatel.medium.com/crud-in-rails-7-by-rutik-patel-a2bbc942d069)

 
As we want to add an address field, we have to generate an address model with house_number, society_name, area, city fields. So for that, fire this command.

rails g model Address house_number:integer society_name area city
Enter fullscreen mode Exit fullscreen mode

Created and invoked file after generating model

This will create and invoke some files inside that we need to work with the model file and one migration file only. And in the migration file, there’s some code for creating an Address table with the given fields.

 

migration file:

class CreateAddresses < ActiveRecord::Migration[7.0]
  def change
    create_table :addresses do |t|
      t.integer :house_number
      t.string :society_name
      t.string :area
      t.string :city

      t.timestamps
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Now we need to add a reference to the employee in this file, so just edit this file and add this line: t.references :employees, and it looks like

class CreateAddresses < ActiveRecord::Migration[7.0]
  def change
    create_table :addresses do |t|
      t.integer :house_number
      t.string :society_name
      t.string :area
      t.string :city
      t.references :employee

      t.timestamps
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

 
If you want to pass null :false with a reference, you can do that also, but here we are keeping it simple.

Now run rails db:migrate it will make some changes to our schema. adding a new addresses table with the given fields and also adding employee_id as a reference in it.

 
Apart from it, suppose you don’t want to make any changes in the migration file for giving references and want to add references using another migration, so you need to follow these steps:

Run this code

rails g migration AddEmployeeToAddress employee:references
Enter fullscreen mode Exit fullscreen mode

It will generate one migration with the following code:

class AddEmployeeToAddress < ActiveRecord::Migration[7.0]
  def change
    add_reference :addresses, :employee, null: false, foreign_key: true
  end
end
Enter fullscreen mode Exit fullscreen mode

and migrate it using rails db:migrate

 
The work of migrating files has been completed; now let’s move on to the model file.

open app/models/employee.rb and paste the below code.

class Employee < ApplicationRecord
  has_many :addresses
  accepts_nested_attributes_for :addresses,
                                allow_destroy: true,
                                reject_if: proc { |att| att['house_number'].blank? || ['society_name'].blank? || ['city'].blank? || ['area'].blank? }
end
Enter fullscreen mode Exit fullscreen mode

 
open app/models/address.rb and paste the below code.

class Address < ApplicationRecord
  belongs_to :employee
end
Enter fullscreen mode Exit fullscreen mode

That’s it for model. Now it is the turn of the controller file.

 
Open employees_controller.rb and add parameters for the address_attributes field.

addresses_attributes:[:id, :house_number, :society_name, :area, :city, :_destroy]

→ Note :*we are also passing the id here to ensure that the fields are not duplicated while editing the data.*

 
Previous params :

params.require(:employee).permit(:employee_name, :gender, { hobbies: [] })
Enter fullscreen mode Exit fullscreen mode

 
New params :

params.require(:employee).permit(:employee_name, :gender, { hobbies: [] }, addresses_attributes: [:id, :house_number, :society_name, :area, :city, :_destroy])
Enter fullscreen mode Exit fullscreen mode

 
After that, you’ll need to add partials and some code for rendering dynamic fields.

Add the following code into the new.html.erb file:

<div class="addresses">
  <%= f.fields_for :addresses do |address| %>
    <%= render 'address_fields', f: address %>
  <% end %>
</div>
<%= link_to_add_association "add address", f, :addresses, data: {association_insertion_node: '.addresses', association_insertion_method: :append} %>
Enter fullscreen mode Exit fullscreen mode

 
So now we have our entire new.html.erb and it contains

<div class="set-center flex-column">
  <div class="card shadow" style="width: 36rem;">
    <div class="card-header">
      <h2 class="text-center">Employee Form</h2>
    </div>
    <div class="card-body">
      <!--Error Message-->
      <% if flash[:errors] %>
        <% flash[:errors].each do |error| %>
          <p class="text-danger"><%= error %></p>
        <% end %>
      <% end %>
      <!-- Form Started Here -->
      <%= form_with model: @employee do |f| %>
        <%= f.label :employee_name, "Employee Name :", class:"mt-3" %>
        <%= f.text_field :employee_name ,placeholder: "Enter Employee's Name",class:"mb-2 form-control" %>
        <br>
        <!-- Radio Button For Gender -->
        <div class="form-group">
          <%= f.label "Gender :" %>
          <%= f.radio_button :gender, "male" %>
          <%= f.label :gender, "Male" %>
          <%= f.radio_button :gender, "female" %>
          <%= f.label :gender, "Female" %>
        </div>
        <br>
        <!-- Checkbox For Hobbies -->
        <div class="form-group">
          <%= f.label "Hobbies :" %>
          <%= f.check_box :hobbies, { multiple: true },"Reading", false %>
          <%= f.label :hobbies, "Reading" %>
          <%= f.check_box :hobbies, { multiple: true },"Photography", false %>
          <%= f.label :hobbies, "Photography" %>
          <%= f.check_box :hobbies, { multiple: true },"Travelling", false %>
          <%= f.label :hobbies, "Travelling" %>
        </div>
        <br>
        <div class="addresses">
          <%= f.fields_for :addresses do |address| %>
            <%= render 'address_fields', f: address %>
          <% end %>
        </div>
        <%= link_to_add_association "add address", f, :addresses, data: {association_insertion_node: '.addresses', association_insertion_method: :append} %>
        <%= f.submit "Save Employee", class:"btn-primary border-0 rounded-pill shadow p-3" %>
      <% end %>
    </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

 
Now let’s make a partial file with the name “_address_fields.html.erb” in the employee directory.

app/views/employees/_address_fields.html.erb

<!-- Nested Form for f -->
<div class="nested-fields">
  <%= f.label :house_number, class: "mt-3" %>
  <%= f.number_field :house_number, placeholder: "Enter Your Flat/House Number", class: "mb-3 form-control" %>
  <%= f.label :society_name, class: "mt-3" %>
  <%= f.text_area :society_name, placeholder: "Enter Your Society Name", class: "mb-3 form-control" %>
  <%= f.label :area, class: "mt-3" %>
  <%= f.text_area :area, placeholder: "Enter Your Area Name", class: "mb-3 form-control" %>
  <%= f.label :city, class: "mt-3" %>
  <%= f.text_area :city, placeholder: "Enter Your City Name", class: "mb-3 form-control" %>
  <%= link_to_remove_association "Delete", f %>
</div>
Enter fullscreen mode Exit fullscreen mode

 
The dynamic fields are now displayed in show.html.erb show page as well.

<!-- Dynamic Address Show Here -->
<div>
  <% @employee.addresses.each do |address| %>
    House Number: <%= address.house_number %> <br>
    Society Name: <%= address.society_name %> <br>
    Area: <%= address.area %> <br>
    City: <%= address.city %> <br>
  <% end %>
</div>
Enter fullscreen mode Exit fullscreen mode

This sort of output will be generated by it.

Show Page — Address with dynamic field

That’s it for implementing a dynamic address field using the cocoon gem.

 

Preview :

Click on this link to see the preview
Showing Add Address and Delete Links


 
Let’s try to make it more appealing by adding some styling now.

Starting with the “add employee” link from new.html.erb or edit.html.erb

Previous Code :

<%= link_to_add_association "add address", f, :addresses, data: {association_insertion_node: '.addresses', association_insertion_method: :append} %>
Enter fullscreen mode Exit fullscreen mode

 

Updated Code :

<%= link_to_add_association "add address", f, :addresses, data: {association_insertion_node: '.addresses', association_insertion_method: :append} %>
Enter fullscreen mode Exit fullscreen mode

 
Additionally, replace the Previous _address_fields with this.

_address_fields.html.erb

 <!-- Nested Form for f -->
<div class="nested-fields">
  <div class="row">
    <div class="col">
      <%= f.label :house_number, class: "mt-3" %>
      <%= f.number_field :house_number, placeholder: "Enter Your Flat/House Number", class: "mb-3 form-control" %>
    </div>
    <div class="col">
      <%= f.label :society_name, class: "mt-3" %>
      <%= f.text_field :society_name, placeholder: "Enter Your Society Name", class: "mb-3 form-control" %>
    </div>
  </div>
  <div class="row">
    <div class="col">
      <%= f.label :area, class: "mt-3" %>
      <%= f.text_field :area, placeholder: "Enter Your Area Name", class: "mb-3 form-control" %>
    </div>
    <div class="col">
      <%= f.label :city, class: "mt-3" %>
      <%= f.text_field :city, placeholder: "Enter Your City Name", class: "mb-3 form-control" %>
    </div>
  </div>
  <%= link_to_remove_association "Delete", f, class:"btn btn-danger mb-2" %>
</div>
Enter fullscreen mode Exit fullscreen mode

 

Output :

Add Address Button, Nested Form after styling

 
show.html.erb

<!-- Dynamic Address Show Here -->
<div>
  <% @employee.addresses.each do |address| %>
    <p>&#10145; House Number: <%= address.house_number %></p>
    <p>&#10145; Society Name: <%= address.society_name %></p>
    <p>&#10145; Area: <%= address.area %></p>
    <p>&#10145; City: <%= address.city %></p>
    <hr>
  <% end %>
</div>
Enter fullscreen mode Exit fullscreen mode

 

Output :

Show page after styling


 

3) Final Preview :

 

4) References :

My Github Repo: https://github.com/rutikkpatel/Cocoon-Gem-Rails

Cocoon Gem Repo: https://github.com/nathanvda/cocoon

Top comments (1)

Collapse
 
superails profile image
Yaroslav Shmarov

I strongly discourage using anything jquery-based in Rails 7. For rails 7 you can use something like this github.com/arielj/vanilla-nested instead of cocoon, or something like this: github.com/nathanvda/cocoon/issues...