Having implemented the functionality you seek, I'll give some ideas:
Accepts Nested Attributes For
As you already know, you can pass attributes from a parent to nested model by using the accepts_nested_attributes_for
function
Although relatively simple, it's got a learning curve. So I'll explain how to use it here:
#app/models/plan.rb
Class Plan < ActiveRecord::Base
has_many :exercises
accepts_nested_attributes_for :exercises, allow_destroy: true
end
This gives the plan
model the "command" to send through any extra data, if presented correctly
To send the data correctly (in Rails 4), there are several important steps:
1. Build the ActiveRecord Object
2. Use `f.fields_for` To Display The Nested Fields
3. Handle The Data With Strong Params
Build The ActiveRecord Object
#app/controllers/plans_controller.rb
def new
@plan = Plan.new
@plan.exericses.build
end
Use f.fields_for To Display Nested Fields
#app/views/plans/new.html.erb
<%= form_for @plans do |f| %>
<%= f.fields_for :exercises do |builder| %>
<%= builder.text_field :example_field %>
<% end %>
<% end %>
Handle The Data With Strong Params
#app/controllers/plans_controller.rb
def create
@plan = Plan.new(plans_params)
@plan.save
end
private
def plans_params
params.require(:plan).permit(:fields, exerices_attributes: [:extra_fields])
end
This should pass the required data to your nested model. Without this, you'll not pass the data, and your nested forms won't work at all
Appending Extra Fields
Appending extra fields is the tricky part
The problem is that generating new f.fields_for
objects on the fly is only possible within a form
object (which only exists in an instance)
Ryan Bates gets around this by sending the current form
object through to a helper, but this causes a problem because the helper then appends the entire source code for the new field into a links' on click
event (inefficient)
We found this tutorial more apt
It works like this:
- Create 2 partials:
f.fields_for
&form
partial (for ajax) - Create new route (ajax endpoint)
- Create new controller action (to add extra field)
- Create JS to add extra field
Create 2 Partials
#app/views/plans/add_exercise.html.erb
<%= form_for @plan, :url => plans_path, :authenticity_token => false do |f| %>
<%= render :partial => "plans/exercises_fields", locals: {f: f, child_index: Time.now.to_i} %>
<% end %>
#app/views/plans/_exercise_fields.html.erb
<%= f.fields_for :exercises, :child_index => child_index do |builder| %>
<%= builder.text_field :example %>
<% end %>
Create New Route
#config/routes.rb
resources :plans do
collection do
get :add_exercise
end
end
Create Controller Action
#app/controllers/plans_controller.rb
def add_exercise
@plan = Plan.new
@plan.exercises.build
render "add_exericse", :layout => false
end
Create JS to Add The Extra Field
#app/assets/javascripts/plans.js.coffee
$ ->
$(document).on "click", "#add_exercise", (e) ->
e.preventDefault();
#Ajax
$.ajax
url: '/messages/add_exercise'
success: (data) ->
el_to_add = $(data).html()
$('#exercises').append(el_to_add)
error: (data) ->
alert "Sorry, There Was An Error!"
Apologies for the mammoth post, but it should work & help show you more info