1. Add Gem
gem "nested_form"
Run$ : bundle
----------------
2. Created new file : app/javascript/js/nested_form.js
app/javascript/js/nested_form.js
rails g nested_form:install
jb ye line run karege to public ke ander >> public/javascript/js/nested_form.js file generate hogi, lekin nested_form.js ka code is directory me add kar dena >> app/javascript/js/nested_form.jsapp/javascript/js/nested_form.js
(function($) {
window.NestedFormEvents = function() {
this.addFields = $.proxy(this.addFields, this);
this.removeFields = $.proxy(this.removeFields, this);
};
NestedFormEvents.prototype = {
addFields: function(e) {
// Setup
var link = e.currentTarget;
var assoc = $(link).data('association'); // Name of child
var blueprint = $('#' + $(link).data('blueprint-id'));
var content = blueprint.data('blueprint'); // Fields template
// Make the context correct by replacing <parents> with the generated ID
// of each of the parent objects
var context = ($(link).closest('.fields').closestChild('input, textarea, select').eq(0).attr('name') || '').replace(new RegExp('\[[a-z_]+\]$'), '');
// context will be something like this for a brand new form:
// project[tasks_attributes][1255929127459][assignments_attributes][1255929128105]
// or for an edit form:
// project[tasks_attributes][0][assignments_attributes][1]
if (context) {
var parentNames = context.match(/[a-z_]+_attributes(?=\]\[(new_)?\d+\])/g) || [];
var parentIds = context.match(/[0-9]+/g) || [];
for(var i = 0; i < parentNames.length; i++) {
if(parentIds[i]) {
content = content.replace(
new RegExp('(_' + parentNames[i] + ')_.+?_', 'g'),
'$1_' + parentIds[i] + '_');
content = content.replace(
new RegExp('(\\[' + parentNames[i] + '\\])\\[.+?\\]', 'g'),
'$1[' + parentIds[i] + ']');
}
}
}
// Make a unique ID for the new child
var regexp = new RegExp('new_' + assoc, 'g');
var new_id = this.newId();
content = $.trim(content.replace(regexp, new_id));
var field = this.insertFields(content, assoc, link);
// bubble up event upto document (through form)
field
.trigger({ type: 'nested:fieldAdded', field: field })
.trigger({ type: 'nested:fieldAdded:' + assoc, field: field });
return false;
},
newId: function() {
return new Date().getTime();
},
insertFields: function(content, assoc, link) {
var target = $(link).data('target');
if (target) {
return $(content).appendTo($(target));
} else {
return $(content).insertBefore(link);
}
},
removeFields: function(e) {
var $link = $(e.currentTarget),
assoc = $link.data('association'); // Name of child to be removed
var hiddenField = $link.prev('input[type=hidden]');
hiddenField.val('1');
var field = $link.closest('.fields');
field.hide();
field
.trigger({ type: 'nested:fieldRemoved', field: field })
.trigger({ type: 'nested:fieldRemoved:' + assoc, field: field });
return false;
}
};
window.nestedFormEvents = new NestedFormEvents();
$(document)
.delegate('form a.add_nested_fields', 'click', nestedFormEvents.addFields)
.delegate('form a.remove_nested_fields', 'click', nestedFormEvents.removeFields);
})(jQuery);
// http://plugins.jquery.com/project/closestChild
/*
* Copyright 2011, Tobias Lindig
*
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
*
*/
(function($) {
$.fn.closestChild = function(selector) {
// breadth first search for the first matched node
if (selector && selector != '') {
var queue = [];
queue.push(this);
while(queue.length > 0) {
var node = queue.shift();
var children = node.children();
for(var i = 0; i < children.length; ++i) {
var child = $(children[i]);
if (child.is(selector)) {
return child; //well, we found one
}
queue.push(child);
}
}
}
return $();//nothing found
};
})(jQuery);
--------------------------------------------
4. config/importmap.rb
# Pin npm packages by running ./bin/importmap
pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin "nested_form", to: "js/nested_form.js"
pin_all_from "app/javascript/controllers", under: "controllers"
pin_all_from "app/javascript/js", under: "js", preload: true
------------------------------------------------
5. app/javascript/application.js
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"
import "js/bootstrap.bundle.min"
import "js/simple-datatables"
import "js/quill.min"
import "js/tinymce.min"
import "js/validate"
import "js/main"
import "nested_form";
--------------------------------------------
6. User.rb
has_many :products, dependent: :destroy
accepts_nested_attributes_for :products, allow_destroy: true
-----------------------------
-----------------------------
7. Product.rb
class Product < ApplicationRecord
belongs_to :user
end
------------------------------------------
8. On form
<%= simple_nested_form_for @user, url: user_path(@user), html: { class: 'row g-3', "data-turbo": false } do |f| %>
<div class="col-md-6 mt-4">
<label class="form-label">Bericht</label>
<%= f.input :email, input_html: { class: "form-control form-control-sm", rows: 5 }, label: false %>
</div>
<h3>Products</h3>
<div id="products">
<%= f.fields_for :products do |product_form| %>
<div class="nested-fields">
<%= product_form.label :name, "Product Name" %>
<%= product_form.text_field :name %>
<%= product_form.link_to_remove "Remove" %>
</div>
<% end %>
</div>
<%= f.link_to_add "Add a products", :products, :data => { :target => "#products" } %>
<% end %>
-------------------------------------------------
9. User controller.rb
def users_params
params.require(:user).permit(:email, products_attributes: [:id, :name, :_destroy])
end