1

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.

James Chevalier
  • 10,604
  • 5
  • 48
  • 74
Mostafa Hussein
  • 11,063
  • 3
  • 36
  • 61

3 Answers3

1

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
graywh
  • 9,640
  • 2
  • 29
  • 27
  • And what about the assigned employee how could he see the assigned tickets only ??? ( employee is a user as well but i don't know if its related to user in the correct way or not) i hope you could help me i have been stuck on this for 3 days – Mostafa Hussein Sep 16 '13 at 22:57
  • also i need to hide the index , for example we made the user can show only what he created , also i need to hide what he has not created form the index – Mostafa Hussein Sep 16 '13 at 23:16
  • 1
    @Dexter I've given you an example of how to restrict the read access for a user to tickets he "owns". Only those tickets will appear in the index because they are queried with the condition `"tickets.user_id = #{user.id}"`. – graywh Sep 17 '13 at 14:42
  • @Dexter As for `#initialize_grid`, you pass in `Ticket`, but since I don't know how it works I can only suggest you try using `@tickets` instead. – graywh Sep 17 '13 at 14:43
  • i found where to use the accessible_by but i don't know how to write it inside , if you have any suggestions please. http://stackoverflow.com/questions/18858352/filter-index-for-cancan – Mostafa Hussein Sep 18 '13 at 11:30
0

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
Jon
  • 10,678
  • 2
  • 36
  • 48
0

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
  • The accessible_by call cannot be used with a block 'can' definition. – Mostafa Hussein Sep 16 '13 at 01:04
  • 1
    check this https://github.com/ryanb/cancan/wiki/Authorizing-Controller-Actions#index-action the accessible_by should be default when you call the load_and_authorize_resource , but you override it by calling Ticket.all for your resources – syaifulsabril Sep 16 '13 at 01:29
  • `d_and_authorize_resource def index @tickets_grid = initialize_grid(Ticket, :include => [{:user => :user_type}, :employee_department, :state]) end` this is my index now and still getting all index not the assigned only – Mostafa Hussein Sep 16 '13 at 01:43
  • do i need to modify my models ? – Mostafa Hussein Sep 16 '13 at 04:01
  • the normal user already cannot access the index , my problem with who can access it already. i need to make them able to see the assigned for them only! , and i don't know how to make this with cancan because employee.id not the same as user.id – Mostafa Hussein Sep 16 '13 at 04:17
  • You should debug/find out why the accessible_by method return all the tickets. – syaifulsabril Sep 16 '13 at 10:21
  • this is the problem of my application do you have a solution for it but not with STI http://stackoverflow.com/questions/18828969/cannot-map-employee-back-to-user-id-correctly – Mostafa Hussein Sep 16 '13 at 14:28