Skip to content

danabr75/cancancan_resource_controller

Repository files navigation

Use CanCan's :permitted_attributes method to automatically determine controller permitted parameters.

CanCan provides a way to set permitted attributes, so why build something else?

Install (add to Gemfile)

gem 'cancancan_resource_controller', '~> 1'

Usage:

(Optional) create init file: config/initializers/cancancan_resource_controller.rb and populate it with the following:

require "cancancan_resource_controller"
# default values shown
CanCanCan::NestedAssignmentAndAuthorization.configure do |config|
  # Allows for stopping unauthorized actions without raising errors
  # - Will let root object (and valid, other nested objects) save, even if an invalid nested object exists, if true
  config.silence_raised_errors = false
  # Auto-determine what action to auth on nested associations (:create, :update, :destroy)
  # - :create if is a new record
  # - :update if pre-existing record
  # - :destroy if :_destroy parameter is present
  # - will use the action of the root object if set to false
  config.use_smart_nested_authorizations = true
  # Set to `true` if you're nesting parameter data under the resource_key
  # - i.e. params => {user: {email: 'test', name: 'fun'}}
  # Set to `false` if resource parameter data is direct in in params.
  # - i.e. params => {email: 'test', name: 'fun'}
  config.use_resource_key_in_params = false
end

Mainly built out the :create, :update, and :destroy methods. We also have the :index and :show methods, but I would recommend overriding those.

class UsersController < ActionController::Base
  include CanCanCan::AbstractResourceController

  # index, update, create, destroy methods are now provided and backed by CanCan's Ability settings.

  # All actions use the following to pull the object from the database:
  # `@resource ||= @resource_class.find(params[:id])`
  # if you need to locate the object yourself, or apply additional logic, you can use a `before_action` hook.

  # almost all methods will render a @resource object
  # - :index renders a @resources object
  # i.e.:
  # respond_to do |format|
  #   format.html # Renders the default
  #   format.json { render json: @resource }
  # end
end

Handing associations in your ability.rb

class User < ApplicationRecord
  has_many :vehicles, inverse_of: :user
  accepts_nested_attributes_for :vehicles, allow_destroy: true
end
class Vehicle < ApplicationRecord
  has_many :vehicles_parts
  has_many :parts, through: :vehicles_parts
  belongs_to :user
end
class Parts < ApplicationRecord
  has_many :vehicles_parts
  has_many :vehicles, through: :vehicles_parts
end

class Ability
  include CanCan::Ability

  def initialize(user = nil)
    # Nested Attribs
    # - `association_name + '_attributes'`
    # - :vehicles_attributes will be used to permit it's usage in your controller
    can :update, User, [:first_name, :last_name, :vehicles_attributes]

    # Add/Remove IDs from has_many assocation
    # - part_ids
    can :update, Vehicle, [:make, :model, :part_ids]
  end
end

Implementing Pagination on :index

class UsersController < ActionController::Base
  include CanCanCan::AbstractResourceController

  protected

  # Use a gem or implement manually, like so
  def respond_with_resources
    page     = params[:page]&.to_i || 1
    per_page = params[:per_page]&.to_i || 10
    offset   = per_page * (page - 1)
    @resources = @resources.limit(per_page).offset(offset)
    super
  end
end

Implementing search on :index

class UsersController < ActionController::Base
  include CanCanCan::AbstractResourceController

  protected

  # - can be used for searching or whatever
  def index_resource_query resource_query
    return resource_query
  end
end

About

A Rails Controller Module that uses CanCan's permitted attribs instead of typical parameter allowlisting.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published