Search code examples
ruby-on-railsruby-on-rails-4cancan

How to work with optionally nested resources and cancan?


I have resources that can optionally be accessed through another resources, like so

resources :projects do
  resources :tasks
end
resources :tasks

If tasks are accessed through project, I want to render some project-related info, and that is pretty much all the difference.

However, I use cancan and need to authorize all the things so in TasksController I write

load_and_authorize_resource :project
load_and_authorize_resource :task, through: :project

but this breaks functionality when we don't nest tasks.

How can I solve this elegantly?

My first thought was to use two controllers instead of TasksController and share all the common things using concerns but that's kind of messy (at least I would have to explicitly specify views).

Another approach I can think of is to authorize things manually instead of using cancan helpers.

Are there some other ways?


Solution

  • I'd limit the routes that can be accesible without the project scope:

    resources :projects do
      resources :tasks
    end
    resources :tasks, only: :index
    

    Then you might call the cancan helper method for the rest of actions and handle the custom loading and authorizing on the index method:

    class TasksController < ActionController::Base
      load_and_authorize_resource :project, except: :index
      load_and_authorize_resource :task, through: :project, except: :index
    
      def index
        authorize! :index, Task
        if params[:project_id].present?
          @project = Project.find(params[:project])
          authorize! :show, @project
          @tasks = @project.tasks.accessible_by(current_ability)
        else
          @tasks = Task.accessible_by(current_ability)
        end
      end
    

    Note: if you want to handle new/create/edit/update actions for both resources, it will require custom views and will be easier with two controllers.