Rails 5 display current users contacts - ruby-on-rails

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

Related

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

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

Move method from Controller to Model in Rails

I have been told to move a method "Top" from Controller to Model, but when I try to call it, it doesn't work anymore.
I am using Rails 6
This is my Controller:
class UsersController < ApplicationController
before_action :authenticate_user!
def index
#users = User.all
end
def show
#user = User.find(params[:id])
#posts = #user.posts.ordered_by_most_recent
end
def edit
#user = User.find(params[:id])
end
def following
#title = 'Following'
#user = User.find(params[:id])
#users = #user.following.paginate(page: params[:page])
render 'show_follow'
end
def followers
#title = 'Followers'
#user = User.find(params[:id])
#users = #user.followers.paginate(page: params[:page])
render 'show_follow'
end
def top
#userst = User.joins(:followers).order('COUNT(followings.follower_id) DESC').group('users.id').limit(10)
end
end
and this would be my Model:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, authentication_keys: [:username]
validates :fullname, presence: true, length: { maximum: 20 }
validates :username, presence: true, length: { maximum: 20 }
validates_uniqueness_of :username
has_many :posts
has_many :active_followings, class_name: 'Following',
foreign_key: 'follower_id',
dependent: :destroy
has_many :passive_followings, class_name: 'Following',
foreign_key: 'followed_id',
dependent: :destroy
has_many :following, through: :active_followings, source: :followed
has_many :followers, through: :passive_followings, source: :follower
mount_uploader :photo, FileUploader
mount_uploader :coverimage, FileUploader
# Follows a user.
def follow(other_user)
following << other_user
end
# Unfollows a user.
def unfollow(other_user)
following.delete(other_user)
end
# Returns true if the current user is following the other user.
def following?(other_user)
following.include?(other_user)
end
end
All code here makes sense to me, so I only had to create a file called top.html.erb like this to render the Top:
<article class="timeline new-initial">
<h3>Top:</h3>
<ul class="posts">
<%= render #userst %>
</ul>
</article>
Now, to be honest, I am lost, I am not sure how to move this method to the User model in the right way to read it in the view section.
This seems like a job for a scope.
Model:
scope :top, -> { joins(:followers).order('COUNT(followings.follower_id) DESC').group('users.id').limit(10) }
Controller:
def top
#userst = User.top
end

Getting countries states and cities in rails

I have a user model with a nested model for user's informations, and i have already a working country field with country_select gem, but it doesn't offer states and cities .
After some research i found this gem ruby geocoder and as it says in the documentation :
In Any Rack-Based Framework
Detect Location of HTTP Request
Get current user's city and country (using IP address). A location method is added to the standard Rack::Request which returns a Geocoder::Result object:
# Rails controller or Sinatra app
city = request.location.city
country = request.location.country_code
Basically i want to get off using country_select gem and use ruby geocoder
I have two models :
models/user.rb
class User < ApplicationRecord
extend FriendlyId
friendly_id :username, use: :slugged
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_one_attached :avatar, :dependent => :destroy
# User Information
has_one :user_information, :dependent => :destroy
accepts_nested_attributes_for :user_information, :allow_destroy => true
def with_user_information
build_user_information if user_information.nil?
self
end
# Login with username or email
attr_accessor :login
validates :username, uniqueness: true, presence: true
def login
#login || self.username || self.email
end
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions.to_h).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
elsif conditions.has_key?(:username) || conditions.has_key?(:email)
where(conditions.to_h).first
end
end
end
and a nested model :
models/user_information.rb
class UserInformation < ApplicationRecord
belongs_to :user
has_one :gender, :dependent => :destroy
accepts_nested_attributes_for :gender, :allow_destroy => true
has_one :relationship, :dependent => :destroy
accepts_nested_attributes_for :relationship, :allow_destroy => true
def age
now = Time.current
dob = self.born_in
now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
end
def country_name
country = ISO3166::Country[country_code]
country.translations[I18n.locale.to_s] || country.name
end
end
this my devise controller
controllers/accounts_controller.rb
class AccountsController < Devise::RegistrationsController
def update
self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)
resource_updated = update_resource(resource, account_update_params)
yield resource if block_given?
if resource_updated
set_flash_message_for_update(resource, prev_unconfirmed_email)
bypass_sign_in resource, scope: resource_name if sign_in_after_change_password?
session[:return_to] ||= request.referer
redirect_to session.delete(:return_to)
else
clean_up_passwords resource
set_minimum_password_length
session[:return_to] ||= request.referer
redirect_to session.delete(:return_to), alert: resource.errors.full_messages[0]
end
end
def settings
#user = current_user
if #user
render "devise/accounts/settings"
else
render file: 'public/404', status: 404, formats: [:html]
end
end
def passwords
#user = current_user
if #user
render "devise/accounts/passwords"
else
render file: 'public/404', status: 404, formats: [:html]
end
end
def security
#user = current_user
if #user
render "devise/accounts/security"
else
render file: 'public/404', status: 404, formats: [:html]
end
end
protected
def update_resource(resource, params)
if params[:current_password].blank? && params[:password].blank? && params[:password_confirmation].blank? && params[:email].blank?
resource.update_without_password(params.except(:current_password, :password, :password_confirmation, :email))
else
resource.update_with_password(params)
end
end
end
If you want to replace country_select with geocoder, you just need to take this values in controller that you need.
class UsersController < ApplicationController
...
def create
#user = User.new(user_params)
#user.user_information.country = country_name(request.location.country_code)
#user.user_information.city = request.location.city
#user.save
end
end
If you're using Devise...
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def new
super
end
def create
# put above logic here
end
def update
super
end
end
You need to implement helper/model methods needed.

