'merge' error with check_box field in rails - ruby-on-rails

In my Rails app, I need to show user email id's with checkboxes in a form to allocate users to a particular project. I have an array object #programmers and each row in the object contains email id's which I need to show inside the form with checkboxes.
My partial view containing form is:
_allocate_programmer.html.erb
<h1> Allocate programmers </h1>(Please check the programmers that you want to add)<br />
<%= form_for(#project) do |f| %>
<% if #project.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#project.errors.count, "error") %> prohibited this project from being saved:</h2>
<ul>
<% #project.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% unless #programmers.nil? %>
<% #programmers.each do |programmer| %>
<%= f.check_box :programmer, programmer.email %>
<% end %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
My routes.rb has:
match 'projects/:id/allocate_programmers' => 'projects#allocate'
My projects_controller.rb has the following code:
def allocate
#project = Project.find(params[:id])
#programmers = User.where(:role => 'programmer')
render "_allocate_programmer"
end
I am getting following error in the view
NoMethodError in Projects#allocate
Showing /home/local/Rajesh/ticket_system/app/views/projects/_allocate_programmer.html.erb where line #18 raised:
undefined method 'merge' for "test#gmail.com":String
I think its an issue with checkbox hash. Please help.
User.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :token_authenticatable,
:rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :role
# attr_accessible :title, :body
has_many :assignments
has_many :projects, :through => :assignments
has_many :tickets
ROLES = ['admin', 'network/system admin', 'manager', 'programmer']
def role?(base_role)
ROLES.index(base_role.to_s) <= ROLES.index(role)
end
end
Project.rb
class Project < ActiveRecord::Base
attr_accessible :project_name, :description, :duration_from, :duration_upto, :user_id
has_many :assignments
has_many :users, :through => :assignments
validates :project_name, :presence => true
validates :description, :presence => true
validates :duration_from, :presence => true
validates :duration_upto, :presence => true
#validates :user_id, :presence => true //this gives error
end
Assignment.rb
class Assignment < ActiveRecord::Base
attr_accessible :user_id, :project_id
belongs_to :user
belongs_to :project
end
Please check. I have updated the question with 3 models.

Supposing that you have set up your associations correctly.
<% #programmers.each do |programmer| %>
<%= check_box_tag "project[programmer_ids][]", programmer.id, #project.programmers.include?(programmer) %>
<%= programmer.email %>
<% end %>

ref this
check_box does not work when the check box goes within an array-like parameter
Following should work for you
<% #programmers.each do |programmer| %>
<%= check_box_tag "project[programmer]", programmer.email %>
<% end %>

Related

Nested fields in form is not rendering

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.

rsvp button (users can only rsvp once)

So currently users can rsvp multiple times for the same event. That is obviously problematic for my website so I want it that a single user can only rsvp once for a specific post/event. After they have made their Rsvp I want the rsvp button to disappear. Heres is how my code is looking.
show.html.erb
id="notice"><%= notice %></p>
<p>
<strong>Date:</strong>
<%= #post.date %>
</p>
<p>
<strong>Name:</strong>
<%= #post.name %>
</p>
<p>
<strong>User_id:</strong>
<%= #post.user_id %>
</p>
<p><strong>Address:</strong> <%= #post.address %></p>
<p>
<strong>Description:</strong>
<%= #post.description %>
</p>
<p>
<strong>registered:</strong>
<%=#post.users.length%>
</p>
<% if current_user == #post.user %>
<%= link_to 'Edit', edit_post_path(#post) %> |
<%end%>
<%= link_to 'Back', posts_path %>
<div class="rsvp"><%= button_to "Rsvp now", rsvps_post_path(#post), class: "btn btn-primary" %></div>
<div class="map"><%= image_tag "http://maps.googleapis.com/maps/api/staticmap?center=#{#post.latitude},#{#post.longitude}&markers=#{#post.latitude},#{#post.longitude}&zoom=12&size=450x400&sensor=false&key=AIzaSyCKzKMEhSNgwSXf7WV71pHWgzdpMkPn8W4",
class: 'img-fluid img-rounded', alt: "#{#post} on the map"%>
</div>
post.rb
class Post < ApplicationRecord
belongs_to :user
geocoded_by :address
after_validation :geocode, if: ->(obj){ obj.address.present? and obj.address_changed? }
reverse_geocoded_by :latitude, :longitude
after_validation :reverse_geocode
has_many :rsvps
has_many :users, through: :rsvps
validates :name, presence: true
user.rb
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 :posts
has_many :rsvps
has_many :posts, through: :rsvps
validates :email, presence: true
rsvp.rb
class Rsvp < ApplicationRecord
belongs_to :user
belongs_to :post
end
#rsvp migration
class Rsvp < ApplicationRecord
belongs_to :user
belongs_to :post
end
So I've looked around in Stackoverflow and googled for a bit but I'm at a loss. I would really appreciate an answer that would solve this issue. I just want the rsvp button to show users who havent rsvped for the specific post/event.
The most important thing to me is a database constraint. You want to instruct the database to throw an error in those cases.
In a migration
add_index :rsvps, [:user_id, :post_id], unique: true
And a validation that reflect that, just to make rails aware of this
validates :user_id, uniqueness: { scope: :post_id }
Now we're sure that no more than one pair some_user_id, some_post_id is present in the database.
Now, let's instruct the view to not display the button in those cases
<% unless Rsvp.exists?(post: #post, user: #post.user) %>
<div class="rsvp">
<%= button_to "Rsvp now", rsvps_post_path(#post), class: "btn btn-primary" %>
</div>
<% end %>
I'd move that exists query in the action and use just a boolean here, this is just demonstrative.

has_one belongs_to association attribute validation

I use devise for user authentication. Have two models User and Profile which are associated to each other like this
#app/model/user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :profile, dependent: :destroy
enum role: [:admin, :seller, :buyer]
end
#app/model/profile.rb
class Profile < ApplicationRecord
belongs_to :user
end
As you can see I have also role for users.
Here is forms for registrations of both models.
#user registration form
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
...
<%= f.select :role, collection: User.roles.keys.last(2) %>
<%= f.input :mobile_number, required: true, autofocus: true %>
...
<% end %>
#profile registration form
<%= simple_form_for(#profile) do |f| %>
...
<% if current_user.seller? %>
<%= f.input :location, required: true %>
<% else %>
<%= f.input :company_code, required: true %>
<% end %>
...
<% end %>
Problem
When current user is seller I need to validate presence of :location , when user is buyer then validate presence of :company_code.
How can I achieve that? I appreciate any helps , Thanks!
validates method accepts if option:
# app/model/profile.rb
class Profile < ApplicationRecord
belongs_to :user
validates :location, presence: true, if: "user.seller?"
validates :company_code, presence: true, if: "user.buyer?"
end
Also, current_user.role == "seller" is obsolete. Rails automatically sets up additional methods in model for enum option values, therefore you may just write: current_user.seller?, etc.

Rails 4.1 accepts_nested_attributes_for and has_one association with devise

I'm trying to implement a dual registration (users can be either customers or providers) with Devise, but I'm getting nowhere (>_<).
So I have a sign up link with a parameter http://localhost:3000/users/sign_up?type=customer and http://localhost:3000/users/sign_up?type=provider
My problem is that if I put the nested form with :provider like <%= f.fields_for :provider do |fp| %> as I expect it to be, because is an has_one association it isn't shown. And if I put it with :providers like <%= f.fields_for :providers do |fp| %> the field is properly shown in the form, but it is not saved.
I tried some of the proposed things in other posts (like this and this), but nothing seems to work for me...
Here is a simplified version of my code:
Routes:
Rails.application.routes.draw do
devise_for :users, :controllers => { registrations: 'registrations' }
resources :users
resources :customers
resources :providers
end
Models:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :customer
accepts_nested_attributes_for :customer
has_one :provider
accepts_nested_attributes_for :provider
end
class Customer < ActiveRecord::Base
belongs_to :user
end
class Provider < ActiveRecord::Base
belongs_to :user
end
Controllers:
class RegistrationsController < Devise::RegistrationsController
def sign_up_params
params.require(resource_name).permit(:email, :password, :password_confirmation, customer: [:field_x], :provider: [:field_y]))
end
end
Views:
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
...
<% if params[:type] == "customer" %>
<%= f.fields_for :customers do |fc| %>
<div class="field">
<%= fc.label :field_x %><br />
<%= fc.text_field :field_x, autofocus: true %>
</div>
<% end %>
<% end %>
<% if params[:type] == "provider" %>
<%= f.fields_for :providers do |fp| %>
<div class="field">
<%= fp.label :field_y %><br />
<%= fp.text_field :field_y, autofocus: true %>
</div>
<% end %>
<% end %>
<div class="actions">
<%= f.submit "Sign up" %>
</div>
<% end %>
Thanks a lot!
When you are using fields_for :provider result is not displaying because you have not build it, build the association for nested attributes.
When you are using fields_for :providers record are not being save because in sign_up_params method you are not white listed providers attribute.
Also in strong parameter if you are using association(nested attribute) then you should use provider_attributes instead of provider.

How to show name of user who wrote comment (Rails)?

In my rails app I have users(authors),posts(articles),comments. If registered user write comment to article, I want to show his name beside his comment, if he isn't registered user I want to show "Anonymous" beside his comment. How can I do this?
comment model:
class Comment < ActiveRecord::Base
attr_accessible :post_id, :text
belongs_to :post
belongs_to :user
end
user model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
has_many :posts, :dependent => :destroy
has_many :comments, :dependent => :destroy
validates :fullname, :presence => true, :uniqueness => true
validates :password, :presence => true
validates :email, :presence => true, :uniqueness => true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :fullname
end
post model:
class Post < ActiveRecord::Base
attr_accessible :text, :title, :tag_list
acts_as_taggable
validates :user_id, :presence => true
validates :title, :presence => true
validates :text, :presence => true
belongs_to :user
has_many :comments
end
view file (show.html.erb)
<h1><%= #post.title %></h1>
<p>
Created: <%= #post.created_at.strftime("%Y/%m/%d")%> by
<%= link_to #post.user.fullname, user_posts_path(#post.user) %>
</p>
<p><%=simple_format #post.text %></p>
<p>
Tags: <%= raw #post.tag_list.map { |t| link_to t, tag_path(t) }.join(', ') %>
</p>
<h2>Comments</h2>
<% #post.comments.each do |comment| %>
<p><%= comment.created_at.strftime("%Y/%m/%d") %>
by <%= HERE I NEED ADD SOMETHING%></p>
<p><%= comment.text %></p>
<p><%= link_to "Delete comment", [#post, comment], :method => :delete,
:confirm =>"Are you sure?"%></p>
<% end %>
<%= form_for [#post, #post.comments.build] do |f| %>
<p><%= f.text_area :text %></p>
<p><%= f.submit "Post comment" %></p>
<% end %>
<% if user_signed_in?%>
<p>
<%= link_to "Back", posts_path %>
<%= link_to "Edit", edit_post_path(#post) %>
<%= link_to "Delete", #post, :method => :delete, :confirm => "Are you sure?"%>
</p>
<% end%>
You can do this by calling the user method on the comment, and then name on that:
<%= comment.user.name %>
You can also define a to_s method in the User model:
def to_s
name
end
Which would mean you could get away with doing just this in the view:
<%= comment.user %>
If you're loading a whole bunch of comments, then I would recommend loading them this way:
#comments = Comment.includes(:user)
If you don't have that includes(:user) there, then Rails will issue a new query for every single comment to find that comment's user. Doing it this way makes Rails load all the users for all the comments upfront in just one query.
I think you want to create two assocations pointing to the same table:
class CreatePostss < ActiveRecord::Migration
def change
create_table :posts do |t|
t.text :text
t.references :user, index: true, foreign_key: true
t.references :author, index: true, foreign_key: { to_table: :users }
t.timestamps null: false
end
end
end
Here user_id references the target user of the post and author_id references the user writing the post. Both reference users.id.
Then create two belongs_to associations in your post model:
class Post < ApplicationRecord
belongs_to :user
belongs_to :author,
class_name: 'User',
inverse_of: :authored_posts
end
And two has_many associations in your User model:
class User < ApplicationRecord
# posts about this user
has_many :posts
# postss written by this user
has_many :authored_posts,
class_name: 'Post',
foreign_key: :author_id,
inverse_of: :author
end
Here is the controller
class PostsController < ApplicationController
before_action :set_user, only: [:new, :create]
# ...
def new
#post = #user.posts.new
end
def create
#post = #user.posts.new(post_params) do |c|
c.author = current_user
end
if #post.save
redirect_to doctor_path(id: #user.id)
else
render :new
end
end
private
def set_user
#user = User.find(params[:id])
end
# ...
end
To display posts you would do:
<% #user.posts.each do |post| %>
<div class="post">
<div class="body">
<%= post.body %>
</div>
<p class="author"><%= post.author.email %></p>
</div>
<% end %>

Resources