Rails and CanCan: Authorization for Specific Fields
A recent project we worked on at Highgroove involved scheduling events on a calendar. These events had a lifecycle of “statuses,” such as “pending approval” and “approved.”
All users were able to set the “status” to values such as “pending approval,” but only certain privileged users could move them to states such as “approved.”
We were already using CanCan for authorization, but there was no built-in facility for authorizing field-level changes. There were workarounds in certain cases, such as using custom actions, but none of these fit with our specific use case.
Read on for how we modeled the problem and used a Plain Old Ruby Object (PORO) to keep things clean.
We wanted to route edits to an event, including status changes, through the
EventsController#update action because users could edit both the status and other attributes from the same
We really wanted to avoid code that looked like below:
It might make sense for the controller to enforce the authorization, but it did not make sense to us for the code to perform the authorization to appear in-line within the controller.
At some point we realized that we could create a Plain Old Ruby Object (PORO) to model the “event status change” and then use the facilities that CanCan already provided to authorize the change.
First, we created an
EventStatusChange class in
lib/event_status_change.rb; note that this class has no Rails dependencies.
(Astute readers may have noticed that
EventStatusChange could be implemented as a one-liner using
Next, we added code to the
Ability class where CanCan authorization code normally lives:
Finally, we did have to keep some code in the controller, but it looks much cleaner:
We are not completely sure this is the best way to approach the problem, but it seemed pretty reasonable to us at the time. While not shown here, many of the pieces can be tested in isolation with external objects as stubs; we also found that ability to be a big win.
Have you ever tried to authorize changes to specific fields in Rails? If so, how did you approach it?