Skip has_many :through Model Creation on No Change - ruby

This could be the wrong way to go about this entirely, and I'm very open to alternatives.
I've got the following models, where Users can have many Positions:
class User < ApplicationRecord
has_many :user_positions
has_many :positions, through: :user_positions
accepts_nested_attributes_for :user_positions,
reject_if: :all_blank
end
class UserPosition < ApplicationRecord
belongs_to :user
belongs_to :position
end
class Position < ApplicationRecord
end
On my edit user form, I'd like to allow a User's current position to be updated. I do that in the following way:
<%= form_for #user do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :user_positions, #user.user_positions.order(created_at: :desc).first do |ff| %>
<%= ff.hidden_field :user_id, value: #user.id %>
<%= ff.collection_select :position_id, Position.all, :id, :label %>
<% end %>
<%= f.submit "Update User" %>
<% end %>
The issue I'm running into is that a new instance of UserPosition is being created every time I submit the form, even if the Position that's selected hasn't changed. This leads to a bunch of duplicate entries in the join table, when I really only care about "promotions" or "demotions" when the value of position_id has changed.
I don't want to add a custom validator to disallow the creation, because I still want the form to be able to submit with an unchanged position. An example of this is when I only want to change the User's name.
Any advice on how to deal with this use case?

It turns out that you can actually use any method as a Symbol argument the to accepts_nested_attributes_for reject_if option.
I updated my User model as follows, and now the behavior is exactly what I want:
class User < ApplicationRecord
has_many :user_positions
has_many :positions, through: :user_positions
accepts_nested_attributes_for :user_positions,
reject_if: :same_as_previous_position
def same_as_previous_position(attributes)
if self.user_positions.empty?
return false
end
Position.find(attributes[:position_id]) == self.user_positions.order(created_at: :desc).first.position
end
end

Related

Unpermitted parameters in Rails 5 using has_many, through

I keep getting unpermitted parameters for my appointment model.
Here are my models
class Appointment < ApplicationRecord
belongs_to :client
belongs_to :trainer
end
class Trainer < ApplicationRecord
has_many :appointments
has_many :clients, through: :appointments
end
class Client < ApplicationRecord
has_many :appointments
has_many :trainers, through: :appointments
end
Here's my controller, I just listed my private method for sake of brevity.
def appt_params
params.require(:appointment).permit(:appointment_date, client_id: [],
trainer_id: [])
end
The error says unpermitted parameters for trainer, client.
Am I missing something in my strong parameters method?
Here is my appointments/new view
<%= form_for #appointment do |f| %>
<%= f.datetime_select :appointment_date %>
<%= f.collection_select :trainer, Trainer.all, :id, :first_name %>
<%= f.collection_select :client, Client.all, :id, :name %>
<%= f.submit %>
<% end %>
I added collection to my appt_params method and still getting the same error. I'm still getting the hang of Rails and any help would be appreciated, thanks!
Since you have used associations, only client_id and trainer_id is enough and those should of integer form not array.
So change your strong parameters method code to :
def appt_params
params.require(:appointment).permit(:appointment_date, :client_id,
:trainer_id)
end
And your applications/new view to:
<%= form_for #appointment do |f| %>
<%= f.datetime_select :appointment_date %>
<%= f.collection_select :trainer_id, Trainer.all, :id, :first_name %>
<%= f.collection_select :client_id, Client.all, :id, :name %>
<%= f.submit %>
<% end %>
Was running with the same issue as you and after hours of debugging I was able to solve it:
Was my first time trying associations in Rails with models similar to you, so most documentation online recommends to define the foreign key's as client_ids:[] assuming your ids were arrays, when definitely they are integers as my parameters where
Parameters: {"utf8"=>"✓", "authenticity_token"=>"LEcwQ56xYJGpq2zIs6Cz0YbU7B7mBKRa6rhspVIxo9vEB5/UoFUvHYiN0UC0krTiIp+d0tzhit6DZT1Z8PmYYg==", "califica"=>{"text"=>"hi", "grade"=>"5", "user_id"=>"1", "party_id"=>"1"}, "commit"=>"Create Calification"}
I think this is due to f.collection_select taking as expected one value. So after hours of using the permit of arrays such as :user_id => [] , user_id:[] I was getting always error of Unpermitted parameters.
Tried the answer of #Bharath (which is correct) but it was still not working, that's was when I realized that my old models weren't made with references (ActiveModel::UnknownAttributeError (unknown attribute 'user_id' for Calification.):) so I had to make a references migration to add a foreign key and then it was all working perfectly.

