ActiveHelper 0.1.0 released
Finally – helpers with proper encapsulation, delegation, interfaces and inheritance!
When I was prototyping a new JavaScript generator library using Rails’ helpers I came to a conclusion: Helpers suck.
Typically helpers in ruby frameworks are implemented as simple modules. However, despite the word simple, they suffer from two problems:
- Dependency helper modules assume there are other helpers mixed into the target and blindly use foreign methods
- Dependency helper modules rely on instance variables from the target they were mixed into.
ActiveHelper - An example
ActiveHelper puts helpers back to where they belong: classes.
Here’s how a typical MVC helper would look like with ActiveHelper. And, hey, if this is too brief, go and check out the README.
class View include ActiveHelper def https_request?; false; end end
We have an exemplary View class that knows if an HTTP request is secure or not.
use
The include pendant for helpers and target instances is use.
> view.use FormHelperThe instance can use a helper to gain he-man’s form building capabilities.
class FormHelper < TagHelper provides :form_tag uses UrlHelper def form_tag(destination) destination = url_for(destination) # in UrlHelper. tag(:form, "action=#{destination}") # in TagHelper. end end
provides
The FormHelper exposes its interface as it provides a method #form_tag that itself uses two foreign methods #tag and #url_for.
The #tag method is simply inherited from the TagHelper, which provides :tag again. See the inheritance?
The latter, #url_for, is implemented in UrlHelper, so the form lib references that dependency when it uses the required helper.
needs
Let’s look into that url thing helper.
class UrlHelper < ActiveHelper::Base provides :url_for needs :https_request? def url_for(url) protocol = https_request? ? 'https' : 'http' "#{protocol}://#{url}" end end
You already got it, don’t you?
The UrlHelper relies on a mysterious method #https_request?. There is no magical inclusion somewhere. As soon as it needs a method an accessor will be created delegated all calls to the View instance, which was the target.
Handling dependencies
So it’s the importing instance that has to handle methods declared with needs.
If the view wouldn’t expose the required method, it would be like
> view.form_tag('apotomo.de') # => 11:in `url_for': undefined method `https_request?' for #<View:0xb749d4fc> (NoMethodError)
which is a clear, debugable issue.
Come on!
The approach found with ActiveHelper is completely generic, not bound to a specific framework like Rails, Merb or Sinatra. It may (and should!) be used in order to make helpers cleaner, use OOP features and make them easier to debug.
What’s your impression, what’s your problems and experiences with helpers? Don’t be shy, tell us!
Curious about the source? Check out my repository at the github, trans.
Cheers!
March 30th, 2010 at 10:56 pm
Very nice. I’ve wondered about something like this myself. Glad to see someone doing it.
Couple of thoughts: 1) Can public methods automatically be ‘provides’? 2) naming the library ActiveHelpers pretty much shouts “RAILS”, so despite being generally useful I am not sure you’ll get a lot of traction outside Rails. Just a thought.
Lastly, a link to the source code/ repository would be great.
March 31st, 2010 at 6:16 pm
yo trans,
thanks for your post!
1) what about some
provides public_instance_methods
or
provides :public_methods => true
or so? i enjoy being explicit.
2) yeah i know, you’re definitly right. i wanted to relate it to ActiveSupport, which is pretty useful and completely rails independent. you using rails?
3) link to the repository is at the top, i’ll add another one in a second.
thanks again,
nick