rails multi-level nested forms with cocoon and tables - ruby-on-rails

I've successfully implemented one level of nested form with Cocoon and tables. However, I'm having a difficult time wrapping my mind on how to do another nested level. My issue is how to do this with a table. And maybe a table isn't the write way to go at all. Thank you for helping a newbie.
Here are my models:
class Profession < ApplicationRecord
has_many :procedure_categories, dependent: :destroy
accepts_nested_attributes_for :procedure_categories, allow_destroy: true
end
And:
class ProcedureCategory < ApplicationRecord
belongs_to :profession
has_many :procedures
accepts_nested_attributes_for :procedures, allow_destroy: true
end
And:
class Procedure < ApplicationRecord
belongs_to :procedure_category
end
Here is my top level form code:
<%= form_for(#profession) do |f| %>
<%= render 'shared/profession_error_messages' %>
<%= f.label :profession %>
<%= f.text_field :profession, class: 'form-control' %>
<%= f.label :description %>
<%= f.text_field :description, class: 'form-control' %>
<%= f.label :active, class: "checkbox inline" do %>
<%= f.check_box :active %>
<span>Active profession?</span>
<% end %>
<table class='table'>
<thead>
<tr>
<th>Category</th>
<th>Description</th>
<th>Display Order</th>
<th>Selection Type</th>
<th>Delete</th>
<th>Edit</th>
</tr>
</thead>
<tbody class="categories">
<%= f.fields_for :procedure_categories do |procedure_category| %>
<%= render 'procedure_category_fields', f: procedure_category %>
<% end %>
</tbody>
</table>
<%= link_to_add_association 'Add Category', f, :procedure_categories,
data: { association_insertion_node: '.categories', association_insertion_method: :append } %>
<br><br>
<%= f.submit "Save", class: "btn btn-primary" %>
<% end %>
And the next partial one level down:
<tr class="nested-fields">
<td><%= f.text_field :category, class: 'form-control' %></td>
<td><%= f.text_field :description, class: 'form-control' %></td>
<td><%= f.text_field :display_order, class: 'form-control' %></td>
<% cs = options_for_select(controls, f.object.selection_type) %>
<td><%= f.select :selection_type, cs, class: 'form-control' %></td>
<td><%= link_to_remove_association "Remove Category", f %></td>
<% if f.object != nil %>
<td><%= link_to "Category", edit_procedure_category_path(#profession,f.object) %><td></td>
<% end %>
</tr>
So, I'm struggling with how to implement the final level of nesting (procedures).
Thank you for listening.

Use has_many :through
Here are my models:
class Profession < ApplicationRecord
has_many :procedures, through: categories
has_many :categories, dependent: :destroy
accepts_nested_attributes_for :categories, allow_destroy: true
end
Rename this procedure_category model in category
class Category < ApplicationRecord
belongs_to :profession
has_many :procedures
accepts_nested_attributes_for :procedures, allow_destroy: true
end
And:
class Procedure < ApplicationRecord
belongs_to :category
end
If I miss something you can check the instruction from the rails guide
The controller professions#new action should create the following variables, so that they are available in the view:
def new
#profession = Profession.new
#categories = #profession.categories.build
#procedures = #categories.procedures.build
end
The view uses that variable so store the user inputs and make a post request at /profession/ with those inputs stored in the parameters hash
<%= form_for(#profession) do |f| %>
<%= f.fields_for :categories do |category| %>
<%= category.fields_for :procedures do |precedure| %>
<% end %>
<% end %>
<% end %>
The fields_for yields a form builder. The parameters' name will be what accepts_nested_attributes_for expects. For example, when creating a user with 2 addresses, the submitted parameters would look like:
This is how your parameters should look like:
{
'profession' => {
'name' => 'John Doe',
'categories_attributes' => {
'0' => {
'kind' => 'Home',
'street' => '221b Baker Street',
'procedures_attributes' => {
'0' => {},
'1' => {}
}
},
'1' => {
'kind' => 'Office',
'street' => '31 Spooner Street'
}
}
}
}
so make sure your form is pointing at post url /professions/ and that the routing will trigger the professions#create action
def create
binding.pry
end
for any problems set a binding pry and check in your console how your parameters are showing up.
Read more at
http://guides.rubyonrails.org/form_helpers.html#building-complex-forms

Related

Unpermitted parameter nested form inside a nested form

I have a Workgroup object, which has many resource_quantities. The Resource_quantity belongs to a resource. I want a form that can create the Workgroup and its children resource_quantities all in one place. I am trying to build a form using nested attributes. My problem is that when I submit my form where I can add many resources_quantities and their related resource, I get
Parameters: {"utf8"=>"✓", "workgroup"=>{"name"=>"Living room", "description"=>"installation floor", "contractor_id"=>"1", "resource_quantities_attributes"=>{"1543577850668"=>{"quantity"=>"22", "resources"=>{"name"=>"wood", "unit_type"=>"Matériel", "purchase_price"=>"20", "price"=>"22", "unit_measure"=>"u", "vat"=>"12"}, "_destroy"=>"false"}}}, "commit"=>"Create Workgroup"}
The fact that I receive a resources params instead of a resource_attributes results in a Unpermitted parameter: :resources. I have tried this params.require(:workgroup).permit! but i still received resources which results in a unknown attribute 'resources' for ResourceQuantity.
Here are the useful piece of code:
Workgroups controller
def new
#workgroup = Workgroup.new
#workgroup.resource_quantities.build.build_resource
end
def create
#workgroup = Workgroup.new(workgroup_params)
if #workgroup.save!
raise
redirect_to resources_path, notice: 'Resource was successfully created.'
else
render :new
end
end
def workgroup_params
params.require(:workgroup).permit(:name, :description, :contractor_id, :_destroy, {resource_quantities_attributes: [:quantity, :_destroy, {resources_attributes: [ :name, :unit_type, :price, :contractor_id, :purchase_price, :unit_measure, :vat]}]})
end
My Three Models
class Workgroup < ApplicationRecord
has_many :resource_quantities, inverse_of: :workgroup
has_many :resources, through: :resource_quantities
accepts_nested_attributes_for :resource_quantities, allow_destroy: true
end
class ResourceQuantity < ApplicationRecord
belongs_to :workgroup, optional: true
belongs_to :resource, optional: true
accepts_nested_attributes_for :resource, :allow_destroy => true
end
class Resource < ApplicationRecord
has_many :workgroups, through: :resource_quantities
has_many :resource_quantities, inverse_of: :resource
end
My form for Workgroup which integrate the form for resource_quantities
<%= simple_form_for(#workgroup) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :name %>
<%= f.input :description %>
<%= f.association :contractor, as: :hidden, input_html: {value: f.object.contractor || "#{current_user.contractor.id}"} %>
<h3> Resources </h3>
<table class='large_table'>
<tbody class="add_resource">
<%= f.simple_fields_for :resource_quantities do |builder| %>
<%= render 'resource_quantity_fields', f: builder %>
<% end %>
</tbody>
</table>
</div>
<div class="form-actions">
<%= f.button :submit %>
<%= link_to_add_association 'Ajouter une resource', f, :resource_quantities, class: 'btn btn-primary', data: { association_insertion_node: '.add_resource', association_insertion_method: :append } %>
</div>
<% end %>
This is my partial to add the fields related to resource_quantity and resource
<tr class="nested-fields">
<td>
<%= f.input :quantity %>
</td>
<%= f.simple_fields_for :resources do |e| %>
<td><%= e.input :name %></td>
<td><%= e.input :unit_type, collection: Resource::TYPES %></td>
<td><%= e.input :purchase_price %></td>
<td><%= e.input :price %></td>
<td><%= e.input :unit_measure, collection: Resource::UNIT_MEASURE%></td>
<td><%= e.input :vat, collection: Resource::VAT%></td>
<td>
<%= link_to_remove_association theme_icon_tag("trash"), f %>
</td>
<% end %>
</tr>
Hope someone will be able to help me I am rookie in rails
It seems that by putting resources_attributes into a string is enabling me to get the right params in the consol.
simple_fields_for "resources_attributes" do |e|
I don't know if it is good practice but it works fine.
Can you try the following change?
Replace
<%= f.simple_fields_for :resources do |e| %>
with
<%= f.simple_fields_for :resources_attributes do |e| %>

How do I save a form in double nested form?

I have three models: Event, Workout, and Round.
A person can create a event, that includes a workout, and configure number of sets and weights through round.
I am currently using cocoon gem to create a nested form. I am able to use the form and save Event and Workout, however, Round is not being saved.
class Event < ActiveRecord::Base
has_many :workouts, dependent: :destroy
has_many :rounds, :through => :workouts
belongs_to :user
accepts_nested_attributes_for :workouts, :allow_destroy => true
end
class Workout < ActiveRecord::Base
belongs_to :event
has_many :rounds, dependent: :destroy
accepts_nested_attributes_for :rounds, :allow_destroy => true
end
class Round < ActiveRecord::Base
belongs_to :workout
belongs_to :event
end
I currently have my routes set like this.
Rails.application.routes.draw do
devise_for :users
root 'static_pages#index'
resources :events do
resources :workouts do
resources :rounds
end
end
In my controller, this is how I have my new methods.
New Method for Event
def new
#event = current_user.events.new
end
New Method for Workout
def new
#workout = Workout.new
end
New Method for Round
def new
#round = Round.new
end
I currently have the form under Events' view folder. Under show.html.erb of Events view file, I am trying to display Rounds as well by
<% #workout.rounds.each do |round| %>
<%= round.weight %>
<% end %>
But I am getting undefined method for rounds. Is it not possible to display round in Event view?
Thanks for the help!
Edit 1:
Here is my nested forms.
At the top, I have form for Event.
<%= simple_form_for #event do |f| %>
<%= f.input :title %>
<h3>Work Outs</h3>
<div id="workouts">
<%= f.simple_fields_for :workouts do |workout| %>
<%= render 'workout_fields', f: workout %>
<% end %>
<div class="links">
<%= link_to_add_association 'add workout', f, :workouts %>
</div>
</div>
<div class="field">
<%= f.label :start_time %><br>
<%= f.datetime_select :start_time %>
</div>
<div class="field">
<%= f.label :end_time %><br>
<%= f.datetime_select :end_time %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="actions">
<%= f.submit "Create new Workout" %>
</div>
<% end %>
<% end %>
</form>
Then there is a form for Workout
<div class="nested-fields">
<%= f.input :name %>
<div class="rounds">
<%= f.simple_fields_for :rounds, :wrapper => 'inline' do |round| %>
<%= render 'round_fields', f: round %>
<% end %>
<div class="links">
<%= link_to_add_association 'add round', f, :rounds, :render_option => { :wrapper => 'inline' } %>
</div>
</div>
<%= link_to_remove_association "remove workout", f %>
</div>
And finally, I have the form for Round
<div class="nested-fields">
<table class="table round-table">
<tr>
<td>
<% index = 0 %>
<%= f.simple_fields_for :rounds do |round| %>
<%= render 'set_fields', {f: round, index: index} %>
<% index = index + 1 %>
<% end %>
</td>
<td>Previous Weight</td>
<td><%= f.input_field :weight %></td>
<td><%= f.input_field :repetition %></td>
<td><%= link_to_remove_association "remove rounds", f %></td>
</tr>
</table>
</div>
I am able to create round on rails console and save them. But when I use the form on the web, I cannot save them.
EDIT 2
This is currently how I have the event_params and workout_params set-up.
def event_params
params.fetch(:event, {}).permit(:title, :description, :start_time, :end_time, :workouts_attributes => [:id, :name, :category, :_destroy])
end
Then the workout_params:
def workout_params
params.require(:workout).permit(:name, :category, :rounds_attributes => [:id, :weight, :set, :repetition, :_destroy])
end
I am confused why the form would save Event and Workout. But Round always returns an empty array.
Finally solved it!
I had to have an association in the params as well. Double nested association.
def event_params
params.fetch(:event, {}).permit(:title, :description, :start_time, :end_time, :workouts_attributes => [:id, :name, :category, :_destroy])
end
For my event_params, I only had :workouts_attributes. I thought having :rounds_attributes in workout_params would be okay. But I needed to have rounds_attributes in event_params as well.
Fixing it like below fixed the issue.
def event_params
params.fetch(:event, {}).permit(:title, :description, :start_time, :end_time, :workouts_attributes => [:id, :name, :category, :_destroy, :rounds_attributes => [:id, :weight, :set, :repetition, :_destroy]])
end
you already have rounds attribute in Events model (has_many :rounds, :through => :workouts), why not use it?
<% #event.rounds.each do |round|
<%= round.weight %>
<% end %>

Ruby on Rails: Creating new model in nested association with has_many relationship

So I have an issue where a user needs to be able to place an order, each order is made up of one of more pizza_order models. A pizza_order has a polymorphic relationship with a pizza model in that it can either point to an entry in the specials table which has a pre-defined list of toppings, or it can point to an entry in the pizzas table, at which point the user can choose the crust type and toppings.
I had previously solved the issue of trying to create multiple pizza_order entries in one order by using the Cocoon gem, and that works fine. It even works fine when each pizza_order is only associated with an entry from specials table. Once I try to get the custom pizza entry to work things start breaking.
This is what a new order will look like
<%= form_for #order do |f| %>
<%= f.fields_for :pizza_orders do |pi_order| %>
<%= render partial: 'pizza_order_fields', locals: {f: pi_order}%>
<% end%>
<div class="add-field">
<%= link_to_add_association 'Add Pizza', f, :pizza_orders, id: "add_btn", class: "btn btn-primary" %>
</div>
<br/>
<%= f.submit "Place Order", class: "btn btn-primary "%>
<% end %>
Whenever the user pressed 'Add Pizza' this will render a new partial to pick either a specialty, or custom pizza. That form looks like this
<div class="nested-fields">
<fieldset data-role="controlgroup" data-type="horizontal" class="form-inline">
<legend>Specialty Pizzas</legend>
<% #specials.each do |sp| %>
<div class="container">
<%= f.label :pizza_id, sp.get_name_and_cost %>
<%= f.radio_button :pizza_id, sp.id %>
<h5>Crust Type: <%= sp.pizza.crust.name %></h5>
<h5>Toppings:
<% sp.pizza.toppings.each do |top| %>
<%= top.name + (top == sp.pizza.toppings.last ? "" : ",") %>
<% end %>
</h5>
<hr/>
</div>
<% end %>
<div class="container">
<%= f.label "Custom" %>
<%= f.radio_button :pizza_id, "Custom", id: "custom_pizza" %>
<div id="custom_pizza_fields">
<%= f.fields_for :pizza_id do |pi| %>
<%= render partial: 'custom_pizza', locals: {pi: pi} %>
<% end %>
</div>
</div>
</fieldset>
<%= f.label :quantity %>
<%= f.number_field :quantity, class: "number_field" %>
<br/>
<%= link_to_remove_association "Delete", f , id: "delete_btn", class: "btn btn-primary" %>
<hr/>
Finally this renders a partial to create a custom pizza, which looks like this
<h5>Crust Type</h5>
<table class="table">
<tr>
<% Crust.all.each do |crust| %>
<td>
<%= pi.label :crust, crust.name %>
<%= pi.radio_button :crust, crust.id %>
</td>
<% end %>
</tr>
</table>
<h5>Toppings</h5>
<table class="table">
<tr>
<% Topping.all.each do |topping| %>
<td>
<%= pi.label :toppings, topping.get_name_and_cost %>
<%= pi.check_box :toppings %>
</td>
<% end %>
</tr>
</table>
I'm fairly certain the problem is coming from this 2nd fields_for line, which reads <%= f.fields_for :pizza_id do |pi| %>. Unlike the specialty pizza, which already exist in the database, custom pizzas will be created when the user places the order. So I don't think :pizza_id will map to anything at this point.
Another issue is how do I return a collection of Toppings to map to the has_many field in the pizza model? I have implemented checkboxes, but I can't verify whether this will return a collection since my previous problem is halting further progress at the moment.
For reference, here are the models being used.
class Order < ActiveRecord::Base
belongs_to :user
has_many :pizza_orders
accepts_nested_attributes_for :pizza_orders
end
class PizzaOrder < ActiveRecord::Base
belongs_to :pizza, polymorphic: true
belongs_to :order
accepts_nested_attributes_for :pizza, allow_destroy: true
end
class Special < ActiveRecord::Base
belongs_to :pizza
validates :pizza, :presence => true
validates :cost, :presence => true
def get_name_and_cost
"#{pizza.name}: $#{cost}"
end
end
class Pizza < ActiveRecord::Base
has_and_belongs_to_many :toppings
has_many :pizza_orders, as: :pizza
belongs_to :crust
validates :crust, presence: true
end
class Topping < ActiveRecord::Base
has_and_belongs_to_many :pizzas
def get_name_and_cost
"#{self.name}: $#{self.cost}"
end
end
class Crust < ActiveRecord::Base
has_many :pizzas
end
One last thing. This is how I plan on handling strong-typed parameters for the order, but I'm not too sure if it is accurate
def order_params
params.require(:order).permit(:cost, pizza_orders_attributes: [:quantity, pizza_attributes: [:crust, :toppings]])
end

rails how to do f.parent.text_field :name

I need to put fields from journals and journal_entries into one row on a table, and have the ability to add and show many data entry rows in the same view. (ie a table of rows and using link_to_add_fields with accepts_nested_attributes to expand the rows in the table).
There has to be some kind of f.parent.text_field or f.object.parent.text_field?
I'm trying to do something like the following
<table>
#in a :pm namespace
<%= form_for [:pm, #lease] do |f| %>
<%= f.fields_for :journal_entries do |journal_entries| %>
<%= render "journal_entry_fields" , f: journal_entries %>
<% end %>
<%= link_to_add_fields "+ Add transactions", f, :journal_entries %>
<% end %>
</table>
_journal_entry_fields.html.erb
<fieldset>
<tr>
## HERE IS WHAT I'M LOOKING FOR <<<<<<<<<<<!!>>>>>>>>>>>>>
<td><%= f.parent.text_field :dated %></td>
<td><%= f.parent.text_field :account_name %></td>
<td><%= f.text_field :credit %></td>
<td><%= f.text_field :notes %></td>
</tr>
</fieldset>
My Models
class Lease < ActiveRecord::Base
has_many :journals, :order => [:dated, :id] #, :conditions => "journals.lease_id = id"
has_many :journal_entries, :through => :journals
accepts_nested_attributes_for :journal_entries , :allow_destroy => true
accepts_nested_attributes_for :journals , :allow_destroy => true
end
class Journal < ActiveRecord::Base
belongs_to :lease, :conditions => :lease_id != nil
has_many :journal_entries
accepts_nested_attributes_for :journal_entries , :allow_destroy => true
end
class JournalEntry < ActiveRecord::Base
belongs_to :journal
end
I'm using Rails 3.2.12 and ruby 1.9.3
I'm trying to see if this is a better solution than the problem faced on: rails link_to_add_fields not adding fields with has_many :through (with nested form inside)
I made a different thread because I think it's so drastically different.
Thanks,
Phil
As per my understanding about your use-case you want to create journals and its entries in a single form of Lease. So, you can us the fields_for for both of them as below:
<table>
#in a :pm namespace
<%= form_for [:pm, #lease] do |f| %>
<%= f.fields_for :journals do |journal| %>
<%= render "journal_entry_fields" , f: journal %>
<% end %>
<%= link_to_add_fields "+ Add transactions", f, :journals %>
<% end %>
</table>
_journal_entry_fields.html.erb
<fieldset>
<tr>
<td><%= f.text_field :dated %></td>
<td><%= f.text_field :account_name %></td>
<%= f.fields_for :journal_entries do |journal_entry| %>
<td><%= journal_entry.text_field :credit %></td>
<td><%= journal_entry.text_field :notes %></td>
<% end %>
</tr>
</fieldset>
Though you need to initialise the journal entries every time a new record is added dynamically. I can't help you with this right now as I'm not on my PC.
Try this: http://railscasts.com/episodes/196-nested-model-form-revised
The RailsCasts model relations are similar to your model relations, although you would need to alter the HTML.
RailsCasts Models: Survey > Question > Answer
Your Models: Lease > Journal > JournalEntry

Rails: Multi-model form won't write data to models

Ok this is driving me round the bend. I have three models [which are relevant to this quesiton]: Outfit, Outfit_relationship and Answer. Outfit is the parent model and the others are the childs. The Outfit model looks like this:
class Outfit < ActiveRecord::Base
attr_accessible :user_id, :outfit_origin_id, :outfit_parent_id, :outfitrelationship_id #review before going live
attr_accessible :item_id, :image_size_height, :image_size_width, :image_x_coord, :image_y_coord, :zindex, :outfit_id
attr_accessible :description, :question_id, :user_id, :outfit_id
has_many :answers
has_many :outfit_relationships
accepts_nested_attributes_for :outfit_relationships, :allow_destroy => :true
accepts_nested_attributes_for :answers
Note that the 2nd and 3rd attr_accessible are to access the attributes from the other models. I'm not sure this is absolutely necessary, some articles say it is, some say it isn't, so I put it in.
I've created a multi-model form for this data which I want to publish with one button. Here is the code:
<%= form_for(#outfit) do |post_outfit| %>
<%= post_outfit.fields_for #outfit.outfit_relationships do |build| %>
<table>
<tr>
<td>X Coord <%= build.text_area :image_x_coord, :size => '1x1' %></td>
<td>Y Coord <%= build.text_area :image_y_coord, :size => '1x1' %></td>
<td>Z Index <%= build.text_area :zindex, :size => '1x1' %></td>
<td>Height <%= build.text_area :image_size_height, :size => '1x1' %></td>
<td>Weight <%= build.text_area :image_size_width, :size => '1x1' %></td>
</tr>
</table>
<% end %>
<%= post_outfit.fields_for #outfit.answers do |build| %></br></br>
<%= image_tag current_user.fbprofileimage, :size => "40x40" %></br>
<%= current_user.name %></br>
Comment: <%= build.text_area :description, :size => '10x10' %>
<% end %>
<%= post_outfit.fields_for #outfit do |build| %> </br>
origin id: <%= build.text_area :outfit_origin_id, :size => '1x1' %></br>
parent id: <%= build.text_area :outfit_parent_id, :size => '1x1' %></br>
<% end %>
<div id="ss_QID_actions_container">
<%= post_outfit.submit "Submit checked", :class => "btn btn-small btn-primary" %>
</div>
<% end %>
And here are the relevant buts of the outfit controller:
def new
#outfit = Outfit.new
#outfit.save
#outfit.answers.build
#outfit.outfit_relationships.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #outfit }
end
end
def create
#outfit = Outfit.new(params[:id])
#comment = #outfit.answers.create(params[:answer])
#outfitrelationship = #outfit.outfit_relationships.create(params[:outfit_relationship])
redirect_to outfit_path(#outfit)
So the problem is nothing gets written into my database apart from the IDs. I'm sure I'm dong something stupid here, but can't figure out why.

Resources