I am using cocoon in my Rails app for assigning employees (users) to projects (many to many connection). The creation of associations is working correctly, but each time I add another employee cocoon adds an empty form field in the edit view. None of the other cocoon form fields in the edit view are populated either. Could this be due to the usage of dropdowns (select)?
When I inspect the form in my browser I can see that each field seems to be assigned to one of the associations, but the selection is still empty.
What I would like to achieve is, that every association is displayed in a cocoon form field, so that they can be edited. Thanks for any help in advance!
My code is below (Sorry for any mess, it is my first time trying out a many to many connection of two models).
Project Edit View
<%= form_for(#project, :url => project_path, method: :patch) do |f| %>
<div class="form-group">
<%= f.label :title %>
<%= f.text_field :title, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :customer %>
<%= f.text_field :customer, class: "form-control" %>
</div>
<%= f.fields_for :user_projects do |collab| %>
<% collab.hidden_field :project_id, value: #project.id %>
<%= render 'user_project_fields', f: collab %>
<% end %>
<div class="add-collaborator">
<%= link_to_add_association "add", f, :user_projects, class: "btn btn-mmc" %>
</div>
<div class="actions">
<%= f.submit "Save Changes", class: "btn btn-mmc btn-mmc-medium" %>
</div>
<% end %>
cocoon field partial
<div class="nested-fields">
<%= f.label "Select User" %>
<div class="form-group custom-form-group">
<%= f.select(:user_id, options_for_select(User.all.map { |u| [u.email, u.id] }), {include_blank: true}, {class: 'form-control'})%>
<div class="btn-user-project">
<%= link_to_remove_association "x", f, class: "btn btn-mmc-attention btn-mmc" %>
</div>
</div>
</div>
Project Model
class Project < ApplicationRecord
has_many :user_projects
has_many :users, :through => :user_projects
accepts_nested_attributes_for :user_projects, reject_if: :all_blank, allow_destroy: true
end
User Model
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :user_projects
has_many :projects, :through => :user_projects
end
Project Controller
def edit
#project = Project.find(params[:id])
#project.user_projects.build
end
def update
#project = Project.find(params[:id])
#project.update(project_params)
#new_collaborator = UserProject.new(user_id: params[:user_id], project_id: params[:project_id])
#new_collaborator.save
if #project.update(project_params) && #new_collaborator.save
redirect_to projects_path
else
render :edit
end
end
private
def project_params
params.require(:project).permit(:title, :customer, :delted, user_projects_attributes: [:user_id, :project_id]).reject { |_, v| v.blank? }
end
I am guessing the mapping to the actual value is not done correctly, e.g. the value of the user_id is not marked as selected, in the options_for_select you have to add the selected value as parameter (see documentation).
However, there is a much easier version:
<%= f.collection_select(:user_id, User.all, :id, :email) %>
BTW using a gem like simple_form also makes building forms a lot more intuitive and straightforward.
That's because you're create object twice.
First time:
#project.update(project_params) # from accepts_nested_attributes_for
Second time:
#new_collaborator = UserProject.new(user_id: params[:user_id], project_id: params[:project_id])
#new_collaborator.save
P.s.
Can you show project_params method? I think I know why first object empty
Related
Im new to ruby on rails so i need some tips please.
Im trying to render some checkboxes on the edit view for a user.
I have tried to follow the documentation for the nested_attributes but the checkboes does not render.
Here is the relation between the two models:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :softwares
has_many :courses
accepts_nested_attributes_for :softwares
accepts_nested_attributes_for :courses
The Edit view for a user
<div class="container">
<div class="row">
<div class="col-md-12 mt-5">
<%= form_for #student, url: auth_student_path(#student), method: :put do |f| %>
<div class="col-md-12 mb-5 mt-5">
<div class="row">
<h3 class="mb-3 filter_heading">Softwares</h3>
<% #softwares.each do |sf|%>
<%= f.fields_for :softwares do |software| %>
<div class="col-md-3">
<div class="courses">
<%= software.label sf.title %>
<%= software.check_box :title, {multiple: true}, sf.title, nil %>
</div>
<% sf.courses.each do |crs|%>
<%= f.fields_for :courses do |course|%>
<div class="mt-1 courses-checkbox">
<%= course.label crs.name %>
<%= course.check_box :name, {multiple: true}, crs.name , nil %>
</div>
<% end %>
<% end%>
</div>
<% end %>
<% end%>
</div>
<div class="form-group">
<%= f.submit "Save", class:"btn btn-primary"%>
</div>
<% end %>
</div>
</div>
</div>
The Controller
module Auth
class StudentsController < ApplicationController
before_action :authenticate_user!
before_action :set_student, only: %i[delete_certificates]
def edit
authorize! :edit, #user
#softwares = Software.all
#student = User.find(params[:id])
end
def update
authorize! :update, #user
#student = User.find(params[:id])
if #student.update(student_params)
redirect_to edit_auth_student_path(#student)
else
redirect_to edit_auth_student_path(#student)
end
end
def show
def set_student
#student = User.find(params[:student_id])
end
private
def student_params
params.require(:user).permit(
:email,
:firstname,
:lastname,
:phone,
:locked,
:approved,
:role,
badges: [],
certificates: [],
softwares_attributes: [:title],
courses_attributes: [:name],
)
end
end
end
Please help me.
You don't need accepts_nested_attributes_for just to select existing records and associate them with something. Its only needed if you need to create/update the other record (the course or software) at the same time.
I'm also guessing you don't actually want to have a one-to-many assocation and duplicate every course and every software for each user - instead you want a many to many assocation and some data normalization.
So create a join table to hold the assocation between users and courses for example:
class User < ApplicationRecord
has_many :enrollments, foreign_key: :student_id
has_many :courses, through: :enrollments
end
# rails g model enrollment student:belongs_to course:belongs_to
class Enrollment < ApplicationRecord
belongs_to :student, class_name: 'User'
belongs_to :course
end
class Course < ApplicationRecord
has_many :enrollments
has_many :students, through: :enrollments
end
And then you just create inputs that use the course_ids / course_ids= setter and getter created by has_many :courses, through: :enrollments.
<%= form_with(model: #student, url: auth_student_path(#student), method: :put) do |f| %>
<div class="field">
<%= f.label :course_ids, 'Select your courses' %>
<%= f.collection_select :course_ids, #courses, :id, :name, multiple: true %>
</div>
# ...
<% end %>
And then you just whitelist an array of ids in your controller:
params.require(:user)
.permit(
# ...
course_ids: []
)
In fact if your ever passing existing records as anything but an ID you're doing it very wrong.
There are still plenty of issues with this code but this should at least be nudge in the correct direction.
I'm trying to create a DRY form that I could use as form_fields for other, nested forms in different view. I'm having 2 problems.
I want the _form partial to not have a submit button, and rather have this button on the form itself. However, clicking the submit button in it's current place doesn't do anything??
The form displays fine in it's current state. If I move the submit button to the _form just to see that it saves, I get a routing error for uninitialized constant UsersController?
My code:
routes.rb
devise_for :users
resources :users do
resources :projects
end
user.rb model - I'm using Devise.
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable
validates :password, :presence => true,
:on => :create,
:format => {:with => /\A.*(?=.{8,})(?=.*\d)(?=.*[\#\#\$\%\^\&\+\=]).*\Z/ }
has_many :projects, inverse_of: :user
accepts_nested_attributes_for :projects
end
projects.rb model
class Project < ApplicationRecord
belongs_to :user, inverse_of: :projects
...
end
projects_controller.rb
class ProjectsController < ApplicationController
...
def new
#project = current_user.projects.build
end
def create
#project = current_user.projects.new(project_params)
if #project.save
redirect_to root_path, notice: 'Your project is being reviewed. We will be in contact soon!'
else
render :new
end
end
...
private
...
def project_params
params.require(:project)
.permit(
:user_id, :project_type_id, :name, :industry_id,
:description, :budget_id, :project_status_id, feature_ids:[], addon_ids:[]
)
end
end
_form.html.erb partial view
<%= form_for #project do |f| %>
<div class="project_form">
<ol>
<div class="field entry_box">
<li><%= f.label "Give your project a name" %>
<%= f.text_field :name, placeholder: "A short name", class: "form-control entry_field" %></li>
</div>
...... # All of the other form fields
<div class="field entry_box">
<li><%= f.label "What budget do you have in mind?" %>
<%= collection_select(:project, :budget_id, Budget.all, :id, :list_of_budgets, {include_blank: 'Please select'}, {class: "form-control entry_field"} ) %></li>
</div>
</ol>
# No f.submit button -> moved to view
</div>
<% end %>
new.html.erb view for new projects
<div class="container">
<div class="center">
<h1>New Project</h1>
</div>
<%= form_for current_user do |f| %>
<%= f.fields_for #project do |builder| %>
<%= render 'form', :locals => { f: builder } %>
<% end %>
<div class="actions center space_big">
<%= f.submit "Save Project", class: "btn btn-lg btn-success" %>
</div>
<% end %>
</div>
How do I get the submit button in it's current position to work?
What's causing the routing error for uninitialized constant UsersController?
In your _form partial you dont need the first line i.e form_for since you have already passed f which is a form ('builder') object for projects since you created it inside of fields_for #project block.
so this much will do:
<div class="project_form">
<ol>
<div class="field entry_box">
<li><%= f.label "Give your project a name" %>
<%= f.text_field :name, placeholder: "A short name", class: "form-control entry_field" %></li>
</div>
...... # All of the other form fields
<div class="field entry_box">
<li><%= f.label "What budget do you have in mind?" %>
<%= collection_select(:project, :budget_id, Budget.all, :id, :list_of_budgets, {include_blank: 'Please select'}, {class: "form-control entry_field"} ) %></li>
</div>
</ol>
# No f.submit button -> moved to view
</div>
and in your form_for current_user change render line to like this:
<%= render partial: 'form', :locals => { f: builder } %>
and you are getting that controller error since you dont have UsersController and you are making the nested_form for a User that is current_user so you should have this code inside UsersController's new action and build a project for user there.
I am unsure why my cocoon nested form is not appearing and any help would be much appreciated
the <button><%= link_to_add_association 'add a required skill', f, :requiredskills %></button> works perfectly well in displaying a form
but i am unsure why this is not rendering the form <%= render 'requiredskill_fields', :f => duty %>
what i would like to do is to have a form already displayed and when a user clicks add a required skill another form is displayed.
At present when a user clicks add a required skill that displays
another form - the main issue is having a form to be displayed
initially. I am assuming this <%= render 'requiredskill_fields', :f
=> duty %> is suppose to put a form in place but i am unsure why it is not working
user/_form.html.erb
<%= simple_form_for(#user) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :firstname %>
<%= f.input :lastname %>
<h3>required skills & expertise</h3>
<div>
<div class="requiredskill_info" id="skill">
<%= f.simple_fields_for :requiredskills do |skill| %>
<%= render 'requiredskill_fields', :f => skill %>
<% end %>
<div class="add_requiredskill"><button><%= link_to_add_association 'add a required skill', f, :requiredskills %></button></div>
</div>
</div>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
user/_requiredskill_fields.html.erb
<div class="nested-fields">
<%= f.association :category_advert, collection: CategoryAdvert.all, prompt: "select a category" %>
<%= f.grouped_collection_select :category_advertskill_id, CategoryAdvert.order(:name), :category_advertskills, :name, :id, :name, {prompt: "Select a category"}, {class: "category_advertskill"} %>
<button><%= link_to_remove_association 'remove required skill', f %></button>
</div>
users_controller.rb
class UsersController < ApplicationController
respond_to :html, :xml, :json
before_action :set_user, only: [:show, :edit, :update, :destroy]
def index
#users = User.all
#user = current_user
end
def show
#resume = #user.resume
end
def edit
end
def update
#user.update(user_params)
redirect_to #user
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:email, :firstname, :lastname, :city, :category_qualification_id, :category_careerlevel_id, :desiredjob, :category_distance_id, :preferedlocation, :category_notice_id, :category_country_id, :category_positiontype_id, :image, :cvattachment, :hidecv, :jobadvice, :validlicense, :owntransport, :considerrelocation, :preferredlocation, :neednotice, :stratdate, :availabletowork, :category_outsource_id, :category_advertskill_id, :category_age_id, languages_attributes: [:id, :name, :_destroy], requiredskills_attributes: [:id, :name, :category_advert_id, :category_advertskill_id, :category_year_id, :category_level_id, :_destroy])
end
end
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :requiredskills
has_many :category_advertskills, through: :requiredskills
has_many :category_adverts, through: :requiredskills
has_one :resume
accepts_nested_attributes_for :languages, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :requiredskills, :reject_if => :all_blank, :allow_destroy => true
end
def edit
#requiredskills = #user.requiredskills.present? ? #user.requiredskills : #user.requiredskills.build
end
change this code in controller
I have tried all of the solutions to similar problems and haven't gotten this one figured out.
I have a has_many :through relationship between 'Clinician', and 'Patient' with a joined model 'CareGroupAssignment'. None of the methods I have tried so far been able to save the clinician to patient association. I would like to have a patient be able to have multiple clinicians associated with it and clinicians will have multiple patients.
clinician.rb (simplified)
class Clinician < ActiveRecord::Base
belongs_to :care_group
has_many :patients ,:through=> :care_group_assignments
has_many :care_group_assignments, :dependent => :destroy
belongs_to :user
accepts_nested_attributes_for :user, :allow_destroy => true
end
patient.rb
class Patient < ActiveRecord::Base
belongs_to :care_group
has_many :clinicians ,:through=> :care_group_assignments
has_many :care_group_assignments
belongs_to :user
accepts_nested_attributes_for :user, :allow_destroy => true
end
care_group_assignments.rb
class CareGroupAssignment < ActiveRecord::Base
belongs_to :clinician
belongs_to :patient
end
I first tried to follow the example from Railscasts PRO #17- HABTM Checkboxes to at least start getting the data collected and to have the models set up correctly. Below is the form with the checkboxes for each clinician as described in the RailsCast, checkboxes show up and the data is sent but not stored (can't figure out why).
patient new.html.erb form
<%= form_for #patient do |form| %>
<%= form.fields_for :user do |builder| %>
<div class="form-group">
<%= builder.label "Email or Username" %>
<%= builder.text_field :email, class: "form-control" %>
</div>
<div class="form-group">
<%= builder.label :password %>
<%= builder.password_field :password, class: "form-control" %>
</div>
<% end %>
<div class="form-group">
<%= form.label :first_name %>
<%= form.text_field :first_name, class: "form-control", placeholder: "First name" %>
</div>
<div class="form-group">
<%= form.label :last_name %>
<%= form.text_field :last_name, class: "form-control", placeholder: "Last name" %>
</div>
<div class="form-group">
<% Clinician.where(care_group_id: #care_group.id).each do |clinician| %>
<%= check_box_tag "patient[clinician_ids][]", clinician.id, #patient.clinician_ids.include?(clinician.id), id: dom_id(clinician) %>
<%= label_tag dom_id(clinician), clinician.full_name %><br>
<% end %>
</div>
<%= form.button 'Create Patient', class: "btn btn-u btn-success" %>
<% end %>
Next, I tried the collection_select answer to this question. This creates a badly formatted list where only one clinician can be selected. The data seems to get sent but again doesn't save.
patient new.html.erb form
<div class="form-group">
<%= collection_select(:patient, :clinician_ids,
Clinician.where(care_group_id: #care_group.id).order("first_name asc"),
:id, :full_name, {:selected => #patient.clinician_ids, :include_blank => true}, {:multiple => true}) %>
</div>
Lastly, I copied what was done in this questions/solution. Also isn't formatted as a normal collection_select dropdown but instead a list with a boarder around it where only one clinician can be selected.
patient new.html.erb form
<div class="form-group">
<% Clinician.where(care_group_id: #care_group.id).each do |clinician| %>
<%= check_box_tag "patient[clinician_ids][]", clinician.id, #patient.clinician_ids.include?(clinician.id), id: dom_id(clinician) %>
<%= label_tag dom_id(clinician), clinician.full_name %><br>
<% end %>
</div>
None of these methods have so far been able to save the clinician to patient association.
patient_controller.rb
def new
#patient = Patient.new
#user = User.new
#patient.build_user
#care_group = current_clinician.care_group
end
def create
#patient = Patient.create(patient_params)
#patient.care_group = current_clinician.care_group
if #patient.save
redirect_to patient_path(#patient), notice: "New patient created!"
else
render "new"
end
end
def show
#patient = Patient.find_by(id: params["id"])
end
private
def patient_params
params.require(:patient).permit({:clinician_ids => [:id]},:first_name,:last_name,:user_id,:care_group_id, user_attributes: [ :email, :password, :patient_id, :clinician_id ])
end
I plan to display the clinicians associated with a patient on the patient show page:
patient show.html.erb
<strong>Shared with:</strong>
<% #patient.clinicians.each do |clinician| %>
<%= clinician.full_name %><
<% end %>
This works if I seed the database but since the data doesn't seem to be stored, nothing is showing up.
Rails 4.1.8, ruby 2.2.1p85, PostgreSQL
Thanks
Found the answer on another question I asked:
problem is this line in the controller:
params.require(:patient).permit({:clinician_ids => [:id]}...
It should be:
params.require(:patient).permit({:clinician_ids => []}...
I'm building a very basic wiki-style app that uses 3 models: User, Wiki, and Collaboration. My goal is, via the edit wiki page, a user should be able to add another user to the wiki as a "collaborator". This is what I have so far:
Wiki Model
class Wiki < ActiveRecord::Base
belongs_to :user
has_many :collaborations
has_many :collaboration_users, through: :collaborations, :source => :user
scope :visible_to, -> (user) { user.role == 'admin' || user.role == 'premium' ? all : where(private: false) }
end
User Model
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
has_many :wikis
has_many :collaborations
has_many :collaboration_wikis, through: :collaborations, :source => :wiki
after_initialize :set_default_role
def set_default_role
self.role ||= 'standard'
end
def upgrade_to_premium
self.update_attribute(:role, "premium")
end
def admin?
role == 'admin'
end
def standard?
role == 'standard'
end
def premium?
role == 'premium'
end
end
Collaboration Model
class Collaboration < ActiveRecord::Base
belongs_to :user
belongs_to :wiki
end
My _form.html.erb
<%= form_for wiki do |f| %>
<% if wiki.errors.any? %>
<div class="alert alert-danger">
<h4>There are <%= pluralize(wiki.errors.count, "error") %>.</h4>
<ul>
<% wiki.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= form_group_tag(wiki.errors[:title]) do %>
<%= f.label :title %>
<%= f.text_field :title, class:'form-control', placeholder: "Enter Wiki name" %>
<% end %>
<div class="form-group">
<%= f.label :body %>
<%= f.text_area :body, rows: 8, class:'form-control', placeholder: "Enter Wiki body" %>
</div>
<% if current_user.role == 'premium' || current_user.role == 'admin' %>
<div class="form-group">
<%= f.label :private, class: 'checkbox' do %>
<%= f.check_box :private %> Private Wiki
<% end %>
</div>
<% end %>
<%= f.submit "Save", class: 'btn btn-success' %>
<% end %>
My goal is to show a list of all users (if role == premium or admin) with the option to add or delete as a collaborator. Can someone point me in the right direction?
Thank you!
When you add a user to a wiki, as a collaborator, you are creating a collaboration record. You can do this via the "collaboration_user_ids=" method on a wiki: the association gives you this method, among many others.
For example, you could add user 123, and user 456 as a collaborator to wiki 789 by saying
#wiki = Wiki.find(789)
#wiki.collaboration_user_ids = [123, 456]
#wiki.save
This will delete or create collaboration records as appropriate, ie delete any collaborations where wiki_id = 789 and user_id NOT IN (123,456), and create a collaboration for user 123 and user 456, if they don't exist already.
So, now we know that we can set the list of collaborating users just by passing an array of their ids to #wiki.collaboration_user_ids, we just need to set up our form to pass this array through as params[:wiki][:collaboration_user_ids], and we can call #wiki.update_attributes(params[:wiki]) as normal.
You could do this by adding this to your form:
<div class="form-group">
<p>Collaborators</p>
<% collaboration_user_ids = #wiki.collaboration_user_ids %>
<%# this should more properly use a variable set in your controller rather than User.all - for example you might want to limit the list of possible collaborators according to some condition %>
<% User.all.each do |user| %>
<div class="user">
<%= check_box_tag "wiki[collaboration_user_ids][]", user.id, collaboration_user_ids.include?(user.id) %>
<%= user.name %>
</div>
<% end %>
</div>