Rails 3 CanCan nested Resources - ruby-on-rails

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

Related

Authorize nested controller

I have this setup:
routes.rb:
resources :orders, only: [] do
# many other member actions
# ...
namespace :confirm, module: :orders do
controller :confirm do
get :cash
end
end
end
orders/confirm_controller.rb:
class Orders::ConfirmController < ApplicationController
load_and_authorize_resource class: Order,
id_param: :order_id,
instance_name: :order
def cash
end
end
ability.rb:
can :cash, Order do |order|
order.buyer == user
end
The idea is: since OrdersController already overloaded with almost 20 actions, I want move new actions to nested controller. The code above is working but I feel that this part:
load_and_authorize_resource class: Order,
id_param: :order_id,
instance_name: :order
can be done easier. And ability that I have is for parent controller and not namespaced to nested controller, so if someone define cash method in OrdersController same ability will be used, what is not good. So is there any better way?

How to Configure Routes of Scaffold Generator in Rails

I'm having trouble with configuring the model which is generated by scaffold in my Rails 4 application.
These are my models:
class Contact < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :contact
after_create :make_contact
def make_contact
create_contact(
:country => "USA",
:city => "Newyork"
)
end
end
As you can see, I'm creating a Profile model instance for each User when they registered for the site.
I generated User model with Devise GEM and generated Contact model with rails scaffold generator.
1) I want my Users to only update or view their profile. I want to prevent them to list all profiles, destroy their profiles or create a new profile. What is the best approach to do this?
2) I want my application to redirect automatically to the users related profile page when they visit /contacts route.
3) User can't be able to see other users profiles by changing the URL like /contacts/1, contacts/2 etc.
How can I do this?
Thanks.
User before_filter/before_action in your controller
def UsersController < ApplicationController
before_filter :restrict_user, :only => [:show, :edit, :update]
private
def restrict_user
redirect_to :root, :alert => "Not authorized" unless params[:id] = current_user.id
end
end
In your routes, you can specify only the actions that you want
resources :users, :only => [:new, :create, :edit, :update, :show] #index and destroy are not in the list
You can do the same in contacts controller too

set current user when creating a comment with inherited_resource rails

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

Devise and cancan gems: has_many association

I use devise and cancan gems and have simple model association: user has_many subscriptions, subscription belongs_to :user. Have following SubscriptionsController:
class SubscriptionsController < ApplicationController
load_and_authorize_resource :user
load_and_authorize_resource :subscription, through: :user
before_filter :authenticate_user!
def index
#subscriptions = #user.subscriptions.paginate(:page => params[:page]).order(:created_at)
end
#other actions
end
And Cancan Ability.rb:
class Ability
include CanCan::Ability
def initialize(user)
user ||=User.new
can [:read], [Edition, Kind]
if user.admin?
can :manage, :all
elsif user.id
can [:read, :create, :destroy, :pay], Subscription, user_id: user.id
can [:delete_from_cart, :add_to_cart, :cart], User, id: user.id
end
end
end
The problem is that i cannot use subscriptions actions as a user but can as a admin. And have no problems with UsersController. When i delete following lines from SubscriptionsController:
load_and_authorize_resource :user
load_and_authorize_resource :subscription, through: :user
before_filter :authenticate_user!
Have no problems at all. So the issue in these lines or in Ability.rb. Any suggestions?
UPDATE: It's interesting that if i add smth like can? :index, Subscription to html template it displays true. If add smth like can? :index, Subscription.first (subscription of another user) it shows false. Looks like Cancan works normally. But what's the problem?..
UPDATE: If change SubscriptionsControlle like:
class SubscriptionsController < ApplicationController
#load_and_authorize_resource :user
#load_and_authorize_resource :subscription, through: :user
before_filter :authenticate_user!
def show
#user = User.find params[:user_id] #line 1
#subscription = #user.subscriptions.find params[:id] #line 2
#container_items = #subscription.container_items.paginate(:page => params[:page])
authorize! :show, #subscription #line 4
end
#some actions
end
It works perfect and prevent unauthorized user access when need.
Are the lines #1, 2 and 4 not equivalent to commented?..
UPDATE: Have the following in routes.rb:
resources :users, except: [:show] do
member do
get 'cart'
delete 'delete_from_cart' => 'users#delete_from_cart'
post 'add_to_cart' => 'users#add_to_cart'
end
resources :subscriptions do
member do
post 'pay'
end
end
end
UPDATE: Next solution prevent unauthorized access to all of subscriptions actions except index:
class SubscriptionsController < ApplicationController
load_resource :user
load_resource :subscription, through: :user
authorize_resource through: :current_user
before_filter :authenticate_user!
#actions
end
So what's the best way to prevent access to index action?
Found only following solution:
before_filter :authorize_index, only: [:index]
def authorize_index
raise CanCan::AccessDenied unless params[:user_id] == current_user.id.to_s
end
It should be
load_and_authorize_resource :subscription
or just
load_and_authorize_resource
in your case, when you want nested resource, then
load_and_authorize_resource :through => :current_user
see https://github.com/ryanb/cancan/wiki/Nested-Resources

best way to establish a relationship between 3 models?

I have these 3 models: and here is what i need:
Basically I have records, and a user must hold a specific role assigned to him to be able to make changes to that record. each different record can have multiple roles and each role can belong to many different versions of a record. now that im having a version control, each single role can belong to many different versions of a record( different versions of the one record can be associated through the common secondary_id attribute for records).
What is the best way to establish this relationship between the three models?
thanks
Use cancan, http://github.com/ryanb/cancan. It'll give you exactly what you need.
Try this:
Change your User model:
class User
has_many :roles
has_many :records
def has_access_to?(obj)
roles.exists?(:id => obj.role_ids)
end
end
Controller code:
class RecordsController < ApplicationController
before_filter :require_user # checks if the user is logged in
before_filter :load_record, :except => [:index]
before_filter :require_permission, :except => [:new, :create, :index]
private
def require_permission
return true if current_user.has_access_to?(#record)
render :text => "You don't have permission to complete this action.",
:status => '401 Unauthorized'
return false
end
def load_record
case(action.to_sym)
when :new, :create
#record = current_user.records.build(params[:record])
when :edit, :update, :show, :destroy
#record = current_user.records.find(params[:id])
end
end
end

Resources