Rails and Nested Attributes

As part of my time coach series, I need models with nested attributes. I wish to have a user model, that has many routines, and routines have many activities. I also wish for the user to be able to click to add more activities when they are in the new page for routines.

This video on youtube by "CodeCampBase" walks you through the process of creating a rails app that has a form, where the user can create a product and add product_variants as needed.

I am using draw.io to sketch up a relational database design for the example on CodeCampBase:

CodeCampBase Product Database Design (2).png

$ rails new shop

This implementation for creating new fields uses jquery. To get it working on rails 6+ I did a little googling and found that you need to do the following:

add jquery-rails to gemfile

gem 'jquery-rails'
$ yarn add jquery

Add below code in config/webpack/environment.js

const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)

Require jquery in application.js file.

require('jquery')

Now jquery should be working. Generate the product scaffold.

$ rails g scaffold product title description
$ rails db:migrate

Set root route

   root 'product#index'

Now generate the product_variant scaffold.

$ rails g scaffold product_variant color size product:references
$ rails db:migrate

In app/models/product.rb add the associotions and ability to have nested attributes

class Product < ApplicationRecord
  has_many :product_variants
  accepts_nested_attributes_for :product_variants
end

In app/controllers/products_controller.rb add product variants to product_params

    def product_params
      params
      .require(:product)
      .permit(:title, :description, product_variants_attributes: [:id, :color, :size])
    end

Let's add some more info to display on the show page for products. app/views/products/show.html.erb

<p>
  <strong>Available Variants <%= @product.product_variants.count  %> </strong>
</p>

  <% @product.product_variants.each do |variant| %>
<p>
    <%= variant.color %>
    <%= variant.size %>
  <% end %>
</p>

In app/controllers/products_controller.rb we need to add the product variant to the product instance variable

  def new
    @product = Product.new
    @product.product_variants.build
  end

Add the variants to the product form partial and make a new partial to hold those fields. In app/views/products/_form.html.erb add

  <div class="field">

    <%= form.fields_for :product_variants do |f| %>
      <%= render 'product_variant_fields', f: f%>
    <% end %>

  <%= link_to_add_fields "Add Variant", form, :product_variants %>

  </div>

app/views/products/_product_variant_fields.html.erb

<%= f.label :color %>
<%= f.text_field :color %>

<%= f.label :size %>
<%= f.text_field :size %>

Let's write the link_to_add_fields helper method. app/helpers/application_helper.rb

module ApplicationHelper

  def link_to_add_fields(name, f, association)
    ## create new object from an association (:product_variants)
    new_object = f.object.send(association).klass.new

    ## create or take the id from the created object
    id = new_object.object_id

    ## create the fields form
    fields = f.fields_for(association, new_object, child_index: id) do |builder|
      render(association.to_s.singularize + "_fields", f: builder)
    end

    ## pass down the link to the fields form
    link_to(name, '#', class: 'add_fields', data: {id: id, fields: fields.gsub("\n", "")})
  end
end

At the bottom of the products form partial we'll add the javascript for creating new fields on click. app/views/products/_form.html.erb

<script>
  $('form').on('click', '.add_fields', function(event){
    var regexp, time;
    time = new Date().getTime();
    regexp  = new RegExp($(this).data('id'), 'g');
    $(this).before($(this).data('fields').replace(regexp, time));
    return event.preventDefault();
    console.log('clicked');
  });
</script>

Now we can create new product_variant fields with the click of a button

Screen Shot 2021-04-30 at 11.28.56 AM.png

Screen Shot 2021-04-30 at 11.28.44 AM.png

No Comments Yet