Search code examples
ruby-on-railsdevisecancan

Rails: Ask user authentication before CanCan access denied


I want to show login form instead of cancan access denied flash message. this is my controller

#app/controllers/oferts_controller.rb
class OfertsController < ApplicationController
  before_action :set_ofert, only: [:show, :edit, :update, :destroy]
  load_and_authorize_resource :only => [:new, :edit, :destroy] 
  before_filter :authenticate_user!, :except => [:show, :index]
  # GET /oferts
  # GET /oferts.json
  def index
    @oferts = Ofert.areactive
  end

  # GET /oferts/1
  # GET /oferts/1.json
  def show
  end

  # GET /oferts/new
  def new
    @ofert = current_user.oferts.new
    @ofert.purchasing_group = PurchasingGroup.new
  end

  # GET /oferts/1/edit
  def edit
  end

  # POST /oferts
  # POST /oferts.json
  def create
    ....
  end

  # PATCH/PUT /oferts/1
  # PATCH/PUT /oferts/1.json
  def update
  ....
  end

  # DELETE /oferts/1
  # DELETE /oferts/1.json
  def destroy
   ...
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_ofert
      @ofert = Ofert.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def ofert_params

      params.require(:ofert).permit(:title, :short_title, :description, :price, :normal_price, :ends_at, :ends_at_date, :ends_at_time)
    end
end

For some reason when I try to access an action that requires login, for example, "create", I only get the access denied message from CanCan, but Im not redirected to the login page as it should, since I have the :authenticate_user! before_filter in the controller.

How can prioritize the devise redirection to login page instead of the cancan access denied flash message?

Thanks

Update: This is my Gemfile

    source 'https://rubygems.org'


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.1.9'
# Use mysql as the database for Active Record
gem 'mysql2'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# Use CoffeeScript for .js.coffee assets and views
gem 'coffee-rails', '~> 4.0.0'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer',  platforms: :ruby

# Use jquery as the JavaScript library
gem 'jquery-rails'
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
gem 'turbolinks'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.0'
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', '~> 0.4.0',          group: :doc

gem 'money-rails', '~> 1.2.0'

gem 'money', '~> 6.5.0'
# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'

gem 'foundation-rails'
gem 'font-awesome-rails', '~>4.3.0.0'
gem 'devise'
gem "cancan"
gem "rolify"
gem "paperclip", "~> 4.2"
gem 'rails-i18n', '~> 4.0.0'
gem 'jquery-ui-rails'
gem 'whenever', :require => false
gem 'jquery-countdown-rails'
# Use unicorn as the app server
# gem 'unicorn'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Use debugger
# gem 'debugger', group: [:development, :test]

group :development do
    # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
    gem 'spring'
    gem 'quiet_assets', '~>1.1'
end

group :development, :test do
  gem 'rspec-rails', '~> 3.0.0'
  gem 'rspec-expectations'
  gem 'factory_girl_rails', '~> 4.0'
end

group :test do
  gem 'capybara', '~> 2.3.0'
  gem 'capybara-email'
  gem 'shoulda-matchers', '~> 2.7.0'
  gem 'rspec-its'
  gem 'rspec-activemodel-mocks', '~> 1.0.1'
end

Solution

  • For the record, here's the conventional and DRY way to do it:

    class ApplicationController < ActionController::Base
    
      # ...
    
      rescue_from CanCan::AccessDenied, with: :access_denied
    
      # ...
    
      private
    
      def access_denied(exception)
        store_location_for :user, request.path
        redirect_to user_signed_in? ? root_path : new_user_session_path, alert: exception.message
      end
    
    end
    

    This will store the requested location in a session and render Devise login form if user is currently not signed in. Stored location will trigger redirect to the requested location after successful sign in.

    Placing the code in ApplicationController will define such behavior globally.

    If you don't need this for the every controller, you are free to put rescue_from CanCan::AccessDenied, with: :access_denied line where you want it, OfertsController in your case. You'll also need to list access_denied method as protected rather than private.