rails: Updating a record nested two join tables deep - ruby-on-rails

I have a problem that's really confusing me.
I have four models, a Workout model, an Exercise model, a workout_exercise join model, and a workout_exercise_set model.
I can add exercises to workouts through the workout_exercises join table. Now I'm trying to add sets to exercises in workouts, which is what the workout_exercises_sets table is for.
Here's an example.
Workout
Exercise 1
Set 1
Set 2
Set 3
Exercise 2
Set 1
Set 2
Set 3
Exercise 3
etc.
In workouts/edit.html.erb I have a form where I can see all the exercises in the workout, and edit the number of sets by using form_for and fields_for, but when I try to update an exercise or the entire workout I get a MassAssignmentSecurity::Error in WorkoutsController#update that says Can't mass-assign protected attributes: workout_exercise_sets. I can't figure out how to get past this. I know I'm editing a workout, but I'm not trying to write to a field called workout_exercise_sets, so I'm really confused here. I really appreciate any guidance. All relevant code is below.
workout/edit.html.erb:
<%= form_for(#workout) do |f| %>
<%= f.label :name %><br />
<%= f.text_field :name %><br />
<%= f.label :description %><br>
<%= f.text_area :description %></br>
<%= f.fields_for :workout_exercises do |s| %>
<%= s.object.exercise.name %></b>
<%= s.fields_for :workout_exercise_sets do |set| %>
<%= set.label :set_number %>:
<%= set.number_field :set_number %>
<%= set.label :reps %>:
<%= set.number_field :repetitions %>
<%= set.label :rest_time %>(seconds):
<%= set.number_field :rest_time %>
<%= set.submit %>
<% end %>
<%= s.hidden_field :_destroy %>
<%= link_to "Remove exercise?", '#', class: "remove_fields" %>
<% end %>
<%= f.submit %>
<% end %>
Here is the workout model:
class Workout < ActiveRecord::Base
attr_accessible :name, :exercises_attributes, :workout_exercises_attributes, :exercise_order, :description
has_many :workout_exercises, dependent: :destroy, :order => "exercise_order DESC"
has_many :exercises, through: :workout_exercises
accepts_nested_attributes_for :exercises
accepts_nested_attributes_for :workout_exercises, allow_destroy: :true
end
Here is the exercise model:
class Exercise < ActiveRecord::Base
attr_accessible :name, :description
has_many :workout_exercises
has_many :workouts, through: :workout_exercises
validates :name, uniqueness: :true, presence: :true
validates :description, uniqueness: :true, presence: :true
end
Here is the workout_exercise model:
class WorkoutExercise < ActiveRecord::Base
attr_accessible :exercise_id, :workout_id
belongs_to :exercise
belongs_to :workout
has_many :workout_exercise_sets, dependent: :destroy
accepts_nested_attributes_for :workout_exercise_sets, allow_destroy: :true
end
and finally, here is the workout_exercise_sets model:
class WorkoutExerciseSet < ActiveRecord::Base
attr_accessible :repetitions, :rest_time, :set_number, :workout_exercise_id
belongs_to :workout_exercise
end
And for good measure, here is a diagram of the DB:

I think in your workout_excercise.rb file, you should add :workout_exercise_sets_attributes to your attr_accessible list.
class WorkoutExercise < ActiveRecord::Base
attr_accessible :exercise_id, :workout_id, :workout_exercise_sets_attributes
belongs_to :exercise
belongs_to :workout
has_many :workout_exercise_sets, dependent: :destroy
accepts_nested_attributes_for :workout_exercise_sets, allow_destroy: :true
end

Related

Many to many association form with collection_check_boxes results in validation error

I have three models: Course, Category, and Categorisation.
# course.rb
class Course < ApplicationRecord
has_many :categorisations, foreign_key: "course_id", dependent: :destroy
has_many :categories, through: :categorisations
end
#category.rb
class Category < ApplicationRecord
has_many :categorisations, foreign_key: "category_id", dependent: :destroy
has_many :courses, through: :categorisations
end
# categorisation.rb
class Categorisation < ApplicationRecord
belongs_to :course
belongs_to :category
validates :course_id, presence: true
validates :category_id, presence: true
end
Course controller has the following params:
def course_params
params.require(:course).permit(:name, :prerequisite, :description,
:user_id, :category_ids => [])
end
I'm trying to build the association via the Course form:
<%= form_for #course do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :prerequisite %>
<%= f.text_field :prerequisite, class: 'form-control' %>
<%= f.label :description %>
<%= f.text_area :description, class: 'form-control' %>
<%= f.label :category_ids, "Category" %>
<%= f.collection_check_boxes :category_ids, Category.all, :id, :name,
{}, { multiple: true, class: 'form-control'} %>
<%= f.submit "Create a new course", class: "btn btn-primary" %>
<% end %>
However when I click submit I get the validation error:
Categorisations is invalid
I'm unsure why this is the case?
Removing the presence validation on :course_id will fix the issue
class Categorisation < ApplicationRecord
belongs_to :course
belongs_to :category
# validates :course_id, presence: true
validates :category_id, presence: true
end
Problem:
Course is not saved but due to through association when you associate category with course rails first tries to save categorisation but course_id is not available yet
And this is the reason presence validation fails for course_id
Solution
Removing the validation is just a workaround the actual solution will be something interesting
NOTE: Even I am facing the similar issue a few days back. we have removed validation for now. I will be interested in any better solution
You're not able to save any entity that relates to Categorization in database because of presence: true action. Firstly, I think it's a better idea to validate :category_id in the schema.rb by category_id null: false. And secondly, are you sure you need the Categorization model? My suggestion is to think of putting a categorizationable.rb file in your yourApp/app/models/concerns folder and after that using its functionality in the existing models. You can read more about concerns here. This technique is really powerful when your models need refactoring.

Creating an object with a nested form - how to pass params to controller

I'm really struggling with how to make a form create an object, and two sub-objects. My Guide has_one City_obj and has_one Country_obj.
guide.rb
class Guide < ActiveRecord::Base
has_one :city_obj, dependent: :destroy
has_one :country_obj, dependent: :destroy
belongs_to :user
has_many :guide_pics, dependent: :destroy
accepts_nested_attributes_for :city_obj, :country_obj
end
city_obj.rb
class CityObj < ActiveRecord::Base
belongs_to :guide
belongs_to :country_obj
end
country_obj.rb
class CountryObj < ActiveRecord::Base
belongs_to :guides
has_many :city_obj, dependent: :destroy
end
new.html
<%= form_for(#guide) do |f| %>
<%= render 'new_form_part', f: f, field: 'name', label: 'name', type: 'text_field',
<%= f.fields_for :country_obj, #guide.country_obj do |p| %>
<%= f.text_field :name, class: 'form-control' %>
<% end %>
<%= f.fields_for :city_obj, #guide.city_obj do |p| %>
<%= f.text_field :name, class: 'form-control' %>
<% end %>
<% end %>
In my controller create method, I get
params[:guide] = {"name"="whatever is submitted for city"}
That's because the partial that sets :name is being overwritten by the text_fields below. How can I get them to pass params[:guide][:city_obj][:name] and params[:guide][:country_obj][:name]?
UPDATE: There is a typo. I should be using p.text_field, not f.text_field

Nested form with values from model

I have a workout model:
class Workout < ActiveRecord::Base
attr_accessible :time
belongs_to :user
has_and_belongs_to_many :trainers
accepts_nested_attributes_for :trainers
end
And a trainer model:
class Trainer < ActiveRecord::Base
attr_accessible :name
validates_uniqueness_of :name
has_and_belongs_to_many :workouts
end
I need to have a nester trainer form, which allows to pull values from database.
Now I have this inside a new workout form:
<%= f.fields_for :trainers do |builder| %>
<%= builder.select :trainer, options_for_select(Trainer.all.collect{ |u| [u.name, u.id] }) %>
<br>
<% end %>
I get "undefined method `trainer' for #"
What am I doing wrong?
I forgot to add attr_accessible :trainers_attributes to Workout controller

using a validation with has_many_through

I want to set up a validation so that people can't submit a post unless they click on a category for that post and also to make sure that they can chose only one of the categorys for that post so the post can have only one category. Here are the modles
class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :categorizations
has_many :posts, :through => :categorizations
end
class Comment < ActiveRecord::Base
belongs_to :user
validates :content, presence: true,
length: { minimum: 5 }
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
has_many :categorizations
has_many :categories, :through => :categorizations
validates :title, :content, presence: true,
length: { minimum: 5, maximum: 140 }
end
also here is my form:
<%= form_for #post, :html => {:multipart => true} do |f| %>
<% if #post.errors.any? %>
<div id="error_explanation" class="animated tada">
<h2><%= pluralize(#post.errors.count, "error") %> prohibited this post from being saved:</h2>
<ul>
<% #post.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.file_field :image %>
</div>
<div class="field">
<%= f.label :content %><br>
<%= f.text_area :content %>
</div>
<%= hidden_field_tag "post[category_ids][]", nil%>
<% Category.all.each do |category| %><br>
<%= check_box_tag "post[category_ids][]", category.id, #post.category_ids.include?(category.id), id: dom_id(category)%>
<%= label_tag dom_id(category), category.name %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
</div>
do any of you know what I would need to put in to make my form come back with a message saying need to select category or can pick only one category?
We've done a similar thing with has_many :through
Validating On The Join Model
Our solution was to use accepts_nested_attributes_for, and validate on the join model. This could be seen as quite inefficient, but works well for us:
#app/models/message.rb
Class Message < ActiveRecord::Base
validates :title, :body,
:presence => { :message => "Needs A Value!" }
accepts_nested_attributes_for :message_subscribers, :allow_destroy => true
end
#app/models/message_subscriber.rb
Class MessageSubscriber < ActiveRecord::Base
#Validations
validates :subscriber_id,
:presence => { :message => "Your Message Needs Subscribers!" }
end
This returns an error if you don't have any subscriber_id's selected
Your Code
For you, I'd be tempted to do this:
class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
#Validation
validates :subscriber_id, :presence => { :message => "You need to select a category!" }
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
has_many :categorizations
has_many :categories, :through => :categorizations
#Validation
validates :title, :content, presence: true,
length: { minimum: 5, maximum: 140 }
end
I was going to include accepts_nested_attributes_for for you, but I realized it would be an inconvenient way to do something you're doing already. In light of this, I believe you may be best suited just performing the validations in your join model, however if it does not work, we'll add the accepts_nested_attributes_for functionality

Rails Nested Form - Can't mass-assign protected attributes:

I've used the nested_form gem, whenever i try and submit something to my form i get the Can't mass-assign protected attributes:items message, even though i've already put attr_accessible in my models.
Form:
<%= nested_form_for(#goods_in) do |f| %>
...
<%= f.fields_for :items do |i| %>
<td><%= i.text_field :description, :autocomplete => :off%></td>
<td><%= i.text_field :quantity, :autocomplete => :off %></td>
<th><%= i.link_to_remove "Remove this item" %></th>
<% end %>
<%= f.submit :"Submit Delivery" %>
<% end %>
Goods In Model:class GoodsIn < ActiveRecord::Base
belongs_to :supplier
has_many :items
attr_accessible :c4lpono,
:courier,
:deliverydate,
:deliverynoteno,
:destination,
:notes,
:quantity,
:signedby,
:supplier_id,
:partcode_ids
accepts_nested_attributes_for :supplier
validates :c4lpono,
:deliverydate,
:deliverynoteno,
:destination,
:quantity,
:signedby,
:presence =>true
end
Item Model
class Item < ActiveRecord::Base
belongs_to :goods_in
attr_accessible :quantity,
:partcode,
:description,
:goods_in_id
accepts_nested_attributes_for :goods_in
end
Goods In Controller:
def create
#goods_in = GoodsIn.new(params[:goods_in])
end
you have to add
attr_accessible :items_attributes
And here's a link to the documentation :)
I think there's an error in your Goods model.
It should read has_many :items instead of has_many :item.
It's hard to tell what you are trying to achieve with your models but I think you want the folllowing:
Your GoodsIn needs to have the relationship for accepts_nested_attributes_for :items . The belongs_to and accepts_nested_attributes_for is off.

Resources