Cocoon, nested fields inside nested fields - ruby-on-rails

I have a project, with a Project Model, Main Groups Model and Trades Model, then each project can have many Main Groups, then each Main Group can have many Trades.
I can create the project and the main groups but the Trades are not saving.
project.rb:
class Project < ActiveRecord::Base
belongs_to :user
has_many :consultants
accepts_nested_attributes_for :consultants, :reject_if => :all_blank, :allow_destroy => true
has_many :contractors
accepts_nested_attributes_for :contractors, :reject_if => :all_blank, :allow_destroy => true
has_one :client
accepts_nested_attributes_for :client
has_many :main_groups
accepts_nested_attributes_for :main_groups, :reject_if => :all_blank, :allow_destroy => true
has_many :trades, through: :main_groupsp
end
trade.rb
class Trade < ActiveRecord::Base
belongs_to :main_group
end
main_group.rb
class MainGroup < ActiveRecord::Base
belongs_to :project
has_many :trades
accepts_nested_attributes_for :trades, :reject_if => :all_blank, :allow_destroy => true
end
Main Group From Partial:
.nested-fields.add-tasks
.row
.col-md-12
.form-horizontal
.panel.panel-default
.panel-heading
Main Group
.panel-body
.form-group
= f.label :name, :class => 'col-sm-1 control-label'
.col-sm-11
= f.input :name,:label => false, input_html:{class: "form-input form-control"}
.form-group
= f.simple_fields_for :trades do |trade|
= render 'projects/forms/trade_fields', f: trade
.trade-links.form-group
.col-md-12
= link_to_add_association 'Add Trade', f, :trades, :partial => 'projects/forms/trade_fields', class: "btn btn-default add-button pull-right btn-success"
.form-group
.col-sm-12
= link_to_remove_association 'Remove Main Group', f,class: "btn btn-default pull-right btn-danger"
Trade Form Partial:
.nested-fields.add-tasks
.row
.col-md-12
.form-horizontal
.panel.panel-default
.panel-heading
Trade
.panel-body
.form-group
= f.label :name, :class => 'col-sm-1 control-label'
.col-sm-11
= f.input :name,:label => false, input_html:{class: "form-input form-control"}
.form-group
= f.label :cost, :class => 'col-sm-1 control-label'
.col-sm-11
= f.input :cost,:label => false, input_html:{class: "form-input form-control"}
.form-group
= f.label :start_date, :class => 'col-sm-1 control-label'
.col-sm-11
= f.input :start_date,:label => false, input_html:{class: "form-input form-control"}
.form-group
= f.label :end_date, :class => 'col-sm-1 control-label'
.col-sm-11
= f.input :end_date,:label => false, input_html:{class: "form-input form-control"}
.form-group
.col-sm-12
= link_to_remove_association 'Remove Trade', f,class: "btn btn-default pull-right btn-danger"

Related

Rails 5 inverse_of join table doesn't want to work with deeply nested Cocoon fields

