2
votes

I am new at Ruby-on-Rails. I could use some help after looking around at this site and Cancan Guides. I am have trouble getting this to work for Cancan and Devise. the User (Devise) only has Prices, so Price belongs to User.

I have a user_id inside of my database for my Price migration:

  create_table "prices", :force => true do |t|
    t.string   "price_name"
    t.decimal  "price"
    t.date     "date"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "user_id"
  end

My Prices Controller ( everything was a scaffolded but the user_id which was separate from another migration into the Price table):

class PricesController < ApplicationController
  before_filter :authenticate_user!



  # GET /prices
  # GET /prices.xml
  def index
    @prices = Price.all

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @prices }
    end
  end

  # GET /prices/1
  # GET /prices/1.xml
  def show
    @price = Price.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @price }
    end
  end

  # GET /prices/new
  # GET /prices/new.xml
  def new
    @price = Price.new

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @price }
    end
  end

  # GET /prices/1/edit
  def edit
    @price = Price.find(params[:id])
  end

  # POST /prices
  # POST /prices.xml
  def create
    @price = current_user.prices.build(params[:price])

    respond_to do |format|
      if @price.save
        format.html { redirect_to(@price, :notice => 'Price was successfully created.') }
        format.xml  { render :xml => @price, :status => :created, :location => @price }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @price.errors, :status => :unprocessable_entity }
      end
    end
  end

  # PUT /prices/1
  # PUT /prices/1.xml
  def update
    @price = Price.find(params[:id])

    respond_to do |format|
      if @price.update_attributes(params[:price])
        format.html { redirect_to(@price, :notice => 'Price was successfully updated.') }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @price.errors, :status => :unprocessable_entity }
      end
    end
  end

  # DELETE /prices/1
  # DELETE /prices/1.xml
  def destroy
    @price = Price.find(params[:id])
    @price.destroy

    respond_to do |format|
      format.html { redirect_to(prices_url) }
      format.xml  { head :ok }
    end
  end
end

And then this is my Ability.rb (app/models/ability.rb):

class Ability
  include CanCan::Ability

   def initialize(user)
    user ||= User.new
    if user.admin?
        can :manage, :all
        cannot :destroy, User, :id => current_user.id
    else
        can :manage, Price, :user_id => user.id
    end
   end
end

My question is, how do i make it so only the current user can to edit or delete his or hers own Prices? My code keeps letting any User who is logged in do anything with anyone's Prices.

Thanks in advanced.

Updated code that works:

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new
    can :manage, Price, :user_id => user.id
  end
end

important* - remove (or comment out) edit,update,destroy,create and new instance variables (e.g. @), i was wondering why my code wasn't working and i removed the following and it did it:

def new
  #  @price = Price.new
def edit
  # @price = Price.find(params[:id])
def create
  # @price = Price.new(params[:price]) or @price = current_user.prices.build(params[:price])
def update
  # @price = Price.find(params[:id])
def destroy
  # @price = Price.find(params[:id])

Then at top of PricesController:

class PricesController < ApplicationController
  before_filter :authenticate_user!
  load_and_authorize_resource
  skip_authorize_resource :only => :show
2

2 Answers

3
votes

You have defined your permissions, but you must also check for them in your controllers and possibly views in order to enforce them.

The index and show actions, for example:

def index
  # Check if the current user can actually see the Price index.
  authorize! :index, Price

  @prices = Price.all

  # ...
end

def show
  @price = Price.find params[:id]

  # Check if the current user can actually see the Price.
  authorize! :show, @price

  # ...
end

As you can see, the call follows the format authorize! :action, object, where object can be the class itself or an instance of it. You should use the latter when you have one available.

To make this easy, you can just add this line somewhere in your controller:

authorize_resource

And it will automatically do authorize! params[:action], @price || Price for every action. The @price || Price idiom means @price will be used unless it is nil, in which case Price will be used.

Additionally, keep in mind that the call to authorize! will raise a CanCan::AccessDenied exception should the current user not have the required permissions. You should rescue from this exception in ApplicationController:

rescue_from CanCan::AccessDenied do |exception|
  redirect_to root_url, :alert => exception.message
end

Check out the Authorizing Controller Actions on the CanCan Wiki for more detailed information.

1
votes

try putting a before_filter into your controller perhaps, and make sure that the user can do the thing of interest. That protects the backend.

Then also use the cancan permissions to hide or otherwise visually protect the data on the front end.