One of the points I try to make in Objects on Rails is that you don’t need to let other libraries or frameworks dictate the public face of your objects. Just because you use ActiveRecord, for instance, to persist the state of an object doesn’t mean you have to commit that object to honoring the effectively infinite ActiveRecord model API. You can use the facilities of ActiveRecord internally to that object, but still project a tidy, well-understood, easily-substituted interface to the rest of the system.
And of course you can do this entirely by convention, by simply being careful to interact only with the methods you’ve explicitly exposed. But it’s easy to forget these conventions in the heat of implementation, gradually coupling the rest of the program to more and more framework-provided methods until one day you discover that your object’s interface surface area has become quite large indeed.
As an exercise in mindfully limiting the interface exposed by objects and classes, in Objects on Rails I demonstrated a tool called “
FigLeaf gives you something akin to the “private inheritance” found in some other OO languages. While it doesn’t actually hide the fact that a class is derived from a framework class, it enables you to selectively privatize some, most, or all methods derived from ancestor classes and modules.
Here’s an example from the readme
class Post < ActiveRecord::Base include FigLeaf hide ActiveRecord::Base, ancestors: true, except: [Object, :init_with, :new_record?, :errors, :valid?, :save] hide_singletons ActiveRecord::Calculations, ActiveRecord::FinderMethods, ActiveRecord::Relation # ...
And here’s what happens when we try to send class or instance-level messages which have been hidden by
ruby-1.9.2-p0 > Post.find(1) NoMethodError: private method `find' called for #<Class:0xa1a4a50> ruby-1.9.2-p0 > Post.new.destroy NoMethodError: Attempt to call private method
Today I’m happy to announce that thanks to the dilligent efforts of Sammy Larbi,
FigLeaf is now available as a RubyGem. You can install it with:
gem install fig_leaf
The project is also available on GitHub.
It’s important to note that
FigLeaf is not intended as a hammer to keep your coworkers or your library clients in line. It’s not as if that would work, anyway; the strictures that it adds are easy enough to circumvent. Instead, it’s intended role is more along the lines of the “rumble strips” along highways which give you a jolt when you veer off into the shoulder. It provides a sharp reminder when you’ve unthinkingly introduced a new bit of coupling to an interface you are trying to keep isolated from the rest of the codebase. Then, you can consciously make the decision whether to make that method public, or find a different way of going about what you were doing.
Got questions or suggestions about
FigLeaf or about OO practices in general? Please join us on the Objects on Rails discussion list!
I love this idea Avdi. Awesome idea. At last, my bare classes will no longer support #find_by_email!
Good idea but if you protect methods like create! or destroy you will start to loose things like factory girl or association dependent => destroy.
I’m not sure if we left methods like destroy or create! unprotected people will be responsible enough to follow the convention to not use theses methods on controllers.
IMHO the idea is good but the ecosystem (gems and even Rails) isn’t prepared for this 🙁
I think that’s a fair assessment. Consider this my way of giving the ecosystem a nudge 😉
Daniel has a valid point. Maybe this means we have to start re-thinking the way we currently use Rails and other tools, like FactoryGirl. I don’t see it as a bad thing, though.
I love the idea behind figleaf. I’ve recently started applying this concept on my projects and it’s helped me a lot, but I’m not sure if it is meant be used in everywhere. I still think that for the parts of the app that don’t involve that much complexity, it is still fine to have AR::Base methods exposed to controllers.
For more complex parts, though, I think it might be a fair trade-off to give up on stuff that Rails gives us for free (like :dependent => :destroy).
As for the ActiveRecord ‘bang-methods!’, I don’t think there is ever a case where calling them from controllers would be acceptable, so I would not worry about protecting them and not being able to use FactoryGirl.
I’m very excited to see where the Rails community is heading with all of this.
I did something similar some time ago (https://twitter.com/#!/elia/status/200245258066460672 ) without realizing that it was coming from your book…