I am using Ruby 2.6.5 and Rails 5.2.3. I have a form with double nested objects with Cocoon. The form is for Preorders. A Preorder can have many Dancers. Dancers can have many and belong to many Groups, so I set up a join table called DancerGroup.
I use Cocoon to add dancers_attributes to the Preorder form, and again to add dancer_groups_attributes inside of the dancers_attributes fields.
My problem is that when I attempt to save a Preorder with a new Dancer and new DancerGroup associations I get the following error:
Dancers dancer groups dancer can't be blank
Which clearly means it's trying to save the dancer_group object before the dancer record has an id The obvious answer is that since I'm using Rails 5, the belongs_to association is required by default, and the work around is to properly use inverse_of and accepts_nested_attributes_for methods to let Rails manage saving everything all at once. But it's not working when it comes to my join table. I even get the same error when I set optional: true on the belongs_to :dancer in the DancerGroup model.
My strong params look like this:
private
def preorder_params
params.require(:preorder).permit( :event_id,
:dancers_attributes => [
:_destroy,
:id,
:name,
:dancer_groups_attributes => [
:_destroy,
:group_id,
:id ]
]
)
end
My models look like this:
app/models/preorder.rb
class Preorder < ApplicationRecord
belongs_to :event
has_many :dancers, dependent: :destroy, inverse_of: :preorder
has_many :dancer_groups, through: :dancers, dependent: :destroy
with_options reject_if: :all_blank, allow_destroy: true do
accepts_nested_attributes_for :dancers
accepts_nested_attributes_for :dancer_groups
end
validates :dancers, :length => { minimum: 1 }
...
end
app/models/dancer.rb
class Dancer < ApplicationRecord
belongs_to :preorder, counter_cache: true, inverse_of: :dancers
has_many :dancer_groups, inverse_of: :dancer, dependent: :destroy
has_many :groups, through: dancer_groups
with_options reject_if: :all_blank, allow_destroy: true do
accepts_nested_attributes_for :dancer_groups
end
...
end
app/models/dancer_group.rb
class DancerGroup < ApplicationRecord
belongs_to :dancer, inverse_of :dancer_groups
belongs_to :group, inverse_of :dancer_groups
...
end
And here's what my form files look like
views/admin/preorders/_form.html.haml
...
%fieldset
%legend Dancers
#dancers
- #preorder.dancers.each do |dancer|
= f.fields_for :dancers, dancer do |ff|
= render "dancer_fields", f: ff, preorder: #preorder
= link_to_add_association f, :dancers,
{ "data-association-insertion-node": "#dancers",
"data-association-insertion-method": "append",
render_options: { locals: { preorder: #preorder } },
class: "btn btn-info btn-sm btn-simple float-right" } do
= icon "fas", "plus"
Add Dancer
...
views/admin/preorders/_dancer_fields.html.haml
...
.card.preorder_dancer
.card-header.row
.col
%h4.card-title Dancer Info
.col-auto
= link_to_remove_association f, { wrapper_class: "preorder_dancer",
class: "btn btn-sm btn-icon btn-simple btn-danger" } do
= icon "fas", "times"
.card-body
.row.pb-3
.col
= f.label :name do
Full Name
%span.text-danger *
= f.text-field :name, class: "form-control"
%h4.card-title Dance Groups
.row{ :id => "dancer_groups_#{f.object.id}" }
- f.object.dancer_groups.each do |dancer_group|
= f.fields_for :dancer_groups, dancer_group do |ff|
= render "dancer_group_fields, f: ff,
preorder: preorder
=link_to_add_association f, :dancer_groups,
{ "data-association-insertion-node": "#dancer_groups_#{f.object.id}",
"data-association-insertion-method": "append",
render_options: {locals: { preorder: preorder } },
class: "btn btn-info btn-sm btn-simple float-right" } do
=icon "fas", "plus"
Add Dance Group
views/admin/preorders/_dancer_group_fields.html.haml
.col-lg-6.preorder_dancer_group
.card.bg-dark
.card-header.row
.col
%h4.card-title Dance Group
.col.text-right
=link_to_remove_association f, { wrapper_class: "preorder_dancer_group",
class: "btn btn-sm btn-icon btn-simple btn-danger"} do
=icon "fas", "times"
.card-body
.row
.col
=f.label :group_id do
Group
%span.text-danger *
=f.collection_select :group_id,
preorder.event.groups,
:id,
:name,
{include_blank: "Select Group"},
{class: "form-control"}
In your _dancer_fields partial you write
%h4.card-title Dance Groups
.row{ :id => "dancer_groups_#{f.object.id}" }
- f.object.dancer_groups.each do |dancer_group|
= f.fields_for :dancer_groups, dancer_group do |ff|
= render "dancer_group_fields, f: ff,
preorder: preorder
and that should be
%h4.card-title Dance Groups
.row{ :id => "dancer_groups_#{f.object.id}" }
= f.fields_for :dancer_groups do |ff|
= render "dancer_group_fields, f: ff,
preorder: preorder
Because otherwise you are not making the dancer-group "connected" to the dancer.

Select and save multiple Checkboxes with simple for

I'm trying to implement a multiple select and save with simple form and nested forms.
My view shows all the branches with name and description in a card format (Bootstrap). Next to te name I would like to have a checkbox. I would like to save only the selected ones
= simple_form_for(#my_branch, html: { class: 'form-horizontal' }) do |f|
= f.error_notification
= f.input :user_id, input_html: { value: current_user.id}, as: :hidden
- #branches.each do |branch|
= f.simple_fields_for :my_branch_items do |b|
.col-md-4
.card{:style => "width: 18rem;"}
.card-header
= branch.name
= b.input :branch_id, input_html: { value: branch.id }, as: :radio_buttons
%ul.list-group.list-group-flush
= branch.description
.form-group
= f.submit 'Save', class: 'btn btn-primary'
Here are the associations
class Branch < ApplicationRecord
has_many :my_branch_items
end
class MyBranch < ApplicationRecord
belongs_to :user
has_many :my_branch_items
accepts_nested_attributes_for :my_branch_items, allow_destroy: true, reject_if: proc { |att| att['name'].blank? }
end
class MyBranchItem < ApplicationRecord
belongs_to :my_branch
belongs_to :branch
end
And the Controller
class MyBranchesController < BaseController
before_action :set_my_branch, only: [:show]
def new
#branches = Branch.all
#my_branch = MyBranch.new
end
def create
#my_branch = Quiz.new(my_branch_params)
if #my_branch.save
redirect_to my_branch_path(#my_branch), notice: 'Thanks for taking the Branch Quiz'
else
render :new
end
end
def show
end
private
def set_my_branch
#my_branch = MyBranch.find(params[:id])
end
def my_branch_params
params.require(:my_branch).permit(:name, :note, :user_id, my_branch_items_attributes: MyBranchItem.attribute_names.map(&:to_sym).push(:_destroy))
end
end
Fixed!
Form
= simple_form_for(#my_branch, html: { class: 'form-horizontal' }) do |f|
= f.error_notification
= f.input :user_id, input_html: { value: current_user.id}, as: :hidden
- #branches.each do |branch|
= f.simple_fields_for :my_branch_items do |b|
.col-md-4
.card{:style => "width: 18rem;"}
.card-header
= branch.name
= b.input_field :branch_id, checked_value: branch.id, as: :boolean, boolean_style: :inline, include_hidden: false
%ul.list-group.list-group-flush
= branch.description
.form-group
= f.submit 'Save', class: 'btn btn-primary'
Model was using a validation for blanks field that did not exist in the table.
class MyBranch < ApplicationRecord
belongs_to :user
has_many :my_branch_items, dependent: :destroy
accepts_nested_attributes_for :my_branch_items, allow_destroy: true, reject_if: proc { |att| att['branch_id'].blank? }
end

Strong parameters not whitelisting attributes with simple_form

I'm having some issues understanding how to nest 3 models. I'm trying to, at the deepest point of the relations, add a Video to the WorkoutSteps (not creating a new video but select an existing one from a dropdown)
Models:
Workout
WorkoutSet
WorkoutStep -> just an array of Video
Video
Relationships
Workout:
has_and_belongs_to_many :workout_sets, :join_table => :workout_sessions, dependent: :destroy
WorkoutSet
has_and_belongs_to_many :workout_steps, :join_table => :sets_steps, dependent: :destroy
WorkoutStep
has_and_belongs_to_many :workout_sets, :join_table => :sets_steps
And the following in the views:
_form.html.haml
= simple_form_for(#workout, url: admin_workouts_path(#workout)) do |f|
= f.input :title
%h3 Sets
.sets.some{ :style => "margin-left: 25px" }
= f.simple_fields_for :workout_sets do |set|
= render 'workout_set_fields', f: set
.links
= link_to_add_association 'add set', f, :workout_sets
= f.submit
_workout_set_fields
= f.label :title
= f.text_field :title
%br
%br
#sets.some{ :style => "margin-left: 25px" }
= f.simple_fields_for :workout_steps do |step|
= render 'workout_step_fields', f: step
.links
= link_to_add_association 'add step', f, :workout_steps
_workout_step_fields
= f.association :main_videos, include_hidden: false
workouts_controller.rb
def workout_params
params.require(:workout).permit(:title, :pro, :workout_step_id, workout_sets_attributes: [ :id, :_destroy, :title, workout_steps_attributes: [ main_video_ids: [:id] ] ])
end
Checking the params:
FYI, the problem was solved as I stated on the comment above.
params.require(:workout).permit(:title, :pro, :workout_step_id, workout_sets_attributes: [ :id, :_destroy, :title, workout_steps_attributes: [ {main_video_ids: []} ]])
for more info, check this link
main_video_ids is an array attribute, not a hash of ids. So you can whitelist it directly:
def workout_params
params.require(:workout).permit(:title, :pro, :workout_step_id, workout_sets_attributes: [ :id, :_destroy, :title, workout_steps_attributes: main_video_ids: [] ] ])
end

Simpleform fields_for and nested_routes not working on Rails 4

I'm trying to get this fields_for on simple_form working with Rails 4:
Below is my form:
= simple_form_for(hall_webcasts_path(#hall), :html => { :class => "form-horizontal" }, :wrapper => "horizontal", defaults: { :input_html => { class: "form-control"}, label_html: { class: "col-lg-4" } } ) do |f|
= f.error_notification
= f.simple_fields_for #webcast do |b|
.form-inputs
= b.input :name
= b.association :hall, prompt: "Choose a hall", :input_html => { class: "chosen" }
= b.input :webcast_type
= b.input :media_type
= b.association :user, prompt: "Choose a user", :input_html => { class: "chosen" } if current_user.has_role? :admin
= b.input :user, as: :hidden, :value => current_user.id if !current_user.has_role? :admin
= b.input :details
= b.association :template, :input_html => { class: "chosen" }
= b.input :background_colour, :input_html => { "colourpicker" => "true" }
= b.input :start, as: :string, :input_html => { class: "datetimepicker form-control" }
= b.input :finish, as: :string, :input_html => { class: "datetimepicker form-control" }
.form-actions
= b.button :submit, class: "btn btn-primary"
Below is my routes:
resources :venues, shallow: true do
#Halls
get "hall/:id/exhibition" => "halls#exhibition", as: :exhibition
get "hall/:id/visit" => "halls#visit", as: :hall_visit
get "structure", :to => "venues#venue_structure"
resources :asset_types, :booths_tags, :tags, :uploaded_files, :events, :chats
resources :halls do
resources :webcasts
resources :booths do
resources :chats
end
end
end
This however always create:
/halls/1/webcasts/new
In my form action and something like below:
/halls/1/webcasts[webcast][name]
in my input fields name attributes.
Is there a way to fix this issue?
class Webcast < ActiveRecord::Base
belongs_to :hall
belongs_to :user
belongs_to :template
validates :name, :hall, :user, :template, presence: :true
end
I also added webcast model for reference

Multiple Level Nested Form Can't mass-assign protected attributes:

I'm having a huge problem here with Rails. The thing is, that I have 32 models that are associated with one main model.
The model is called Problem - each problem has a leveloneeffect, levelonecause, leveloneend, leveloneway.
Now each of the previous level, has another models associated with them like leveltwoeffect is associated with leveloneeffect and so on..
Each model is correctly associated with other and the form shows all the label and texfields correctly to submit; but when I do submit I get the following error:
Can't mass-assign protected attributes: levelonecause_id
app/controllers/problems_controller.rb:27:in `update'
Now in the controller I have the follow code in the 27 line
def update
#problem = Problem.find(params[:id])
#problem.update_attributes(params[:problem]) <--- LINE 27
flash[:notice] = "Programa editado correctamente."
redirect_to edit_problem_path(#problem)
end
Any ideas?
Here is my problemcontroller
def index
#problem = Problem.all
end
def new
#problem = Problem.new
end
def create
#problem = Problem.new(params[:problem])
#problem.user_id = current_user.id
#problem.save
flash[:notice] = "Prorgrama Creado."
redirect_to edit_problem_path(#problem)
end
def show
#problem = Problem.find(params[:id])
end
def edit
#problem = Problem.find(params[:id])
end
def update
#problem = Problem.find(params[:id])
#problem.update_attributes(params[:problem])
flash[:notice] = "Programa editado correctamente."
redirect_to edit_problem_path(#problem)
end
def destroy
#problem = Problem.find(params[:id])
#Problem.destroy
flash[:notice] = "Programa borrado correctamente."
redirect_to problems_path
end
end
Add here is the problem model
attr_accessible :leveltwocause_attributes,:indicator_attributes, :leveloneeffect_attributes, :levelonecause_attributes, :budget_program, :city, :department, :email, :name, :responsable_unit, :init_date, :end_date, :organism, :definition, :idea_attributes
belongs_to :user
has_many :idea
has_many :levelonecause
has_many :leveltwocause, :through => :levelonecause
has_many :levelthreecause, :through => :leveltwocause
has_many :levelfourcause, :through => :levelthreecause
has_many :levelfivecause, :through => :levelfourcause
has_many :levelsixcause, :through => :levelfivecause
has_many :levelsevencause, :through => :levelsixcause
has_many :leveleightcause, :through => :levelsevencause
has_many :indicator
has_many :leveloneeffect
has_many :leveltwoeffect, :through => :leveloneeffect
has_many :levelthreeeffect, :through => :leveltwoeffect
has_many :levelfoureffect, :through => :levelthreeeffect
has_many :levelfiveeffect, :through => :levelfoureffect
has_many :levelsixeffect, :through => :levelfiveeffect
has_many :levelseveneffect, :through => :levelsixeffect
has_many :leveleighteffect, :through => :levelseveneffect
accepts_nested_attributes_for :idea, :allow_destroy => true
accepts_nested_attributes_for :levelonecause, :allow_destroy => true
accepts_nested_attributes_for :leveltwocause, :allow_destroy => true
accepts_nested_attributes_for :levelthreecause, :allow_destroy => true
accepts_nested_attributes_for :levelfourcause, :allow_destroy => true
accepts_nested_attributes_for :levelfivecause, :allow_destroy => true
accepts_nested_attributes_for :levelsixcause, :allow_destroy => true
accepts_nested_attributes_for :levelsevencause, :allow_destroy => true
accepts_nested_attributes_for :leveleightcause, :allow_destroy => true
accepts_nested_attributes_for :leveloneeffect, :allow_destroy => true
accepts_nested_attributes_for :leveltwoeffect, :allow_destroy => true
accepts_nested_attributes_for :levelthreeeffect, :allow_destroy => true
accepts_nested_attributes_for :levelfoureffect, :allow_destroy => true
accepts_nested_attributes_for :levelfiveeffect, :allow_destroy => true
accepts_nested_attributes_for :levelsixeffect, :allow_destroy => true
accepts_nested_attributes_for :levelseveneffect, :allow_destroy => true
accepts_nested_attributes_for :leveleighteffect, :allow_destroy => true
accepts_nested_attributes_for :indicator, :allow_destroy => true
end
Here is the form
<%= simple_nested_form_for #problem, :html => { :class => " widgetbox box-inverse form-horizontal" } do |f| %>
<div class="tabbable"> <!-- Only required for left/right tabs -->
<ul class="nav nav-pills">
<li class="active">Definicion del Problema</li>
<li>Arbol del Problema</li>
<li>Arbol de Objetivos</li>
<li>Matriz Marco Logico</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade in active" id="definicion">
<ul class="nav nav-tabs">
<li>Informacion Basica</li>
<li>Definicion del Problema</li>
<li>Lluvia de Ideas</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade" id="infbasica">
<div class="span4 offset1">
<%= f.input :name, label: 'Nombre del Programa' %>
<%= f.input :organism, label: 'Dependencia', collection: ["Dependencia 1", "Dependencia 2", "Dependencia 3"] %>
<%= f.input :responsable_unit, label: 'Nombre del Responsable del Programa' %>
<%= f.input :city, label: 'Ciudad', collection: [ "Chihuahua", "DF", "Toluca", "Juarez"] %>
<%= f.input :department, label: 'Departamento', :collection => [ "Direccion Tecnica", "Proyectos Especiales", "Sub Direccion de Estudios"] %>
<%= f.input :email, label: 'Correo Electronico', placeholder: 'usuario#correo.com' %>
</div>
<div class="span4 offset1">
<%= f.input :init_date, label: 'Fecha problable de Inicio'%>
<%= f.input :end_date, label: 'Fecha problable de Terminacion' %>
</div>
</div>
<div class="tab-pane fade" id="nombrar">
<div class="span6 offset1">
<%= f.input :definition, label: 'Definicion del Problema', as: :text %>
</div>
</div>
<div class="tab-pane fade" id="lluvia">
<div class="span6 offset1">
<%= f.simple_fields_for :idea do |i| %>
<%= i.input :content, label: 'Idea', as: :text, required: false %>
<%= i.link_to_remove "Quitar", :class => 'btn btn-danger' %>
<% end %>
<%= f.link_to_add "Agregar Idea", :idea %>
</div>
</div>
</div>
</div>
<div class="tab-pane fade" id="arbolproblema">
<ul class="nav nav-tabs">
<li>Causas</li>
<li>Efectos</li>
<li>Generar Diagrama</li>
</ul>
<div class="tab-content">
<div class="tab-pane" id="causas">
<div class="span6 offset1">
<%= f.simple_fields_for :levelonecause do |levelone| %>
<%= levelone.input :content, label: 'Causa', as: :text, required: false %>
<%= levelone.link_to_remove "Quitar", :class => 'btn btn-danger' %>
<% end %>
<%= f.link_to_add "Agregar Causa del nivel 1", :levelonecause %>
</div>
</div>
<div class="tab-pane" id="efectos">
<div class="span6 offset1">
<%= f.simple_fields_for :leveltwocause do |leveltwocause| %>
<%= leveltwocause.input :content, label: 'Efect', as: :text, required: false %>
<%= leveltwocause.link_to_remove "Quitar", :class => 'btn btn-danger' %>
<%= leveltwocause.label :levelonecause_id, "Efecto" %>
<%= leveltwocause.collection_select(:levelonecause_id, Levelonecause.all, :id, :content) %>
<% end %>
<%= f.link_to_add "Agregar Efecto del nivel 1", :leveltwocause %>
</div>
</div>
</div>
</div>
</div>
<%= f.button :submit, 'Guardar Datos', class: 'btn btn-primary pull-right' %>
<% end %>
</div>
EDIT
OK guys i removed the trough in my model and it works! does anyone knows why?
This new Rails version is killing me
Try to add :levelonecause_id to attr_accessible list of levelonecause model and the other models that has it too.

Resources