Ruby- How to add comments list to user's dashboard? - ruby-on-rails

I have three tables: books, comments and users. Tables are related. Users can login and then comment on books. Users have a dashboard. How can I make users see the comments that have been made to their books on their dashboard?
Here are the models,
book.rb:
class Book < ApplicationRecord
validates :title, presence: true
validates :author, presence: true
belongs_to :user
has_many :comments
end
comment.rb:
class Comment < ApplicationRecord
belongs_to :book
belongs_to :user
scope :approved, -> {where(status: true)}
end
user.rb:
class User < ApplicationRecord
before_create :set_username
has_many :books
has_many :comments
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
attr_writer :login
def login
#login || self.username || self.email
end
# validates_length_of :username,
# :within => 5..50,
# :too_short => " is too short, must be at least 5 characters.",
# :presence => true
private
def set_username
self.username = self.email.split("#").first
end
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
else
if conditions[:username].nil?
where(conditions).first
else
where(username: conditions[:username]).first
end
end
end
end
dashboard controller:
class DashboardController < ApplicationController
def index
#books = current_user.books
end
end

Since you already have the book, you can also include the comments to reduce n+1. So in your DashboardController you can modify the statement you have with #books = current_user.books.includes(:comments). This will collect all of the comments for that user's books. Then in the view you can iterate through them like this. Where you can display them however you want, ie, ol/ul with children li for teh comments
<% #books.each do |book| %>
<%= book.title %>
<%= book.author %>
<p>Comments:</p>
<% book.comments.each do |comment| %>
<%= comment.text %>
<% end %>
<% end %>

I have some suggestion for you.
You can write like this
class DashboardController < ApplicationController
def index
#books = current_user.books.includes(:comments)
end
end
It will get all of the comments you need, especially you also avoid n+1 query.
Or you can also write
class DashboardController < ApplicationController
def index
#books = current_user.books
#comments = current_user.comments.where('id = ?', #books.pluck(:id))
end
end

Related

Rails 5 display current users contacts

I am trying to show only the current_user contacts via the rails current_user method:
def index
if params[:category_id] && !params[:category_id].empty?
category_find = Category.find(params[:category_id])
#contacts = category_find.current_user.contacts.search(params[:term]).order(created_at: :desc).page params[:page]
else
#contacts = Contact.current_user.search(params[:term]).order(created_at: :desc).page params[:page]
end
end
contacts model:
scope :search, -> (term) do
where('LOWER(name) LIKE :term or LOWER(email) LIKE :term or LOWER(country) LIKE :term', term: "%#{term.downcase}%") if term.present?
end
But it returns unfined method current_user.
what am I doing here that prevents me from showing the contacts only for the current user?
EDIT:
Here's the user contact:
class Contact < ApplicationRecord
belongs_to :category
has_one_attached :contact_avatar
belongs_to :user
validates :name, :email, presence: true
validates_length_of :name, minimum: 3
validates_length_of :mobile, minimum: 7, maximum: 15, allow_blank: true
validates_length_of :phone, minimum: 7, maximum: 15, allow_blank: true
scope :search, -> (term) do
where('LOWER(name) LIKE :term or LOWER(email) LIKE :term or LOWER(country) LIKE :term', term: "%#{term.downcase}%") if term.present?
end
end
Here's the User model.
class User < ApplicationRecord
has_many :contacts
has_many :categories
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_one_attached :user_avatar
end
Here's the category model:
class Category < ApplicationRecord
has_many :contacts
belongs_to :user
end
Note I am using devise gem for authentication.
You have User#contacts and User#categories associations, just use them:
def index
if params[:category_id] && !params[:category_id].empty?
category = Category.find(params[:category_id])
#contacts = current_user.contacts.where(category: category)
else
#contacts = current_user.contacts
end
#contacts = #contacts.search(params[:term]).order(created_at: :desc)
end
or maybe even better (with some refactoring):
#contacts = current_user.contacts.search(params[:term]).order(created_at: :desc)
if params[:category_id].present?
category = Category.find(params[:category_id])
#contacts = #contacts.where(category: category)
end
If you really wanted to make this chainable you should write it as:
class Contact < ApplicationRecord
# ...
# don't use scope for anything except oneliners
def self.search(term)
return self unless term.present? # prevents nil error
where(
'LOWER(name) LIKE :term or LOWER(email) LIKE :term or LOWER(country) LIKE :term',
term: "%#{term.downcase}%"
)
end
def self.by_category(category_id)
category_id.present? ? where(category_id: category_id) : self
end
end
The key is that your "scopes" (which are really just class methods) must always return a ActiveRecord::Relation or self which prevents nil errors.
def index
#contacts = current_user.contacts
.search(params[:term])
.by_category(params[:category_id])
.order(created_at: :desc)
.page(params[:page])
end
This will work for you.
def index
if params[:category_id].present?
category = Category.find(params[:category_id])
#contacts = category.current_user.contacts
else
#contacts = current_user.contacts
end
#contacts = #contacts.search(params[:term]).order(created_at: :desc).page params[:page]
end
You can use safe navigator also, If you get any nil error.
Better way to get pagination object first and then filter record on the basis of incoming params like below.
def index
// Here we are trying to fetch all contacts of current user, order and paginate also.
#contacts = current_user.contacts.order(created_at: :desc).page(params[:page])
// Here we are filtering current user all contact for particular category if category_id is present.
#contacts = #contacts.where(category_id: params[:category_id]) if params[:category_id].present?
// Here we are filtering records with search term
#contacts = #contacts.search(params[:term]) if params[:term].present?
end

