How to associate intermediary model's extra column with value from a form - ruby

I have a many to many relationship between Plays and Users with the intermediary WorksOn.
class Play < ActiveRecord::Base
has_many :works_on
has_many :users, :through => :works_on
end
class User < ActiveRecord::Base
has_many :works_on
has_many :plays, :through => :works_on
end
#The WorksOn model also has a :student_role field
class WorksOn < ActiveRecord::Base
belongs_to :user, :foreign_key => 'user_id' #these columns were added via a migration
belongs_to :play, :foreign_key => 'play_id'
end
And in Play's form I have a checkbox to select users
<div class="field" id="checkbox-list">
<%= f.label "Cast and Crew"%> <br />
<%= collection_check_boxes(:user_id, :user_ids, #users, :id, :simple_to_s) %> #simple_to_s defined in Play's model. #users defined in play controller's 'show' form
</div>
In Play's controller I manually create the associations using the checkbox id's.
#...
def update
#grab user_id from params and then the user_ids values
works_on = params[:user_id]
works_on = works_on[:user_ids].select{|id|id.length!=0} #rails includes hidden empty string field for checkboxes, must filter it out by removing the 0 length string
WorksOn.where(:play_id=>#play.id).destroy_all #remove previous associations
works_on.each do |user_id|
WorksOn.create(:play_id=>#play.id, :student_role => "aaaaaaaaa",:user_id=>user_id) #create new associations to user
end
#other update stuff -> respond_to do |format| ...
end
#...
The controller currently works but I want to have a text field associated with each check box, and when the admin hits submit the :student_role field uses that value instead of "aaaaaaaaa". Any ideas on how to get this started?
Also, bonus question. Why is :user_id a part of params and not play_params?
Thanks so much!

This ended up being more simple than I expected!
Play's new form
<div class="col-md-6 field checkbox-list">
<%= f.label "Cast and Crew"%> <br />
<%= collection_check_boxes(:user_id, :user_ids, #users, :id, :simple_to_s) %>
</div>
<div class="col-md-6 field user-roles-text-list">
<%= f.label "Roles and Responsibilities (eg Actor: Hamlet)"%> <br />
<% #users.each do |user| %> #add a text field for each checkbox
<%= text_field_tag "user_role[#{user.id}]", nil, placeholder: user.simple_to_s %>
<% end %>
</div>
Play controller
def associate_play_with_users( params )
user_ids = params[:user_id] #grab user_id hash from params
user_roles = params[:user_role]
user_ids = user_ids[:user_ids] #select the :user_ids key's values from the hash
user_ids = user_ids.select{|id|id.length!=0}#rails includes hidden empty string field for checkboxes, must filter it out by removing the 0 length string
WorksOn.where(:play_id=>#play.id).destroy_all #remove previous associations
user_ids.each do |user_id|
WorksOn.create(:play_id=>#play.id, :student_role => user_roles[user_id],:user_id=>user_id) #create new associations to user
end
end
Parameters
Parameters: {"utf8"=>"✓", "authenticity_token"=>"+7rIGuD59ld5lQHfTtRY130TkGCNnnHm4GJfBQJkP3C0IKmz4YStDxOcuxc2pKsS8nodB/UV1fR5XwWNXXbJ4w==",
"play"=>{"title"=>"As You Like It", "description"=>"a dope play faaao sho", "date_of_play(1i)"=>"3000", "date_of_play(2i)"=>"2", "date_of_play(3i)"=>"2", "date_of_play(4i)"=>"00", "date_of_play(5i)"=>"00"},
"user_id"=>{"user_ids"=>["69", "70", ""]},
"user_role"=>{"69"=>"ss", "70"=>"sssasasasas", "71"=>"", "72"=>"", "73"=>"", "74"=>""}, #only the ids from user_id are used, 69 and 70
"commit"=>"Update Play", "id"=>"17"}

Related

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)

Rails 3 creates duplicate form object for nested records when updating them

There are two models
class Category < ActiveRecord::Base
has_many :products
accepts_nested_attributes_for :products, allow_destroy: true
validates_associated :products
end
class Product < ActiveRecord::Base
belongs_to :category
validate_uniqueness_of :name, scope: :category_id
end
And a form for category
<%= simple_form_for #category do |f| %>
<%= f.simple_fields_for :products do |p| %>
<%= render :partial => "product_fields", :locals => { :f => p } %>
<% end %>
<% end %>
As you can see there is a uniqueness validation which makes sure that products are uniq within category. The problem starts when validation is not passed.
Calling #category.update_attributes(params[:category]) results in duplicate form object. When rendering the form with errors rails creates additional associated product with the id of duplicate record but different name.
For example:
Given we have two products within category: Bread and Butter. If set Butter to Bread when editing a category's products a new form object will be created and the form will be rendered with Bread, Butter, Bread, saying last entry has a duplicate name.
How can i prevent rails from creating those duplicate records? I'm using rails 3.2.11 if it matters.

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.

How to save to Database with associations in rails protecting mass assignment

After trying for few hours I can not save to the database.
The context is this:
I have two types of users, one for that I only need very basic information [Username, email, password] and another kind of user for who I need a lot of information [age, gender, city and so on]
I did not use STI becouse of the vast quantity of Null values there would be in the table.
So I created this three modes in which a user has a profile (profiles table) or not depending of its type [1 or 2], and a field of this profile is the city this user is living in, that relates to another table in the DB, the cities table
class User < ActiveRecord::Base
has_one :profile
has_one :city, through: :profile
end
class Profile < ActiveRecord::Base
belongs_to :user
belongs_to :city
[...a bunch of fields here]
end
class City < ActiveRecord::Base
has_many :profiles
has_many :users, through: :profiles
end
When I play with them in the rails console everything goes OK:
usr = User.new(name: "roxy", email: "roxy#example.me", password: "roxanna", password_confirmation: "roxanna", utype: 1)
cty = City.new(name: "Bucaramanga")
prf = Profile.new (rname: "Rosa Juliana Diaz del Castillo"...)
prf.city = cty
usr.profile = prf
usr.valid?
=> true
usr.save
=> true
but when I try to save in the app (View an Model)
<%= f.label :city, "En que ciudad te encuentras?"%>
<%= select_tag :city, options_from_collection_for_select(City.all, 'id', "name"),{:prompt => 'Selecciona tu ciudad'}%>
def new
#profile = Profile.new
end
def create
#profile = params[:profile]
#city= City.find_by_id(params[:city].to_i)
#profile.city = #city
end
I get this error:
undefined method `city=' for #<ActiveSupport::HashWithIndifferentAccess:0xa556fe0>
Can someone please help me?
UPDATE
As David suggested I created the Profile object in the first line of the create method, so my controller now look like this:
def create
#profile = Profile.new(params[:profile])
#city= City.find_by_id(params[:city].to_i)
#profile.city = #city
#usr = current_user
if #usr.profile.exists? #profile
#usr.errors.add(:profile, "is already assigned to this user") # or something to that effect
render :new
else
#usr.profile << #profile
redirect_to root_path
end
end
But I'm getting this error now
undefined method `exists?' for nil:NilClass
current_user returns the #current_user
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
Could you tell me please, what am I doing wrong?
I want to write this to all of you who are beginning as well as I am and are stuck in this step.
I had to create a new project and play with it to realize what I was doing wrong. I figured out that I was validating a last time field I added to the Profiles table and had
# education :string(255) not null
but I had not added it yet to the form so the error launched is:
Failed to save the new associated so_profile.
Now, you know if you got this error, go check your schema and look for NOT_NULL fields you might be missing in the form, also you can comment out all your model validations and after it's working uncomment'em to be sure.
So, my Final Models:
class User < ActiveRecord::Base
has_one :profile
has_one :city, through: :profile
attr_accessible :email, :name
end
class Profile < ActiveRecord::Base
belongs_to :user
belongs_to :city
attr_accessible :age, :fcolor, :gender
end
class City < ActiveRecord::Base
has_many :profiles
has_many :users, through: :profiles
attr_accessible :name
end
My controllers:
class ProfilesController < ApplicationController
def new
#user = User.find_by_id(params[:id])
#profile = Profile.new
end
def create
#profile = Profile.new(params[:profile])
city = City.find_by_id(params[:city])
#profile.city = city
#user = User.find_by_id(params[:userid])
#user.profile = #profile
if #user.save
flash[:success] = "Guardado"
redirect_to profile_path(id: #user.id)
end
end
def show
#user = User.find(params[:id])
end
end
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
flash[:success] = "Registrado!"
redirect_to new_profile_path(id: #user.id)
else
flash[:error] = "No Registrado :("
redirect_to new
end
end
def show
#user = User.find_by_id(params[:id])
end
end
In a real app you have to use Cookies or something else to keep the session alive and therefore the user_token from where you get the user_id, but it works to play with associations.
The views:
profiles/new.html.erb
<%= #user.name %>
<%= form_for #profile, url: {action: :create, userid: #user.id } do |f| %>
<%= f.label :age, "Edad" %>
<%= f.text_field :age%> <br />
<%= label :city, "Ciudad"%>
<%= select_tag :city, options_from_collection_for_select(City.all, 'id', 'name')%>
<%= f.submit %>
<% end %>
profiles/show.html.erb
Hello <%= #user.name %><br />
Tu edad es: <%= #user.profile.age %><br />
Vives en <%= #user.profile.city.name%>
users/new.html.erb
<%= form_for #user do |f|%>
<%= f.label :name, "Nombre"%>
<%= f.text_field :name, size: 20, placeholder: "Escribe tu nombre aqui" %><br />
<%= f.label :email, "Email"%>
<%= f.text_field :email, size: 20, placeholder: "Escribe tu email aqui" %><br />
<%= f.submit "Sign me up!"%>
users/show.html.erb
Name: <%= #user.name %><br />
Email: <%= #user.email %>
And that's it!
Cheers.
Learn to read the error messages. The problem is that #profile is a hash because you didnt't actually create a new Profile object on the first line of the create method.
I think that the correct is
#so_profile.City
not
#so_profile.city
Because the class name is City

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