ForbiddenAttributesError for polymorphic model in

2019-08-07 11:49发布

问题:

Rails 4 ships with strong_parameters, which is a great addition - but I've run into a problem with it. I have a polymorphic model Comment and I cannot for the life of me get the controller to accept the parameters it needs. Here is my code (shortened for clarity):

Routes:

resources :articles do
  resources :comments
end

Models:

class Article < ActiveRecord::Base
  has_many :comments, :as => :commentable
end

class Comment < ActiveRecord::Base
  belongs_to :commentable, :polymorphic => true
end

Controller:

class CommentsController < ApplicationController
  before_action :get_commentable

  def create
    @comment = @commentable.comments.new(comment_params)
    if @comment.save
      redirect_to @commentable, :notice => "Thank you!"
    else
      render :new
    end
  end

private

  def get_commentable
    resource, id = request.path.split("/")[1,2]
    @commentable = resource.singularize.classify.constantize.find(id)
    redirect_to :home unless defined?(@commentable)
  end

  def comment_params
    params.require(:comment).permit(:title, :message)
  end
end

Posted params (from form on articles#show):

{"authenticity_token"=>"v70nN8aFpofNw9vbVjhpsm9SwLOwKlOpNOEOTozUwCk=",
"comment"=>{"title"=>"Test","message"=>"Testing"},
"article_id"=>"1"}

Looks to me like it should work, yet whatever I try I get ActiveModel::ForbiddenAttributesError in CommentsController#create - even when I try

  def comment_params
    params.permit! 
  end

in the controller. I have no such problems with my other (non-polymorphic) models, which is why I suspect it has something to do with the polymorphism. Any ideas?

回答1:

As the lack of answers seemed to indicate I was barking up the wrong tree here. The issue lies not with strong_parameters but with the CanCan gem that I use for doing role and action based authorization. Apparently it's got to do with how CanCan assigns params to objects (CanCan takes over the default ActionController methods) - see the details in this bug report, specifically the reply from "rewritten". In short, putting this in my application controller solves the problem:

before_filter do
  resource = controller_name.singularize.to_sym
  method = "#{resource}_params"
  params[resource] &&= send(method) if respond_to?(method, true)
end

Update:

As pointed out by @scaryguy, if the method above is called from a controller that does not have an associated model it will fall over. The solution is simply to name the method and call it as a before_filter, while explicitly excluding it in those controllers which have no models (and hence would not benefit from CanCan's automatic ability assignment anyway). I reckon something like this:

before_filter :can_can_can

def can_can_can
  resource = controller_name.singularize.to_sym
  method = "#{resource}_params"
  params[resource] &&= send(method) if respond_to?(method, true)
end

And then in the model-less controller:

skip_before_filter :can_can_can