How to check for roles using rolify gem?

I have a user model, where I am having methods to check for roles. I have total 5 to 6 roles. Super administrator should have access to view users with all the roles. I am using rolify gem (has_role?) for checking admin role. Can someone guide me on how to use it? As of now, I am getting undefined method include? error.
class User < ActiveRecord::Base
extend FriendlyId
self.table_name = "DIM_USER"
self.primary_key = "user_id"
self.sequence_name = 'DIM_USER_ID_SEQ'
rolify
devise :database_authenticatable,
:recoverable, :rememberable, :trackable, :validatable, :timeoutable
before_validation :strip_whitespace, :only => [:email]
default_scope {where(clean_up_flag: false)}
has_many :store_user_assignments
has_many :stores, through: :store_user_assignments
has_and_belongs_to_many :clients, join_table: :clients_users
has_many :store_user_assignments
has_many :stores, through: :store_user_assignments
belongs_to :role
before_save {self.email = email.downcase}
before_save :update_full_name
after_create :create_slug
friendly_id :slug, use: :slugged
NAME_MIN_LENGTH = 1
NAME_MAX_LENGTH = 100
EMAIL_MAX_LENGTH = 100
NAME_RANGE = NAME_MIN_LENGTH..NAME_MAX_LENGTH
validate :password_check
validates :encrypted_password, presence: true
scope :admin, -> {joins(:users_roles, :DIM_ROLE).where("users_roles.role_id = DIM_ROLE.role_id AND DIM_ROLE.name = 'super_administrator'").order(:last_name)}
scope :pmt_ptl_accnt_manager, -> {joins(:users_roles, :DIM_ROLE).where("users_roles.role_id = DIM_ROLE.role_id AND DIM_ROLE.name = 'Portal-Account-Manager-Client'").order(:last_name)}
scope :inactive_pmt_ptl_accnt_manager_with_no_stores, -> {joins(:users_roles, :DIM_ROLE).where("users.active=? AND users_roles.role_id = DIM_ROLE.role_id AND DIM_ROLE.name = ? AND users.user_id NOT IN (select user_id from stores_users)", false, 'Portal-Account-Manager-Client').order(:last_name)}
def active_for_authentication?
super && self.active?
end
def inactive_message
:invalid
end
def update_full_name
self.full_name = "#{first_name} #{last_name}"
end
def admin?
self.has_role?(:super_administrator)
end
def vt_user?
self.has_role?(:Virtual-Terminal-User)
end
def pmt_ptl_accnt_manager?
self.role.include?(Role.where(:name => 'Portal-Account-Manager-Client').first) ||
self.role.include?(Role.where(:name => 'Radial-Account-Manager').first)
end
def radial_account_manager?
self.role.include?(Role.where(:name => 'Radial-Account-Manager').first)
end
def payments_portal_readonly?
self.role.include?(Role.where(:name => 'Portal-Account-Manager-Read-Only-Client').first)
end
def search_user?
self.role.include?(Role.where(:name => 'Transaction-Search-Only').first)
end
def radial_readonly?
self.role.include?(Role.where(:name => 'Radial_ReadOnly').first)
end
end
According to the documentation, you should call has_role? method.
def pmt_ptl_accnt_manager?
self.has_role?('Portal-Account-Manager-Client') || self.has_role?('Radial-Account-Manager')
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

Resources