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
Related
I'm trying to build a software for small banks, which involves deals, pools (of banks) and credit facilities. For information, a facility belongs to a pool of banks, which belongs to a deal.
Below is my issue when i try to create a "facility" :
Couldn't find Pool with 'id'=
I have 3 models : Deal, Pool, Facility
class Deal < ActiveRecord::Base
has_many :banks
has_many :pools, :dependent => :destroy
end
class Pool < ActiveRecord::Base
belongs_to :deal
end
class Facility < ActiveRecord::Base
belongs_to :pool
end
Below is my Facilitys controller :
class FacilitysController < ApplicationController
before_action :authenticate_user!
before_action :set_pool
before_action :set_facility, only: [:show, :edit, :update, :destroy]
def new
#pool = Pool.find(params[:id])
#facility = Facility.new
end
def edit
#facility = Facility.find(params[:id])
end
def create
#facility = Facility.new(facilitys_params)
if #facility.save
redirect_to root_path, notice: "Facility successfully created!"
else
render "New"
end
end
def show
#facility = Facility.find(params[:id])
#facility.pool_id = #pool.id
end
def update
#facility.update(facilitys_params)
if #facility.update(facilitys_params)
redirect_to deal_facility_url(#pool, #facility), notice: "Facility successfully updated!"
else
render :edit
end
end
def destroy
#facility.destroy
end
private
def set_pool
#pool = Pool.find(params[:id])
end
def set_facility
#facility = Facility.find(params[:id])
end
def facilitys_params
params.require(:facility).permit(:name)
end
end
My routes are
resources :deals do
resources :pools, except: [:index] do
resources :facilitys, except: [:index]
end
end
Looks like you're trying to use nested resources, but you've not provided enough information.
If your routes are not configured like this:
resources :pools do
resources :facilities
end
...then please add the relevant routes to your question.
If your routes are configured like that then good, but now your set_pool is incorrect. Take a look at the output of rake routes and you should see something like this for your Facility routes:
pool_facility_index GET /pools/:pool_id/facility(.:format) facility#index
POST /pools/:pool_id/facility(.:format) facility#create
new_pool_facility GET /pools/:pool_id/facility/new(.:format) facility#new
edit_pool_facility GET /pools/:pool_id/facility/:id/edit(.:format) facility#edit
pool_facility GET /pools/:pool_id/facility/:id(.:format) facility#show
PATCH /pools/:pool_id/facility/:id(.:format) facility#update
PUT /pools/:pool_id/facility/:id(.:format) facility#update
DELETE /pools/:pool_id/facility/:id(.:format) facility#destroy
Notice how there are two params mentioned in each route, :id and :pool_id. Now take a look at your set_pool method and see which param you're using to find the Pool. You need to change that to use params[:pool_id] too.
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'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
I have the following in my ability model :
class Ability
include CanCan::Ability
#...
def superuser_rules
can :access, :items
cannot :update, :items
can :update, :items, :foo_attributes
end
end
I have a form which mirrors that by only displaying the foo_attributes nested form.
However, when submitting the form, it says the access is denied to update the item.
Is there a way to circumvent this without adding new routes/actions ?
Many thanks !
You can create new actions to handle these "special attributes".
First you can clean up the special attributes of the params.
class UserController
before_filter :only => [:create, :update] { params[:user].delete(:accepted_at) }
end
Then you create a special action to change a special attribute:
def accept
User.find(params[:user_id]).update_attributes :accepted_at => Time.now
end
Now you can set different permissions for create, update, and accept actions.
class Ability
include CanCan::Ability
def initialize(user)
if user && user.admin?
can :accept, User
elsif user
can :update, User
end
can :create, User
end
end
Take a look at this too
I have a number of resources (Trips, Schedules, etc) with actions that should be limited to just the resource's owner.
How do you implement code with a #require_owner method defined in ApplicationController to achieve this? Ideally, the code will look up the inheritance chain for the owner so the before_filter will work on a :comment that belongs_to :trip that belongs_to :user.
class TripsController < ApplicationController
belongs_to :member
before_filter :require_owner
...
end
I don't fully follow the description (would a comment really be owned by the trip owner?), but expanding slightly on jonnii's answer, here is an example that restricts the trip controller:
class ApplicationController < ActionController::Base
...
protected
# relies on the presence of an instance variable named after the controller
def require_owner
object = instance_variable_get("##{self.controller_name.singularize}")
unless current_user && object.is_owned_by?(current_user)
resond_to do |format|
format.html { render :text => "Not Allowed", :status => :forbidden }
end
end
end
end
class TripsController < ApplicationController
before_filter :login_required # using restful_authentication, for example
# only require these filters for actions that act on single resources
before_filter :get_trip, :only => [:show, :edit, :update, :destroy]
before_filter :require_owner, :only => [:show, :edit, :update, :destroy]
...
protected
def get_trip
#trip = Trip.find(params[:id])
end
end
Assuming the model looks like this:
class Trip < ActiveRecord::Base
belongs_to :owner, :class_name => 'User'
...
def is_owned_by?(agent)
self.owner == agent
# or, if you can safely assume the agent is always a User, you can
# avoid the additional user query:
# self.owner_id == agent.id
end
end
The login_required method (provided by or relying on an auth plugin like restful_authentication or authlogic) makes sure that the user is logged in and provides the user with a current_user method, get_trip sets the trip instance variable which is then checked in require_owner.
This same pattern can be adapted to just about any other resource, provided the model has implemented the is_owned_by? method. If you are trying to check it when the resource is a comment, then you'd be in the CommentsController:
class CommentsController < ApplicationController
before_filter :login_required # using restful_authentication, for example
before_filter :get_comment, :only => [:show, :edit, :update, :destroy]
before_filter :require_owner, :only => [:show, :edit, :update, :destroy]
...
protected
def get_comment
#comment = Comment.find(params[:id])
end
end
with a Comment model that looks like:
class Comment < ActiveRecord::Base
belongs_to :trip
# either
# delegate :is_owned_by?, :to => :trip
# or the long way:
def is_owned_by?(agent)
self.trip.is_owned_by?(agent)
end
end
Make sure to check the logs as you are doing this since association-dependent checks can balloon into a lot of queries if you aren't careful.
There's a few different ways to do this. You should definitely check out the acl9 plugin (https://github.com/be9/acl9/wiki/tutorial:-securing-a-controller).
If you decide you want to do this yourself, I'd suggest doing something like:
class Trip < ...
def owned_by?(user)
self.user == user
end
end
class Comment < ...
delegate :owned_by?, :to => :trip
end
# in your comment controller, for example
before_filter :find_comment
before_filter :require_owner
def require_owner
redirect_unless_owner_of(#commemt)
end
# in your application controller
def redirect_unless_owner_of(model)
redirect_to root_url unless model.owned_by?(current_user)
end
Forgive me if there are any syntax errors =) I hope this helps!
Acl9 is a authorization plugin. I'd give you the link, but I don't have cut and paste on my iPhone. If no one else provides the link by the time I get to a computer, I'll get it for you. Or you can google. Whichever. :)
I have only just started using it, but it has an extremely simple interface. You just have to create a roles table and a roles_user. Let me know how it goes if you decide to use it.
Or just use inherited resources:
InheritedResources also introduces another method called begin_of_association_chain. It’s mostly used when you want to create resources based on the #current_user and you have urls like “account/projects”. In such cases you have to do #current_user.projects.find or #current_user.projects.build in your actions.
You can deal with it just by doing:
class ProjectsController < InheritedResources::Base
protected
def begin_of_association_chain
#current_user
end
end