Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abilities with accepts_nested_attributes #774

Open
hidr0 opened this issue Mar 18, 2022 · 1 comment
Open

Abilities with accepts_nested_attributes #774

hidr0 opened this issue Mar 18, 2022 · 1 comment

Comments

@hidr0
Copy link

hidr0 commented Mar 18, 2022

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:

  • on new CanCanCan builds the parent object only and parent.nested == nil (so we always get Unauthorised)
  • on update CanCanCan loads the object from the database which means that we are not comparing the ability on the nested object. ( so we always get Authorised)

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.

@hidr0 hidr0 changed the title Merge abilities with accepts_nested_attributes Abilities with accepts_nested_attributes Mar 18, 2022
@tuzz
Copy link

tuzz commented Feb 23, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants