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
Related
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.
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
A question has_many kpi through kpiquestions:
class Question < ActiveRecord::Base
has_many :kpiquestions, dependent: :destroy
has_many :kpis, through: :kpiquestions
end
class Kpi < ActiveRecord::Base
has_many :kpiquestions, dependent: :destroy
has_many :questions, through: :kpiquestions
end
class Kpiquestion < ActiveRecord::Base
belongs_to :kpi
belongs_to :question
end
Now I have a working question form where I can add KPI's by using checkboxes:
= form_for #question do |f|
= render "layouts/form_messages", target: #question
.form-group
= f.label "Name"
= f.text_field :name
.form-group
= f.label "KPI's"
%ul
= hidden_field_tag "question[kpi_ids][]", nil
- #kpis.each do |kpi|
%li.checkbox
= check_box_tag "question[kpi_ids][]", kpi.id, #question.kpi_ids.include?(kpi.id), "data-category" => kpi.id
= kpi.name
.form-group
= f.submit(class: 'btn btn-default', value: "Save")
But the join table kpiquestion has an additional attribute weight. This attribute should also be added to the form, with a text_field_tag. Something like this:
= text_field_tag "question[kpi_ids][:weight]", kpi.weight, #question.kpi_ids.include?(kpi.id), "data-category" => kpi.weight
Obviously this doesn't work, because it now returns an error saying kpi has no attribute called weight. So how can I add this attribute to the form?
One way would be to make the entire form_for into a form_tag that passes to a post request in the relevant controller, then the only thing limiting which parameters you can pass would be the the params you've chosen to allow controller-side. Obviously this is against convention, but it works great for cases like this.
Here is what the code would change to:
= form_tag question_index_path, :method => :post do
= render "layouts/form_messages", target: #question *
.form-group
= label_tag "Name"
= text_field_tag :name
.form-group
=label_tag :weight
=text_field_tag :weight
.form-group
=label_tag "KPI's"
= hidden_field_tag "question[kpi_ids][]", nil
- #kpis.each do |kpi|
%li.checkbox
= check_box_tag "question[kpi_ids][]", kpi.id, #question.kpi_ids.include?(kpi.id), "data-category" => kpi.id
= kpi.name
.form-group
=submit_tag("Save", class: btn btn-default)
I am trying to create a workout tracker. I have these nested resources
Routes.rb
resources :days do
resources :workouts
resources :meals
end
My models look fine:
Workout.rb
class Workout < ActiveRecord::Base
has_many :exercises
belongs_to :day
end
Day.rb
class Day < ActiveRecord::Base
has_many :workouts
has_many :meals
accepts_nested_attributes_for :workouts, :allow_destroy => true
accepts_nested_attributes_for :meals, :allow_destroy => true
end
The issue lies when I try to create a new workout..
../views/workouts/_form.html.haml
= form_for([#day, #workout]), html: {class: 'form-horizontal'}) do |f|
= f.input :workout, label: "What did you work out", input_html: { class: "form-control"}
= f.input :mood, label: "How do you feel", input_html: { class: "form-control"}
= f.hidden_field :day_id, value: params[:day_id], input_html: { class: "form-control"}
= f.submit
I cant seem to save the :day_id the workout is associated with the workout though using strong params to receive them.
WorkoutsController.rb
private
def workout_params
params.require(:workout).permit(:day_id, :name, :mood)
end
def find_day
#day = Day.find(params[:day_id])
end
Instead what gets saved is a nil for :day_id
Rails Console:
Workout id: 11, name: "Hey", mood: "no", day_id: nil, created_at: "2014-12-19 14:53:29", updated_at: "2014-12-19 14:53:29"
HELP?
PS - i tried to do
= form_for([#day, #workout]), as: :foo, html: {class: 'form-horizontal'}) do |f|
= f.input :workout, label: "What did you work out", input_html: { class: "form-control"}
= f.input :mood, label: "How do you feel", input_html: { class: "form-control"}
= f.hidden_field :day_id, value: params[:day_id], input_html: { class: "form-control"}
= f.submit
and then
def workout_params
params.require(:foo).permit(:day_id, :name, :mood)
end
But it just kept saying the :foo params where empty
The way your routes are set up, I am assuming that the day_id is being passed as a url params:
/days/:day_id/workouts
If that is the case, then you do not need to worry about #day in the form, nor the day_id in the workout_params
Form:
= form_for #workout, html: {class: 'form-horizontal'}) do |f|
= f.input :workout, label: "What did you work out", input_html: { class: "form-control"}
= f.input :mood, label: "How do you feel", input_html: { class: "form-control"}
= f.submit
Controller:
def create
#workout = #day.workouts.new(workout_params)
....
end
private
def workout_params
params.require(:workout).permit(:name, :mood)
end
def find_day
#day = Day.find(params[:day_id])
end
I have searched on stack overflow and google to no avail.
I have a person who has_one next_of_kin
I can create a person with a nested form (with cocoon) and it saves perfectly. For some reason when I then go to the edit page it deletes the associated next_of_kin record. It renders the fields populated with the record's data, but the actual record in the database gets deleted.
My form
.full-width-row
= simple_form_for #person, url: {action: action}, wrapper: 'two_column_form' do |f|
.columns.medium-4
h4 = heading
.columns.medium-8
= f.button :submit, 'Save', class: 'right button tiny radius'
.columns.medium-12
.row
.medium-8.columns
= f.input :first_name
= f.input :last_name
= f.input :email
br
h6 Next of Kin
br
= f.simple_fields_for :next_of_kin do |nok|
= render 'next_of_kin_fields', f: nok
.link
= link_to_add_association "Add Next of Kin", f, :next_of_kin, class: 'button secondary tiny next_of_kin_button'
hr
My _next_of_kin_fields partial
.nested-fields
= f.input :first_name
= f.input :last_name
= f.input :relationship, as: :select, collection: NextOfKin::RELATIONSHIP
= f.input :telephone
= link_to_remove_association "Remove next of kin", f, class: 'remove-association button tiny alert'
My Person model:
class Person < ActiveRecord::Base
has_one :next_of_kin, dependent: :destroy
accepts_nested_attributes_for :next_of_kin, allow_destroy: true
end
My Next_of_kin model:
class NextOfKin < ActiveRecord::Base
belongs_to :person
RELATIONSHIP = [ "Mother", "Father", "Brother", "Sister", "Aunt", "Uncle", "Spouse", "Other"]
end
How do I stop it from deleting the next_of_kin record when I visit the edit page?
set force_non_association_create in link_to_add_association to true to avoid this
= link_to_add_association "Add Next of Kin", f, :next_of_kin, force_non_association_create: true, class: 'button secondary tiny next_of_kin_button'
Cocoon's documentation for this parameter: https://github.com/nathanvda/cocoon#force_non_association_create