Skip to content

Instantly share code, notes, and snippets.

@jhjguxin
Created July 13, 2012 07:19
Show Gist options
  • Save jhjguxin/3103325 to your computer and use it in GitHub Desktop.
Save jhjguxin/3103325 to your computer and use it in GitHub Desktop.
cancan Nested Resources

nested resources 一般用来搭配关联的 resources, cancan 可以根据父类实例来生成,或者过滤子类从而达到权限控制。 如果 在model 没有直接的 belongs_to or has_many 关联cancan 是不会正常工作的 除非, 提供了 parent.children parent.children.built 这样的方法给 cancan 调用, 一个解决方案是, 在父类中创建 一个 children 方法返回 一个 nest 的 resources 的 list。 但是在 bbtangcms 中 我使用的是单表继承 因此 会有一些 问题。

如果使用不到 对父类的认证 那么 完全可以不去使用 through 选项,

class Tag::TimelinesController < Tag::TagBaseController
  load_and_authorize_resource
end

class Ability
  include CanCan::Ability

  def initialize(user, controller_namespace)
    alias_action :index, :show, :to => :read
    alias_action :new, :to => :create
    alias_action :edit, :to => :update
  def create_tag_identity
    can :create, Identity
  end

  def create_tag_identities_timeline
    # can :manage, Category, :restaurant => {:user_id => user.id}
    can :create, Timeline
  end

  def create_tag_identities_timelines_category
    #debugger
    can :create, Category#, :identity => Identity.new, :timeline => Timeline.new
  end
end

如果需要使用 nested resources 作为 认证的首要前提 那么可以试试

class Tag::TimelinesController < Tag::TagBaseController
  load_resource :identity
  load_resource :timeline
  load_and_authorize_resource
end

Let's say we have nested resources set up in our routes.

resources :projects do
  resources :tasks
end

We can then tell CanCan to load the project and then load the task through that.

class TasksController < ApplicationController
  load_and_authorize_resource :project
  load_and_authorize_resource :task, :through => :project
end

This will fetch the project using Project.find(params[:project_id]) on every controller action, save it in the @project instance variable, and authorize it using the :read action to ensure the user has the ability to access that project. If you don't want to do the authorization you can simply use load_resource, but calling just authorize_resource for the parent object is insufficient. The task is then loaded through the @project.tasks association.

If the resource name (:project in this case) does not match the controller then it will be considered a parent resource. You can manually specify parent/child resources using the :parent => false option.

Nested through method

As of 1.4, it's also possible to nest through a method, this is commonly the current_user method.

class ProjectsController < ApplicationController
  load_and_authorize_resource :through => :current_user
end

Here everything will be loaded through the current_user.projects association.

Shallow nesting

As of 1.4, the parent resource is required to be present and it will raise an exception if the parent is ever nil. If you want it to be optional (such as with shallow routes), add the :shallow => true option to the child.

class TasksController < ApplicationController
  load_and_authorize_resource :project
  load_and_authorize_resource :task, :through => :project, :shallow => true
end

Singleton resource

What if each project only had one task through a has_one association? To set up singleton resources you can use the :singleton option.

class TasksController < ApplicationController
  load_and_authorize_resource :project
  load_and_authorize_resource :task, :through => :project, :singleton => true
end

It will then use the @project.task and @project.build_task methods for fetching and building respectively. 实际上通过 through的都会调用 parent.childrenparent.children.build这样的方法。

Polymorphic associations

Let's say tasks can either be assigned to a Project or an Event through a polymorphic association. An array can be passed into the :through option and it will use the first one it finds.

load_resource :project
load_resource :event
load_and_authorize_resource :task, :through => [:project, :event]

Here it will check both the @project and @event variables and fetch the task through whichever one exists. Note that this is only loading the parent model, if you want to authorize the parent you will need to do it through a before_filter because there is special logic involved.

before_filter :authorize_parent
private
def authorize_parent
  authorize! :read, (@event || @project)
end

Accessing parent in ability

Sometimes the child permissions are closely tied to the parent resource. For example, if there is a user_id column on Project, one may want to only allow access to tasks if the user owns their project.

This will happen automatically due to the @project instance being authorized in the nesting. However it's still a good idea to restrict the tasks separately. You can do so by going through the project association.

# in Ability
can :manage, Task, :project => { :user_id => user.id }

This means you will need to have a project tied to the tasks which you pass into here. For example, if you are checking if the user has permission to create a new task, do that by building it through the project.

<% if can? :create, @project.tasks.build %>

As of 1.4 it's also possible to check permission through an association like this.

<% if can? :read, @project => Task %>

This will use the above :project hash conditions and ensure @project meets those conditions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment