Using CanCanCan With StimulusReflex In Your Rails App
Tags: rails, stimulusreflex, cancancan
Published: 2021-03-23 04:52:49

If you are using [CanCanCan](https://github.com/CanCanCommunity/cancancan) for authorization and also want to use the magic of [StimulusReflex](https://docs.stimulusreflex.com/) for reactive page updates, these strategies will help you check user abilities in your reflexes.

![stimulus_reflex_cancancan](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u89pv9s0pp1aowktl9pe.png) 

[CanCanCan](https://github.com/CanCanCommunity/cancancan) is a powerful authorization library that allows you to _authorize!_ the _current_user_ for an action, as well as restrict records only _accessible_by_ their _current_ability_.

```ruby
def index
 authorize! :index, Classroom
 @classroom = Classroom.accessible_by(current_ability)
end
```

Once you start using [StimulusReflex](https://docs.stimulusreflex.com/), you’ll soon need to utilize the _accessible_by_ in your reflexes to only obtain records permitted for the _current_user_ as well. The following are strategies how to do this for both [selector morphs](https://docs.stimulusreflex.com/rtfm/morph-modes#page-morphs) and [page morphs](https://docs.stimulusreflex.com/rtfm/morph-modes#page-morphs).

---

### Selector Morphs With CanCanCan

For [selector morphs](https://docs.stimulusreflex.com/rtfm/morph-modes#selector-morphs), you have two options for using CanCanCan’s _accessible_by_ in your reflex.

#### Option 1: create new ability for user
First delegate the _current_user_ to your _connection_, then create a new ability passing that into the _accessible_by_ call.

```ruby
class ClassroomsReflex < ApplicationReflex
 delegate :current_user, to: :connection
 def change_school
  if element.value.present?
   user_ability = Ability.new(current_user)
   school = School.find(element.value)
   classrooms = school.classrooms.accessible_by(user_ability).order(:name)
  else
   school = nil
   classrooms = []
  end
  morph “#classrooms”, render(partial: “/classrooms/classrooms”, locals: { school: school, classrooms: classrooms })
 end
end
```

#### Option 2: delegate current_ability to controller
Another technique is to delegate the _current_ability_ from the _controller_, passing that into the _accessible_by_ call.

```ruby
class ClassroomsReflex < ApplicationReflex
 delegate :current_ability, to: :controller
 
 def change_school
  if element.value.present?
   school = School.find(element.value)
   classrooms = school.classrooms.accessible_by(current_ability).order(:name)
  else
   school = nil
   classrooms = []
  end
  morph “#classrooms”, render(partial: “/classrooms/classrooms”, locals: { school: school, classrooms: classrooms })
 end
end
```
Note, one of the reasons selector morphs are faster than page morphs is because they don’t need to instantiate the controller. This means that the above will increase the response time _adding 10–25ms hit on every reflex_. So **Option 1 is recommended when possible**, although there might be some scenarios where this is still useful.

--- 

### Page Morphs With CanCanCan
For [page morphs](https://docs.stimulusreflex.com/rtfm/morph-modes#page-morphs), you **can not** delegate _current_ability_ to the controller, due to the fact that both StimulusReflex and CanCanCan instantiate the controller internally. This causes an issue with the instance variables set in your reflex always being nil in the controller afterwards. So you have two options for page morphs instead.

#### Option 1: create new ability for user
Similar to the selector morph, you can delegate to _current_user_, then create a new ability and pass it into _accessible_by_.

```ruby
class ClassroomsReflex < ApplicationReflex
 delegate :current_user, to: :connection
 
 def change_school
  if element.value.present?
   user_ability = Ability.new(current_user)
   @school = School.find(element.value)
   @classrooms = @school.classrooms.accessible_by(user_ability).order(:name)
  else
   @school = nil
   @classrooms = []
  end
 end
end
```

#### Option 2: move accessible_by calls to controller
Another option is to move the _accessible_by_ calls out of the reflex and into the controller. This is _not a very StimulusReflex-y way_, although there are some scenarios where this could suffice so still worth noting.

```ruby
class ClassroomsReflex < ApplicationReflex
 def change_school
  if element.value.present?
   @school = School.find(element.value)
  else
   @school = nil
  end
 end
end
```

```ruby
class ClassroomsController < ApplicationController
 def index
  authorize! :index, School
  authorize! :index, Classroom
  @school ||= School.find(params[:school_id)
  @schools ||= School.accessible_by(current_ability).order(:name)
  @classrooms ||= @school.present? ? @school.classrooms.accessible_by(current_ability).order(:name) : []
 end
end
```

---

### Model Based CanCanCan Abilities
If you’ve transitioned to using [separate abilities per model](https://medium.com/@coorasse/cancancan-that-scales-d4e526fced3d), then the good news is _Option 1 will work even better for you_!

```ruby
class ClassroomsReflex < ApplicationReflex
 delegate :current_user, to: :connection
 
 def change_school
  if element.value.present?
   classroom_ability = ClassroomAbility.new(current_user)
   @school = School.find(element.value)
   @classrooms = @school.classrooms.accessible_by(classroom_ability).order(:name)
  else
   @school = nil
   @classrooms = []
  end
 end
end
```

In this case, rather than creating abilities for all models you are only creating abilities for the Classroom model. This can especially help if you are using _ids queries in your abilities, since those other abilities won’t be executed. If you are interested in separate abilities per model, I’d recommend reading [Lazy Load CanCanCan Abilities In Rails](https://dev.to/dalezak/lazy-load-cancancan-abilities-in-rails-1h10).

---

For more information regarding using CanCanCan with StimulusReflex, visit [authentication section on the official documentation](https://docs.stimulusreflex.com/rtfm/authentication#cancancan).

Big thanks to [@theleastbad](http://twitter.com/theleastbad), [@RogersKonnor](http://twitter.com/RogersKonnor) and [@marcoroth_](http://twitter.com/marcoroth_) for helping debug the issues I was having using CanCanCan with StimulusReflex, which was the source of these above strategies. 🙏