17

I have a standard RESTful controller that uses strong parameters.

class UsersController < ApplicationController
  respond_to :html, :js

  def index
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end

  def edit
    @user = User.find(params[:id])
  end

  def create
    @user = User.new(safe_params)

    if @user.save
      redirect_to @user, notice: t('users.controller.create.success')
    else
      render :new
    end
  end

  def update
    @user = User.find(params[:id])

    if @user.update_attributes(safe_params)
      redirect_to @user, notice: t('users.controller.update.success')
    else
      render :edit
    end
  end

  def destroy
    @user = User.find(params[:id])

    if current_user != @user
      @user.destroy
    else
      flash[:error] = t('users.controller.destroy.prevent_self_destroy')
    end
    redirect_to users_url
  end

  private

  def safe_params
    safe_attributes =
      [
        :first_name,
        :last_name,
        :email,
        :password,
        :password_confirmation,
      ]
    if current_user.is?(:admin)
      safe_attributes += [:role_ids]
    end
    params.require(:user).permit(*safe_attributes)
  end
end

In my config/initializers I have the file strong_parameters.rb

ActiveRecord::Base.send(:include,  ActiveModel::ForbiddenAttributesProtection)

When I add a simple call to CanCan's load_and_authorize_resource I get

1) UsersController POST create with invalid params re-renders the 'new' template
 Failure/Error: post :create, user: @attr
 ActiveModel::ForbiddenAttributes:
   ActiveModel::ForbiddenAttributes
 # ./spec/controllers/users_controller_spec.rb:128:in `block (4 levels) in <top (required)>'

Where @attr in the test is defined as

  before(:each) do
    @attr =
      {
        first_name: "John",
        last_name: "Doe",
        email: "user@example.com",
        password: "foobar",
        password_confirmation: "foobar"
      }
  end

In the tests I have it all setup properly to login the user and give them the necessary roles for being an administrator so I know it's not that. I don't know why this is causing ForbiddenAttributes to trigger. I'm sure it's something simple I've overlooked. Has anyone else encountered this problem and found a solution to it?

Tiggers
  • 179
  • 1
  • 3

2 Answers2

20

I believe this is because CanCan will use its own getter method for the requested resource if you don't pre-load it with a before_filter. So you could add this to the controller and it should work:

class UsersController < ApplicationController
  before_filter :new_user, :only => [:new, :create]

  load_and_authorize_resource

  def new_user
    @user = User.new(safe_params)
  end
end

(And then do the same for the edit/update actions.)

Peter Duijnstee
  • 3,759
  • 2
  • 20
  • 30
  • I believe I am having the same issue. Would you mind clarifying your solution further? – Eric H Feb 08 '13 at 19:48
  • 6
    It's been a while but I'll give it a shot ;) What part is giving you trouble? Basically if you call cancan's `load_and_authorize_resource` it will try to load the most "logical" resource given the controller's name, which precedes the operation of the strong parameters gem. In this case it will try to build the User, `@user = User.new(params[:user])` But strong_parameters won't allow mass assignment this way. If you use a `before_filter` to set the @user instance variable, CanCan will just use that instead. If your `before_filter` complies with strong_parameters it shouldn't raise an error. – Peter Duijnstee Feb 10 '13 at 19:54
  • Ahhh Much appreciated! I wasn't understanding how CanCan was "...[using] its own getter method..." but that makes a lot of sense now. Thanks again! – Eric H Feb 12 '13 at 19:43
  • 1
    Glad I could help! Good luck with your project! – Peter Duijnstee Feb 13 '13 at 20:49
  • 1
    @PeterDuijnstee: I believe you want to have :only => [:create], instead of :only => [:new, :create]. :new won't have any parameters at all. So, it will choke in safe_params on params.require(:user).permit(*safe_attributes) – Victor Ronin Aug 27 '13 at 03:05
  • Oh of course, it requires params[:user]. Don't have a whole lot of experience with Strong Parameters yet so I missed that one, good catch :) – Peter Duijnstee Aug 29 '13 at 16:43
  • @PeterDuijnstee , thank you for your answer. But thank you more for your warning at your comment! Instance name makes difference in this issue! My controller was `Admin::Ads` but my model was `Ad` , whenever my instance variable was `@admin_ad` it didn't work. But when I changed it to `@ad` it worked! – scaryguy Sep 20 '13 at 21:18
  • Should check out the [`cancancommunity/cancancan`](https://github.com/cancancommunity/cancancan) gem. It is the new official home of CanCan and includes support for strong_parameters out of the box. – jxpx777 Mar 24 '14 at 15:34
7
before_filter do
  params[:user] = safe_params
end
load_and_authorize_resource
kuboon
  • 9,557
  • 3
  • 42
  • 32
  • 1
    Major emphasis here on the **before_filter** coming before **load_and_authorize_resource** – Gerard Jan 24 '14 at 12:22