Rails 5 generate parent record on child creation, only if not already present

I have two models, User and Company. I have used the device gem for User model creation.
class User < ApplicationRecord
belongs_to :company
accepts_nested_attribute_for :company
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable, :lockable
end
class Company < ApplicationRecord
has_many :users
end
When I create users, I want to associate with them the company they work in. I have included the company_name attribute in the user creation form. What I don't want is the company table to have multiple records for the same company_name attribute.
<% form_for(#user) do |user_form| %>
<% user_form.fields_for :company do |company_form| %>
<%= company_form.label :company_name %>
<%= company_form.text_field :company_name %>
<% end %>
# other fields for user
<% end %>
I want to check if the company the user is associated with, is already present or not in the company table. Create a new record for the company only if it is not already present.
class User < ApplicationRecord
before_save :create_or_initialize_company
private
def create_or_initialize_company
Company.where("name ILIKE '%#{company_name}%'").first_or_create
end
end
Now here, you can do couple of variations based on your requirements e.g.
If you want exact match then .where(name: company_name)
If you don't want case insensitive match then replace ILIKE with LIKE.
Hope it helps..
class UsersController < ApplicationController
def create
#user = User.new(user_params)
assign_new_or_existing_company(#user)
if #user.save
# ...
end
def update
#user = User.find(params[:id])
#user.assign_attributes(user_params)
assign_new_or_existing_company(#user)
if #user.save
# ...
end
private
def assign_new_or_existing_company(user)
user.company = Company.where(
'company_name ILIKE ?',
"%#{user_params[:company_attributes][:company_name]}%"
)
.first_or_initialize(user_params[:company_attributes])
end
def user_params
params.require(:user).permit(:id, company_attributes: [:company_name])
end
end

Rails Client side Collection Validation fails - Simple Form

I have to build a simple app that allows users to loan and borrow books. Simply put a User can create books, and they can pick another user to loan the book to.
I have three models User, Book and Loan:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :books
has_many :loans, through: :books
has_many :borrowings, class_name: "Loan"
validates :username, uniqueness: true
validates :username, presence: true
end
class Book < ActiveRecord::Base
belongs_to :user
has_many :loans
validates :title, :author, presence: true
end
class Loan < ActiveRecord::Base
belongs_to :user
belongs_to :book
validates :user, :book, :status, presence: true
end
The LoansController looks like this:
class LoansController < ApplicationController
before_action :find_book, only: [:new, :create]
def new
#users = User.all
#loan = Loan.new
authorize #loan
end
def create
#loan = Loan.new
#loan.book = #book
#loan.user = User.find(loan_params[:user_id])
#loan.status = "loaned"
authorize #loan
if #loan.save
redirect_to :root
else
render :new
end
end
private
def loan_params
params.require(:loan).permit(:user_id)
end
def find_book
#book = Book.find(params[:book_id])
end
end
My form looks like:
<%= simple_form_for([#book, #loan]) do |f| %>
<%= f.input :user_id, collection: #users.map { |user| [user.username, user.id] }, prompt: "Select a User" %>
<%= f.submit %>
<% end %>
If I submit the form without selecting a user, and keep the "Select a User" prompt option, the form is submitted and the app crash because it can't find a user with id=
I don't know why the user presence validation in the form does not work...
you will change your Create method
def create
#loan = Loan.new
#loan.book = #book
#loan.user = User.find_by_id(loan_params[:user_id])
#loan.status = "loaned"
authorize #loan
if #loan.save
redirect_to :root
else
render :new
end
end

CanCan for employees and users

I have a Ticket model, an Employee model, and a User model.
Users and Employees can create tickets, but employees also have tickets assigned to them. So user_id refers to the creator of the ticket, and employee_id refers to the assigned employee (I am not sure if this the best way or not).
Ticket.rb
class Ticket < ActiveRecord::Base
before_save :default_values
after_commit :close_solved
after_commit :close_canceled
before_create :assign_state
attr_accessible :description, :title, :employee_department_id, :user_id, :first_name, :last_name , :email, :state_id, :employee_id, :ticket_state, :assign_state
belongs_to :employee_department
belongs_to :user
belongs_to :state
belongs_to :employee
has_many :replies
def default_values
self.state_id = 3 if self.state_id.nil?
end
def to_label
ticket_state.to_s
end
def close_solved
if self.ticket_state == "solved"
self.update_column(:ticket_state, "closed (solved)")
self.save!
end
end
def close_canceled
if self.ticket_state == "canceled"
self.update_column(:ticket_state, "closed (canceled)")
self.save!
end
end
def assign_state
if self.employee_id.nil?
self.assign_state = "un-assigned"
else
self.assign_state = "assigned"
end
end
Ticket.all.each do |ticket|
if ticket.ticket_state.blank?
ticket.ticket_state = 'open'
end
ticket.save
end
end
Employee.rb
class Employee < ActiveRecord::Base
# attr_accessible :title, :body
after_create :add_to_users
attr_accessible :employee_number, :joining_date, :first_name, :middle_name, :last_name,
:gender, :job_title, :employee_department_id, :qualification, :experience_detail,
:experience_year, :experience_month, :status_description, :date_of_birth, :marital_status,
:children_count, :father_name, :mother_name, :husband_name, :blood_group, :nationality_id,
:home_address_line1, :home_address_line2, :home_city, :home_state, :home_pin_code,
:office_address_line1, :office_address_line2, :office_city, :office_state, :office_pin_code,
:office_phone1, :office_phone2, :mobile_phone, :home_phone, :email, :fax, :user_id, :school_id,
:employee_category_id, :employee_position_id, :reporting_manager_id, :employee_grade_id,
:office_country_id, :home_country_id
belongs_to :employee_department
belongs_to :employee_category
belongs_to :employee_position
belongs_to :employee_grade
belongs_to :nationality, class_name: 'Country'
belongs_to :reporting_manager, class_name: "Employee"
belongs_to :school
belongs_to :user
has_many :tickets
def add_to_users
new_user = User.new
new_user.user_name = self.first_name
new_user.first_name = self.first_name
new_user.last_name = self.last_name
new_user.email = self.email
new_user.password = "123456"
new_user.password_confirmation = "123456"
new_user.user_type_id = 2
new_user.save
t = Employee.find(self.id)
t.user_id = new_user.id
t.save
end
def to_label
full_name = first_name + " " + last_name
end
def full_name
full_name = first_name + " " + last_name
end
end
User.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :validatable,:confirmable and :omniauthable
devise :database_authenticatable, :registerable, :recoverable, :rememberable,
:trackable, :lockable, :timeoutable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :user_name, :first_name, :last_name, :password, :password_confirmation, :remember_me,
:role_ids, :current_password, :user_type
attr_accessor :current_password
# attr_accessible :title, :body
has_many :assignments
has_many :roles, :through => :assignments
has_many :articles
has_many :comments
has_many :students
has_many :guardians
has_many :employees
has_many :tickets
has_many :permissions
accepts_nested_attributes_for :tickets
def has_role?(role_sym)
roles.any? { |r| r.role_name.underscore.to_sym == role_sym }
end
end
Ability.rb
class Ability
include CanCan::Ability
def initialize(user)
#user = user || User.new
if user.has_role? :administrator
can :manage, :all
end
if user.has_role? :admission_manager
can :manage, Student
end
if user.has_role? :news_manager
can :manage, Article
end
if user.has_role? :ticket_manager
can :manage, Ticket
end
if user.has_role? :student_viewer
can :read, Student
end
if user.has_role? :news_viewer
can :read, Article
end
if user.has_role? :ticket_viewer #he should be able to create tickets and see what he has created.
can :create, Ticket
can :read, Ticket
end
end
end
Ticket_controller.rb
class TicketsController < ApplicationController
load_and_authorize_resource
def index
#tickets = Ticket.all
#tickets_grid = initialize_grid(Ticket, :include => [{:user => :user_type}, :employee_department, :state])
end
def show
#ticket = Ticket.find(params[:id])
#reply = #ticket.replies.build # this for comments on ticket
#state = State.all # this for a model called State which describe the priority of the ticket (Emergency / High / Normal )
end
def new
#ticket = Ticket.new
end
def create
#ticket = Ticket.new(params[:ticket])
if #ticket.save
flash[:notice] = 'Support ticket request created.'
redirect_to #ticket
else
flash[:error] = 'An error occurred please try again!'
redirect_to '/dashboard'
end
end
def edit
#ticket = Ticket.find(params[:id])
end
def update
#ticket = Ticket.find(params[:id])
if #ticket.update_attributes(params[:ticket])
flash[:notice] = 'Successfuly updated.'
redirect_to tickets_path
else
flash[:error] = 'An error occurred please try again!'
render #ticket
end
end
end
I need to allow Employees to be able to manage their assigned tickets, and I need the creator of the ticket to see only the tickets he created.
How can I do this using CanCan? I'm open to other suggestions, if it cannot be done with CanCan.
For users to be able to read the tickets they've created, you just need to add a condition on the ability (see below). You can use the same condition on the :create ability and cancan will pre-fill those attributes for you when it builds a new object for the #new or #create actions.
# app/models/ticket.rb
class Ticket < ActiveRecord::Base
# <snip>
belongs_to :user
belongs_to :employee
# <snip>
end
# app/models/user.rb
class User < ActiveRecord::Base
has_one :employee
end
# app/models/ability.rb
class Ability
# <snip>
if user.has_role? :ticket_viewer
can :create, Ticket
can :read, Ticket, :user_id => user.id
end
if user.employee # && any other necessary conditions
can :create, Ticket
can :read, Ticket, :employee_id => user.employee.id
end
end
# app/controllers/tickets_controller.rb
controller TicketsController < ApplicationController
load_and_authorize_resource
def index
# #tickets = Ticket.accessible_by(current_ability) # cancan's
# load_and_authorize resource will take care of loading ticket(s) for
# all controller actions, so I've commented them out
#tickets_grid = initialize_grid(#tickets, :include => [{:user => :user_type}, :employee_department, :state])
end
def show
# #ticket = Ticket.find(params[:id])
#reply = #ticket.replies.build # this for comments on ticket
#state = State.all # this for a model called State which describe the priority of the ticket (Emergency / High / Normal )
end
def new
# #ticket = Ticket.new
end
def create
# #ticket = Ticket.new(params[:ticket])
if #ticket.save
flash[:notice] = 'Support ticket request created.'
redirect_to #ticket
else
flash[:error] = 'An error occurred please try again!'
redirect_to '/dashboard'
end
end
def edit
# #ticket = Ticket.find(params[:id])
end
def update
# #ticket = Ticket.find(params[:id])
if #ticket.update_attributes(params[:ticket])
flash[:notice] = 'Successfuly updated.'
redirect_to tickets_path
else
flash[:error] = 'An error occurred please try again!'
render #ticket
end
end
end
This is fairly simple to achieve using CanCan. Here's a quick example using a modified subsection of the ability file you included:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
# Full access if you're the assigned employee
if user.has_role? :support_manager
can :manage, Ticket do |ticket|
ticket.try(employee) == user
end
end
# Read access only if you created the ticket
can :read, Ticket do |ticket|
ticket.try(user) == user
end
end
end
you will see all the tickets because in index action inside your controller you call:
#tickets = Ticket.all
you can try this:
#tickets = Ticket.accessible_by(current_ability)
by using this method current_user will see all tickets that current_user has access to
Update
you can define specific access in the accessible_method
#tickets = Ticket.accessible_by(current_ability, :manage)
the rest is how you define the access
https://github.com/ryanb/cancan/wiki/Fetching-Records
Example on define the access:
if user.has_role? :ticket_manager
can :manage, Ticket, employee: {:user_id => user.id}
end

How to avoid being friends multiple times

I am stuck with the friendships model. I can be friends with an user multiple times. So I need a condition to avoid add to friend link .
My users_controller :
class UsersController < ApplicationController
before_filter :authenticate_user!
def index
#users = User.all
end
def show
#user = User.find(params[:id])
#topics = #user.topics.paginate(page: params[:page])
#friendship = #user.friendships.build(:friend_id => params[:friend_id])
#friendships = #user.friendships.all
end
my show.html.erb:
<section>
<h1><%= #user.username %></h1>
<%= link_to "Arkadaşlarıma Ekle", friendships_path(:friend_id => #user), :method => :post,class: "btn btn-large btn-primary" %>
</section>
my friendships_controller :
class FriendshipsController < ApplicationController
before_filter :authenticate_user!
def create
#friendship = current_user.friendships.build(:friend_id => params[:friend_id])
if #friendship.save
flash[:notice] = "Arkadaşlara eklendi."
redirect_to root_url
else
flash[:error] = "Arkadaşlara Eklenemiyor."
redirect_to root_url
end
end
def destroy
#friendship = current_user.friendships.find(params[:id])
#friendship.destroy
flash[:notice] = "Arkadaşlarımdan kaldırıldı."
redirect_to current_user
end
end
so I guess , I tried add this method in users_controller but still no solution for it. Can you help me to fix it?
def friend?(other_user)
friendships.find_by_friend_id(other_user.id)
end
and In before link
<% unless friend?(#user) %>
%= link_to "Arkadaşlarıma Ekle", friendships_path(:friend_id => #user), :method => :post,class: "btn btn-large btn-primary" %>
<%end %>
My user model :
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :username
# attr_accessible :title, :body
has_many :topics
has_many :posts
has_many :friendships
has_many :friends, :through => :friendships
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id"
has_many :inverse_friends, :through => :inverse_friendships, :source => :user
end
my Friendship model :
class Friendship < ActiveRecord::Base
attr_accessible :friend_id, :user_id
belongs_to :user
belongs_to :friend, :class_name => "User"
validates :friend, :presence => true, :unless => :friend_is_self
def friend_is_self
user_id == friend_id ? false : true
end
end
I think it is best to do such validations on the model level. use validate_uniqueness_of in your model and test for validity in your controller.
I think the friend? method should be something similar as:
def friend?(other_user)
current_user.friendships.find_by_friend_id(other_user.id)
end
since I guess friendship should be a table with user_id and friend_id, what we want to check is if this user has othter_user as a friend or not.
Your friend? method is returning the friendship or nil, not true or false as you would expect with a ? method.
Try
def friend?(other_user)
# this assumes Friendship is a user, which probably isn't true
# but I don't have your models but this should give you the right idea
self.friendships.include other_user
end

Resources