CanCanCommunity / cancancan

The authorization Gem for Ruby on Rails.
MIT License
5.55k stars 635 forks source link

Abilities with accepts_nested_attributes #774

Open hidr0 opened 2 years ago

hidr0 commented 2 years ago

Hello we are trying to write an ability for a model which accepts_nested_attributes.

And I was wondering if CanCanCan can do this. Our check is simple:

parent: { nested: { user_id: user.id}}}

But this does not work, because:

Are we supposed to use CanCanCan to check for abilities using accepts_nested_attributes or use another kind of check? We were thinking of deploying model validations, but it really made sense if you try to create a nested object for the wrong user to be a CanCanCan Ability check.

tuzz commented 5 months ago

In our case, we are permitting the nested attribute parameter in the Rails controller, e.g.

def foo_params
  params.require(:foo).permit(:something, { children: [:name] })
end

However, we then get an error:

ActiveRecord::AssociationTypeMismatch:
       Child(#158640) expected, got { name: "hello" } which is an instance of ActiveSupport::HashWithIndifferentAccess(#158660)

I think this is because CanCanCan's #assign_attributes method sets foo.children to an array of hashes rather than child objects. I figured out a workaround which is to add a virtual setter in the Foo model:

class Foo < ApplicationRecord
  has_many :children
  accepts_nested_attributes_for :children

  # Work around a CanCanCan issue with accepts_nested_attributes_for.
  # https://github.com/CanCanCommunity/cancancan/issues/774
  def children=(array)
    super(array.map { |o| o.is_a?(Hash) ? Child.new(o) : o })
  end
end

This then allows us to verify child attributes in our ability.rb file, e.g.

can :create, Foo, children: { visible_to: current_user.id }

The above rule would only allow users to call the Foos#create controller action if they include at least one child in the params that is visible_to the current user (this is just an example of one such rule you could enforce).

It would be great if CanCanCan could figure out where it needs to instantiate child objects, rather than just pass a hash so that this workaround isn't needed.

mattdrewitt commented 2 weeks ago

This is still an issue that I would love to see addressed. For now @tuzz' workaround worked for me, Thank you!