[RUBY] How to store data simultaneously in a model associated with a nested form (Rails 6.0.0)

Conclusion: Add accepts_nested_attributes_for to the parent model

Demonstration environment

・ Cloud9 Ubuntu Server Rails 6.0.0 ・ Ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]

Prerequisites

Suppose you have a Parent model and a Kid model with one-to-many relationships like this:

models/parent.rb


class Parent < ApplicationRecord
  has_many :kids, dependent: :destroy
end

models/kid.rb


class Kid < ApplicationRecord
  belongs_to :parent
end

Nested form implementation

Since the data of the child model is saved at the same time as the data of the parent model is saved, accepts_nested_attributes_for To the Parent model.

models/parent.rb


class Parent < ApplicationRecord
  has_many :kids, dependent: :destroy
  accepts_nested_attributes_for :kids
end

This allows you to use nested forms that allow you to register the associated data in one form.

Next, in the Parents controller of the parent model, create an empty instance that receives the params sent from the form page.

It also creates an empty instance of the associated child model. Also in the strong parameter to receive the prams of the child model

kids_attributes: [:name, :age, :toy]


 Is passing.


#### **`controllers/parents_controller.rb`**
```rb

class ParentsController < ApplicationController

#(Omitted)
  
  def new
    @parent = Parent.new
    @parent.kids.build #Create an empty instance of the child model
  end
  
  def create
    @parent = Parent.new(parent_params)
    if @parent.save
      redirect_to root_url
    else
      render :new
    end
  end

#(Omission)
  
  private
  
    def parent_params
      #Allows you to receive parameters for child models
      params.require(:parent).permit(
        :name, :age, kids_attributes: [:name, :age, :toy] 
      )
    end

end

erb:new.html.erb


<div class="container">
  <div class="col-sm-10 col-sm-offset-1">
    <h1 class="text-center">Parent registration</h1>
    <%= form_with(model: @parent, local: true) do |f| %>
      <div class="field form-group">
        <%= f.label :name %>
        <%= f.text_field :name, class: "form-control" %>
      </div>
      <div class="field form-group">
        <%= f.label :age %>
        <%= f.number_field :age, class: "form-control" %>
      </div>

      <!--Nested form to receive data for child models-->

      <%= f.fields_for :kids do |kf| %>
      <h1 class="text-center">Child registration</h1>
        <div class="field form-group">
          <%= kf.label :name %>
          <%= kf.text_field :name, class: "form-control" %>
        </div>
        <div class="field form-group">
          <%= kf.label :age %>
          <%= kf.number_field :age, class: "form-control" %>
        </div>
        <div class="field form-group">
          <%= kf.label :toy %>
          <%= kf.text_field :toy, class: "form-control" %>
        </div>
      <% end %>

      <div class="field form-group">
        <%= f.submit "Register with the above contents", class: "btn btn-primary btn-lg btn-block" %>
      </div>
    <% end %>
  </div>
</div>

It's hard to see because it contains the Bootstrap class, but the above `` `f.fields_for``` is the form that receives the data of the child model. ネストされたフォーム

Allows new associated data to be saved when updating data

Although it is an input form for child models, by creating multiple empty instances, you can increase the number of input fields without increasing the form of view itself. For example, by changing the Parents controller as follows, one set of child model input forms can be displayed at the time of new registration, and two sets of child model input forms can be displayed at the time of update.

controllers/parents_controller.rb


class ParentsController < ApplicationController

#(Omission)

  def new
    @parent = Parent.new
    @parent.kids.build #Create an empty instance of the child model
  end
  
  def create
    @parent = Parent.new(parent_params)
    if @parent.save
      redirect_to root_url
    else
      render :new
    end
  end

#(Omission)

  def edit
    @parent = Parent.find(params[:id])
    @parent.kids.build #Create an empty instance of the child model
  end
  
  def update
    @parent = Parent.find(params[:id])
    if @parent.update(parent_params)
      redirect_to root_url
    else
      render :edit
    end
  end
  
  def destroy
    @parent = Parent.find(params[:id])
    @parent.destroy
    redirect_to root_url
  end
  
  private
  
    def parent_params
      params.require(:parent).permit(
        :name, :age, kids_attributes: [:name, :age, :toy]
      )
    end
    
end

By creating an empty instance of the child model also in the edit action, "registered child model data" + 1 input form will be created at the time of update.

This makes it easier to increase the number of input forms than adding them dynamically with JS. (The bottleneck is that the number is limited ...)

If you want to add more forms, you can create more empty instances.

@parent.kids.build


 From

#### **`n.times { @parent.kids.build }`**

By doing, you can generate n input forms.

Prevents empty data from being saved if not entered

However, as it is, empty data will be saved if the added form is not filled out. To prevent this, pass the following Proc as the second argument of accepts_nested_attributes_for added to the Parent model.

models/parent.rb


class Parent < ApplicationRecord
  has_many :kids, dependent: :destroy
  accepts_nested_attributes_for :kids, reject_if: lambda {|attributes| attributes['name'].blank?}
end

In the above case, if the name of the child model input form is empty, all other attributes (here, age, toy) will not be saved even if they are entered.

If you do not want to register if name or age is not entered, change as follows.

models/parent.rb


class Parent < ApplicationRecord
  has_many :kids, dependent: :destroy
  accepts_nested_attributes_for :kids, reject_if: lambda {|attributes| attributes['name'].blank? || attributes['age'].blank?}
end

In addition to the above, you can freely control the stored values by changing the validation to the child model.

In addition to this, you may want to delete only the data of the associated child model, but for details, see "Action View Form Helper" in the Rails guide. It will be helpful.

Afterword

I'm Tatsuron, a beginner who is currently studying Ruby on Rails. I posted about the features that I got stuck for several days while creating my own application. If you have any mistakes or advice, please comment.

Recommended Posts

How to store data simultaneously in a model associated with a nested form (Rails 6.0.0)
How to rename a model with foreign key constraints in Rails
How to store Rakuten API data in a table
How to insert a video in Rails
How to get boolean value with jQuery in rails simple form
[Rails] I want to send data of different models in a form
[Rails] How to log in with a name by adding a devise name column
How to implement a like feature in Rails
How to easily create a pull-down in Rails
How to make a follow function in Rails
How to make a unique combination of data in the rails intermediate table
How to clear all data in a particular table
How to make a factory with a model with polymorphic association
How to implement a like feature in Ajax in Rails
How to delete a new_record object built with Rails
How to manually generate a JWT with Rails Knock
How to create a data URI (base64) in Java
How to write a date comparison search in Rails
How to query Array in jsonb with Rails + postgres
[Rails 6] How to set a background image in Rails [CSS]
[Rails] How to load JavaScript in a specific view
How to get started with creating a Rails app
How to deal with errors in Rails s could not find a JavaScript runtime.
How to store a string from ArrayList to String in Java (Personal)
How to display a graph in Ruby on Rails (LazyHighChart)
How to add the same Indexes in a nested array
Mapping to a class with a value object in How to MyBatis
How to simulate uploading a post-object form to OSS in Java
How to set up a proxy with authentication in Feign
How to write in Model class when you want to save binary data in DB with PlayFramework
[Rails] How to write in Japanese
Implement a contact form in Rails
How to introduce jQuery in Rails 6
How to get along with Rails
How to install Swiper in Rails
How to make a jar file with no dependencies in Maven
[Rails 6] How to create a dynamic form input screen using cocoon
[rails] How to display parent information in child view in nested relationships
How to delete large amounts of data in Rails and concerns
How to store the information entered in textarea in a variable in the method
How to implement search functionality in Rails
How to change app name in rails
How to get date data in Ruby
How to use custom helpers in rails
How to implement a slideshow using slick in Rails (one by one & multiple by one)
[For beginners] I want to automatically enter pre-registered data in the input form with a selection command.
How to create a query using variables in GraphQL [Using Ruby on Rails]
[Personal memo] How to interact with a random number generator in Java
[Rails] How to use rails console with docker
How to use MySQL in Rails tutorial
How to update user edits in Rails Devise without entering a password
How to build a Ruby on Rails development environment with Docker (Rails 6.x)
Steps to set a favicon in Rails
[Rails] How to get the user information currently logged in with devise
[rails] How to configure routing in resources
[rails] How to create a partial template
How to implement ranking functionality in Rails
How to delete data with foreign key
How to debug the processing in the Ruby on Rails model only on the console
How to display the text entered in text_area in Rails with line breaks
How to build a Ruby on Rails development environment with Docker (Rails 5.x)