The Protector (abandoned)

The Protector (abandoned)

More than a year ago me and Peter Zotov released Heimdallr – gem allowing to control models’ security using shiny DSL. It was an extraction from a huge API backend project where it was used to consolidate access control, ease testing and DRY controllers.

Heimdallr was a proof-of-concept release and while I really like DSL part I never had a chance to seriously use it anywhere else. It appeared to be way too paranoid, difficult, slow and sometimes even buggy – it was so stubborn it didn’t feel Ruby way at all. But what’s even worse – it was incompatible with almost anything trying to work with ActiveRecord besides really basic interaction. Even with things like Kaminari.

Heimdallr as a proof-of-concept could afford having technical issues. And what’s good about having technical issues is that usually they can be solved. So I decided to make a Ruby-way clone of Heimdallr propagating the same idea (with similar DSL) but with really different implementation base and ideology.

And the first thing I fixed was the name: meet Protector.

Evolution

If you ever used Heimdallr before you might be interested in particular differences. So here are the key changes that make Protector totally another story:

  • Unlike Heimdallr that was trying to act as a duck-type compatible proxy, Protector works as an extension. It carefully modifies ORM from inside and theoretically should not affect any other gems.
  • Heimdallr was trying to protect a model like a black box. Protector is after persistence process. It doesn’t interrupt attributes modification but instead injects creation and updation security validations.
  • Protector trusts you a bit more. Heimdallr puts hands on everything even custom SQL queries. Protector doesn’t act that agressive – it only works at object level.

And the improvements that I always wanted to have at Heimdallr:

  • It has support for several adapters! There are only two of them for the moment – AR and Sequel. But two beats one. And I’m after Mongoid and DataMapper as well.
  • It supports custom actions. Besides basic readable?updateable?, etc actions it lets you to define your own.
  • It works well with both strategies of eager loading (yay yay).
  • It has perf tests! ^_^

The reason

We have CanCan and we have Strong Parameters. Why would we need Protector then? Well, there are several reasons why.

Comfort

Unlike CanCan and Strong Parameters, Protector is model-based. And therefore is easily and directly accessible from any part of your software where you use models. You can use it at controllers, queues, tests and even console in straightforward and handsome way. You can achieve close behavior with Ability, but you have to think about proper includes and additional entities which is not always good. Even if we reduce it by 3 LoC – okay, why not?

Centralized management

Another and probably the most important thing is that since we are working on model level – we can manage fields. CanCan operates with entities and you are supposed to manage fields on your own. Here comes Strong Parameters functionality but it’s heavily bound to controllers (at least if you use it in comfortable way). So you get your security scope smeared. With Protector you get all your security logic at one place. Easily-readable. Easily-testable. Easily-maintainable. At all levels.

Rails and most of Ruby web apps are Data-centric. We are used to the fact that it’s models’ duty to control data integrity. Could one consider security restrictions being a part of the integrity? I believe the answer is yes. And Protector gives you a way to implement that layer of integrity check seamlessly.

DRYing your code

Initial reason for Heimdallr to appear was not clarifying security restrictions though. It was an additional task. We required this kind of domain description to DRY the code. As I mentioned previously it was an extraction from a big Rails-based JSON API backend. Typically such backends mostly consist of basic CRUD implementations. And with Protector this code can be mostly inherited from base controller. It requires even less code comparing to things like Inherited Resources or (again) CanCan.