I'm relatively new to rails and exploring cancancan gem . Now I do understand this gem and functionality. let me give example lines which I perfectly understand.
can :read, :all
cannot :read, GpPatient
cannot :read, GpDenguePatient
cannot :zero_patient, Patient
cannot :released_patients, Patient
Now these are generic function names like read includes show and index and so on.
Now I am putting lines which I don't understand clearly.
can :combined_map, Patient
can :case_response_evidence, Patient
There names of actions are not the ones which are generic, What this is doing.
I am putting a controller code here also just to get the clear explanation.
Thanks.
class DashboardController < ApplicationController
def case_response_evidence
authorize! :case_response_evidence, Patient
end
def combined_map
authorize! :combined_map, Patient
end
end
Related
I'm working with the acts_as_votable gem for a project that will allow users to 'like' their favorite courses and their favorite guides. The favorited guides will then show up on one page and the favorited courses on another. I'm having trouble with retrieving model specific results in my controller below is code that works but is not scoping to a specific controller.
class FavoritesController < ApplicationController
def guides
end
def courses
user = current_user
#courses = user.find_up_voted_items
end
end
This is the only code I've gotten to work, I realize there is nothing in the controller currently to narrow down the results to a specific model but I wasn't able to get anything I tried to work.
From the acts_as_votable docs:
Members of an individual model that a user has voted for can also be
displayed. The result is an ActiveRecord Relation.
#user.get_voted Comment
#user.get_up_voted Comment
#user.get_down_voted Comment
https://github.com/ryanto/acts_as_votable
So in your case I would use:
class FavoritesController < ApplicationController
before_action :get_user, only: %i[guides courses]
def guides
#guides = #user.get_up_voted Guide
end
def courses
#courses = #user.get_up_voted Course
end
private
def get_user
#user = current_user
end
end
I've added a controller collaborators to manage a particular type of join association between Users and Companies. The issue is that whenever I load anything from collaborators, I get the error
uninitialized constant Collaborator
From my understanding, this is because there is no model Collaborator and I am using cancancanfor authorization. From the old cancan (note not cancancan) documentation, I've been able to gather that controllers that don't have a corresponding model need to have a model manually authorized for them something like: load_and_authorize_resource :the_model, :parent => false.
This seems to work if I disable load_and_authorize_resource in my application.rb controller.
SO my quesestion is: what is the best way to authorize controllers that don't have corresponding models with cancancan? Can I continue to load_and_authorize_resource in my application controller?
Many thanks in advance.
This LINK will help.
From the link, I quote,
class ToolsController < ApplicationController
authorize_resource :class => false
def show
# automatically calls authorize!(:show, :tool)
end
end
And in your ability.rb:
class Ability
include CanCan::Ability
def initialize(user)
can :show, :tool
end
end
I have two models:
Student
Classroom
Both of them have an action that does the same exact thing: it shows a report of daily activity. That is:
/students/1
/classrooms/1
Grabs activity for the model in question and displays it on the page.
In an attempt to dry this up, I created a ReportsController which extracts all the common logic of building a report.
If I leave the routes like this:
/students/1/report
/classrooms/1/report
Then I can have the ReportsController#show action look for params for :student_id or :classroom_id to determine which model type it is dealing with (for purposes of querying the database and rendering the correct view).
But I would prefer the URLs to be cleaner, so I also changed my routes.rb file to pass the show action for these models to the reports#show controller action:
resources :students, :classrooms do
member do
get :show, to: 'reports#show'
end
end
This works, but I can no longer depend on params to identify which model to work with and which view to render.
Question: should I parse request.fullpath for the model? Or is there a better way to make a shared controller understand which model it is working with?
Routing both show methods to the same controller method for code reuse is somewhat like banging a nail in with a dumptruck.
Even if you can find the resource by looking at the request url you would start splitting the ResortsController into a bunch of ifs and switches even before you got off the ground.
One solution is to add the common action in a module:
module Reporting
extend ActiveSupport::Concern
def show
# the Student or Classroom should be available as #resource
render 'reports/show'
end
included do
before_action :find_resource, only: [:show]
end
private
def find_resource
model = self.try(:resource_class) || guess_resource_class
#resource = model.find(params[:id])
end
# This guesses the name of the resource based on the controller name.
def guess_resource_class
self.class.name[0..-11].singularize.constantize
end
end
class StudentController < ApplicationController
include Reporting
end
# Example where resource name cannot be deduced from controller
class PupilController < ApplicationController
include Reporting
private
def resource_class
Student
end
end
self.class.name[0..-11].singularize.constantize is basically how Rails uses convention over configuration to load a User automatically in your UsersController even without any code.
But the most important key to DRY controllers is to keep your controllers skinny. Most functionality can either be moved into the model layer or delegated out to service objects.
I would put the common logic in the Event Model:
#Event Model
class Event < ...
def self.your_event_method
#self here will be either student.events or classroom.events
#depending on which controller called it
end
end
class StudentsController < ...
...
def show
student = Student.find(params[:id])
student.events.your_event_method
end
end
class ClassroomsController < ...
...
def show
classroom = Classroom(params[:id])
classroom.events.your_event_method
end
end
I know there's probably solutions to this elsewhere, but I'm looking for help that works specifically in my case because I'm having a lot of trouble translating other solutions into my situation.
I currently have a device set up and the database is seeded so an admin is already created. Everyone else that signs up after that is a user.
There are two tables right now, a user table generated by rails and a cadet table. The cadet table stores information such as company, room number, class year and such.
My question is, how do I allow a user to edit/destroy only the cadet record that they've created? I know it seems like a big question but I've been looking all over and still can't find a reasonable way to implement this. Thank you!
Devise is related to authentication (who you are), you need a solution for authorization (who can do what). My suggestion is to go for CanCan (https://github.com/ryanb/cancan), which is a gem very widely use together wide Devise.
For your example, and after install the gem via Gemfile+Bundler:
Initialize the gem for your User model
rails g cancan:ability
it will create a file in app/models/ability.rb to define your restrictions
Define your restrictions, for instance:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (this line it to manage users not logged in yet)
if user
can :manage, Cadet, user_id: user.id
end
end
end
That will allow a user just to read, create, edit and destroy Cadets which user_id matches the id for the User.
Take a look at CanCan github page is wery well documented and with lot of examples; it's very simple to set up and works great.
You can also use a before_filter, something like the following:
class CadetsController < ApplicationController
before_filter :cadet_belongs_to_user, only: [:show, :edit, :update, :destroy]
....
private
def cadet_belongs_to_user
# following will work only on routes with an ID param
# but there are a few ways you could check if the cadet
# belongs to the current user
unless current_user && current_user.cadets.where(id: params[:id]).any?
flash[:notice] = "You are not authorized to view this page."
redirect_to root_path
end
end
end
I have several controllers that require a correct user for their edit/update/delete actions. What is the Rails-way to accomplish the following:
Currently, in each controller I have the following code:
class FooController < ApplicationController
before_filter :correct_user, :only => [:edit, :update, :destroy]
# normal controller code
private
def correct_user
#foo = Foo.find params[:id]
redirect_to some_path unless current_user == #foo.user
end
end
I have similar code in 3 controllers. I started to bring it out to a helper like this:
module ApplicationHelper
def correct_user( object, path )
if object.respond_to? :user
redirect_to path unless object.user == current_user
end
end
But I'm wondering if this is a good way to do it. What's the accepted way to solve this?
Thank you
EDIT
The correct user check here is because I want to make sure it's only the author who can make edits/deltes to each of the objects.
To clarify, the objects would be things like Questions and Posts. I don't want to use something like CanCan as it's overkill for something simple like this.
I really like using RyanB's CanCan, which allows you to both restrict access to actions based on the user, and centralize such authorization into basically a single file.
CanCan on GitHub: https://github.com/ryanb/cancan
Screencast explaining how to setup/use it: http://railscasts.com/episodes/192-authorization-with-cancan
EDIT
No problem. I hear you on CanCan - it takes a little while to get up and running on it, but it's designed to do exactly what you're asking - per object authorization.
Alternative:
Another way to do this is move your authoriship/current_user check to the ApplicationController class, from which all of your other Controllers inherit (so they will get that code through inheritance - and you don't need to write the same code in multiple Controllers), and it would look something like...
class ApplicationController < ActionController::Base
...
helper_method :correct_user
private
def correct_user( object, path )
redirect_to path unless object.user == current_user
end
end
You should do the following :
def edit
#foo = current_user.foos.find(params[:id])
end
This way, only if the current user is the owner of the Foo he will be able to see it.