I have a problem with 3 levels nesting of models in CanCan combined with Inherited Resources. I've read that we should nest everything up to 2 levels, but I had to put everything under account model and now I've tried doing this in CanCan:
load_and_authorize_resource :account
load_and_authorize_resource :project, :through => :account
load_and_authorize_resource :model, :through => :project
That gives me #account variable that has a value of #project, like it is overwriting that. #project is what is supposed to be and #model too. Is that fault of mine, CanCan's, Inherited Resources or just CanCan isn't supporting 3 levels nesting? Also, I do this in IR for the ModelsController.
belongs_to :account, :finder => :find_by_name! do
belongs_to :project, :finder => :find_by_name!
end
Another strange thing is when i remove the part load_and_ from CanCan's definition. It works then, but I've read that it can be dangerous not to use the load part.
Can I use only the authorize_resource or should I do something with CanCan?
Your authorizations have been correct as far as I can say.
The developer of the CanCan gem ryan posted how this should behave: https://github.com/ryanb/cancan/issues/127#issuecomment-364475
That means that your
load_and_authorize_resource :account
load_and_authorize_resource :project, :through => :account
load_and_authorize_resource :model, :through => :project
will end up in an block like this (here: create action. For other actions should the last authorize! and the #model change):
#account = Account.find(params[:account_id])
authorize! :read, #account
#project = #account.projects.find(params[:project_id])
authorize! :read, #project
#model = #project.models.build
authorize! :new, #model
I hope that this answer can help developers looking for nested cancan authorization :-) .
source: https://github.com/ryanb/cancan/issues/127#issuecomment-364475
ps: wrong behavior for /accounts/1/projects/2/models/new:
load_and_authorize_resource :project
load_and_authorize_resource :model, :through => :project
This is kind of a security issue, because this will do
#project = Project.find(params[:project_id])
[...]
, and does not check if the current account is allowed to read the linked account '1'.
And it does not check, if the project '2' is really a project of account '1'.
Related
Have a nested resource as such
class Dealer < ActiveRecord::Base
has_many :vehicles
end
and
class Vehicle < ActiveRecord::Base
belongs_to :dealer
end
below are my routes.
resources :dealers do
resources :vehicles, :except => [:index]
end
resources :vehicles, :only => [:index]
looking at the wiki at the github page for cancan I did the following:
class VehiclesController < ApplicationController
load_and_authorize_resource :dealer
load_and_authorize_resource :vehicle, :through => :dealer
def index
#vehicles = Vehicle.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #vehicles }
end
end
end
but now when the admin tries to go to the index page with the abilities:
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.has_role? :admin
can :manage, :all
end
end
I get
Couldn't find Dealer with id=
what do i need to change for admin to still be able to do all the actions and yet have others be checked before they can do any action.
The problem is not that he is not authorized to this action. The problem is that CanCan tries to fetch an instance of dealer to load all its vehicles and you have not provided a :dealer_id within params[:dealer_id]. Cancan assumes you would be loading only dealer's vehicles in this controller because you used an load_and_authorieze :through. This authorization should be used within Dealers::VehiclesController.
As long as you only loading vehicles just use load_and_authorize_resource :vehicle. And because load_and_authorize will set #vehicles for you within the before filter there is also no need to load the vehicles explicitly with Vehicle.all.
load_and_authorize is just a convenient method and it assumes some defaults. Once you will come to a point where you have some more complex use case. It will be time to throw away this method and just use CanCan's authorize!, can? and a properly configured Vehicle .accessible_by (for listing) methods.
When using load_and_authorize_resource :vehicle, through: :dealer it expects to receive a dealer_id in the request in order to authorize the dealer.
Since you use except: :index in your routes dealer_id will not be automatically included in the request.
If you don't want to authorize the dealer in the index action you can do something like this (taken from Can Can wiki)
class CommentsController < ApplicationController
load_and_authorize_resource :post
load_and_authorize_resource :through => :post
skip_authorize_resource :only => :show
skip_authorize_resource :post, :only => :show
end
I have combined custom authentication with CanCan and to my frustration it is not exactly working as expected. So, here is the snippet of code in my controller
class Profile < ActiveRecord::Base
belongs_to :user, :inverse_of => :profile
delegate :email, to: :user
validates_presence_of :user_id
end
class Api::V1::ProfileController < Api::V1::UserActivityController
load_and_authorize_resource :user
load_and_authorize_resource :profile, :through => :user, :class => "Profile"
def index
#profile = subject_user.profile
authorize! :index, #profile
end
end
Here is the Ability class
class Ability
include CanCan::Ability
def initialize(user)
alias_action :index, :show, :to => :read
can :read, Profile
end
end
If I try can :read, :all it works fine but with can :read, Profile it fails. I had the initial hypothesis that maybe the delagate in the Profile class causes the denial but that is not the case. any thoughts?
The page is supposed to render into JSON using rabl
object #profile
attributes :first_name, :last_name, :gender
-- Update --
I figured that namespacing and nesting causes the issues with my code. My controlled is in fact namespaced (I updated the controller code) and I am trying to access Profile throught he following routing:
namespace :user
namespace :profile
end
I tried the following code for load_and_authorize_resource in ProfileController (also updated the controller code above) but it is not working. Any thoughts on how it should be modified? Does anything need to be changed in the Ability class?
load_and_authorize_resource :user
load_and_authorize_resource :profile, :through => :user, :class => "Profile"
I am using Rails Inherited_resource gem in my comments controller, and comments is a nested resource so:
resources :projects do
resources :comments do
end
I also have a belongs_to in the comments controller:
belongs_to :project, :finder => :find_by_project_uuid!, :class_name => "Thfz::Project", :polymorphic => true
How can I set the comment's user association to the current_user(user_id) when its created? As user_id is not suppose to be massive assigned.
I tried following:
def begin_of_association_chain
current_user
end
This does set the user id correctly, but I cannot get nested resource working for Project with this.
Same question come when destroy a comment, I will need to find the comment through current_user, so how to achieve this?
So do I have to write my own create and destroy actions?
Thanks :)
Have you tried the following inside comments_controller?
class CommentsController < InheritedResources::Base
before_filter :authenticate_user! # Assuming you are using Devise for authentication
respond_to :html, :xml, :json
belongs_to :project, :finder => :find_by_project_uuid!, :class_name => "Thfz::Project"
def create
#comment = build_resource
#comment.author = current_user
create!
end
end
I have an Owner model wich has_one Address, and accepts_nested_attributes for it. When loading a Owner, for the :new action, I expected the :load_resource method to build the association like #owner.build_address, but this don't happen with the code below:
class OnwersController < ApplicationController
load_and_authorize_resource
load_resource :address, :through => :owner, :singleton => true, :parent => false
Is this the expected behaviour and I have to do #owner.address = #address by my own ?
Thank you
You may refer to the answer. https://stackoverflow.com/a/7015900/950843
It works for me except I have to ignore both :new and :create actions.
I'd like to create a numerical rating system in rails where users can rate a post from 1 - 10.
I've looked on Google but I only find outdated tutorials and star rating gems which simply don't do the job for me.
Perhaps someone can point me to a gem that can help me achieve this?
Ruby Toolbox lists several, although most are DOA. Mongoid_ratings seemed to be the most recently updated, although you may not want to go the Mongo route.
https://www.ruby-toolbox.com/categories/rails_ratings
I would suggest building from scratch. Heres a quick (and probably non-functional/non-secure) hack that might help get you started:
Routes
resources :articles do
resources :ratings
end
Models
class Article < ActiveRecord::Base
has_many :ratings, :dependent => :destroy
end
class Rating < ActiveRecord::Base
belongs_to :article
validates_presence_of :article
validates_inclusion_of :value, :in => 1..10
end
Controllers
class RatingsController < ApplicationController
before_filter :set_article
def create
#rating = #article.ratings.new :value => params[:value]
if #rating.save
redirect_to article_ratings_path(#article), :notice => "Rating successful."
else
redirect_to article_ratings_path(#article), :notice => "Something went wrong."
end
end
def update
#rating = Rating.find(params[:id])
#rating.update_attribute :value, params[:value]
end
private
def set_article
#article = Article.find(parms[:article_id])
end
end
In an article view somewhere:
form_for [#article,#rating] do |f|
f.select("rating", "value", (1..10))
f.submit "Rate this Article"
end
Have a look at the Letsrate gem: https://github.com/muratguzel/letsrate
Works great for me.