Rails Validation of Association IDs

I am building a Rails 4.2.4 app where I have Units and Medics. When I edit each unit I have two spots for the medics, incharge and attendant. I want some way to validate that both the incharge_id and attendant_id are not the same. That way I can't assign myself as both positions on the unit.
Here is what my model and form view looks like.
unit.rb
class Unit < ActiveRecord::Base
belongs_to :attendant, :foreign_key => :attendant_id, :class_name => 'Medic'
belongs_to :incharge, :foreign_key => :incharge_id, :class_name => 'Medic'
belongs_to :unit_status
end
medic.rb
class Medic < ActiveRecord::Base
has_many :units
end
units/_form.html.erb
<%= form_for(#unit) do |f| %>
<%= f.label 'Attendant'%>
<%= f.collection_select(:attendant_id, Medic.order('name ASC'), :id, :name, {}) %>
<%= f.label 'In Charge'%>
<%= f.collection_select(:incharge_id, Medic.order('name ASC'), :id, :name, {}) %>
<%= f.label 'Unit Status'%>
<%= f.collection_select(:unit_status_id, UnitStatus.order("status ASC"), :id, :status, {})%>
<%= f.submit "Update" %>
<% end %>
So in summary if I edit a unit and I accidentally assign the id of "1" to the unit, I want to error out and give some sort of message, "Cannot assign the same medic to both positions". Something like that.
The only thing I can think of, is to somehow filter the params in the controller saying if the params of attendant_id and incharge_id are are == then redirect to the edit_unit_path and display a flash message, "You cannot assign the same medic to both positions".
It seems like it would be better to do validations on the model side instead of stuffing logic in the controller, but I'm not sure how to simultaneous validate the two different columns for uniqueness.
I came up with this on the Unit model.
validate :attendant_and_incharge
def attendant_and_incharge
errors.add(:attendant_id, "can't be the same as the incharge") if attendant_id == incharge_id
end
This will not allow me to save the same id to the unit model for attendant_id and incharge_id. It fails silently and directs to the units_path. Just need to put some conditional in the controller to redirect to the edit path on failure. (ThumbsUp)

has_many and belongs_ association rails 4

I am while leraning how to use association in rails 4 application
I have a user having many opinions and I want to add user opinion in the book show page
This is how i proceed:
my user.rb
class User < ActiveRecord::Base
has_one :panier
has_many :opinions
end
opinion.rb
class Opinion < ActiveRecord::Base
belongs_to :user
end
views/books/show.html.erb
<h2>Votre opinion nous intéresse:</h2>
<%= form_for([#user, #user.opinions.build]) do |f| %>
<p>
<%= f.label :body, 'votre opinion' %><br>
<%= f.text_area :body %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
opinion_controller.rb
class OpinionsController < ApplicationController
def create
#user = current_user
#opinion= #user.opinion.create(opinion_params)
end
private
def opinion_params
params.require(:opinion).permit(:body)
end
end
and in books_controllers this is my show method:
def show
#user= current_user
#books= Book.all
end
my routes:
get 'books/show' => 'books#show' , as: :books_list
resources :users do
resources :opinions
end
what I got as error:
undefined method `opinions' for nil:NilClass
in this line of code:
Most probably #user.opinions in your form causing this issue. check whether current_user returning object or not.
Also in your create method there is typo(#user.opinion), it should be #user.opinions.
Use accept nested attributes for same.

Edit a Parent Class from a Child Form

Currently, I am able to create multiple Students through my Adult Form by using accepts_nested_attributes_for :student.
But how can I Edit an existing Adult through a Student Form? (So the Opposite, except Edit)
Currently I am able to create more Parents through my Student form, but thats not what I want.
MODELS
class Adult < ActiveRecord::Base
has_many :students
accepts_nested_attributes_for :student
end
class Student < ActiveRecord::Base
belongs_to :adult
accepts_nested_attributes_for :adult
end
CONTROLLER
class StudentsController < ApplicationController
def update
#adult = Adult.find(params[:adult_id])
#student = Student.find(params[:id])
if #student.update_attributes(student_params)
redirect_to path
end
end
private
def student_params
params.require(:student).permit(:adult_id, :school_id, :username, :password,
:firstName, :middleName, :lastName, :alias,
adult_attributes: [:id, :name])
end
end
VIEWS
###HOW CAN I UPDATE THE EXISTING PARENT AS OPPOSE TO CREATING ONE
<%= simple_form_for #student.build_adult do |f| %>
<h4>Update Parent Information</h4>
<%= f.label :firstName, 'First Name' %>
<%= f.text_field :firstName, placeholder: "Parent's First Name", :class => "form-control"%>
<%= f.submit "Save & Continue", class: "btn btn-primary"%>
<% end %>
ROUTES
resources :adult do
resources :student
end
Firstly,it is has_many :students,so it should be accepts_nested_attributes_for :students not accepts_nested_attributes_for :student.
Secondly,if i understood your question correctly,you are trying to update the existing parent(adult) record but you are ending up creating a new one.Is that the problem? If so,you need to permit the :id of the student for which the parent(adult) record need to be updated.
Change your student_params method to like this
def student_params
params.require(:student).permit(:id,:adult_id, :school_id, :username, :password,
:firstName, :middleName, :lastName,:alias,adult_attributes: [:id, :name])
end
I'm not sure about this solution, but it may work...
Try using #parent.update_attributes instead of creating a separate parent form, send the parent params with the student params. You may need to define a parent_params() function to permit them.
** I believe the permit params will filter out the non-permitted ones, so you won't need to specify only the student or parents fields for their respect update_attributes.

has_many through form driven by checkboxes

Models : based on rails 3 guide!
class Physician < ActiveRecord::Base
has_many :physician_specialities
has_many :specialities, :through => :physician_specialities
end
class speciality < ActiveRecord::Base
has_many :physician_specialities
has_many :physicians, :through => :physician_specialities
end
class PhycianSpeciality < ActiveRecord::Base
belongs_to :physician
belongs_to :patient
end
And the database schema looks like this :
Physician
id
name
Speciality
id
name
PhycianSpeciality
id
physician_id
speciality_id
description
I want to have a form which is able to add specialities to a physician and write a small description of this speciality (according to the physician).
I think i can use somethings like this Quick Tip: has_many :through => checkboxes!
<% form_for #physician do -%>
<% Speciality.all.each do |group| -%>
<div>
<%= check_box_tag :speciality_ids, speciality.id, #user.specialities.include?(speciality), :name => 'user[speciality_ids][]' -%>
<%= label_tag :speciality_ids, speciality.name -%>
</div>
<% end -%>
<%= submit_tag -%>
<% end -%>
But i don't know where can i put the speciality description ...
It looks to me like you are missing at least two relationship tables/models.
SpecialtyDescription
PhysicianSpecialityDescription
That way you can have the right connection between PhysicianSpeciality & PhysicianSpecialityDescription (PhysicianSpeciality_id, SpecialtyDescription_id) & SpecialtyDescription.
Since the description is pending the creation of the join record in PhysicianSpecialty, I would just pass the description in as a text_area and sort it out pending the save of the Physician.
physicians_controller.rb
# i imagine ur update method would look kinda like this
def update
#physician = Physician.find(params[:id])
if #physician.update_attributes(params[:physician])
physician_specialty = PhysicianSpecialty.find_by_specialty_id(:specialty_id => specialty.id)
physician_specialty.update_attribute(:description, params[:description])
else
render :action => :edit
end
end

Resources