Archive for the ‘ActiveHelper’ Category

ActiveHelper + Rails is no pain in the ass!

Saturday, April 10th, 2010

Recently (about 6 minutes ago) I added support for Rails in ActiveHelper. The library as-it is still completely independent from any MVC framework and simply provides a generic helper API.

However, we got some Rails bindings now.

If you’re not yet familiar with ActiveHelper, no problem, it’s brandnew. Consider skimming the introducing post first. Or directly jump to the github repository.

Writing the helper

Let’s assume you want to create a new helper to replace the #link_to method. Why not name it LinkingHelper?

class LinkingHelper < ActiveHelper::Base
  provides :link_for
 
  def link_for(title, url)
    '<a href="' + url + '">' + title + '</a>'
  end
end

You’d put this into app/active_helpers/linking_helper.rb.

Preparing the controller

Bla bla bla, it’s obvious how to use ActiveHelpers in controllers.

class BlogController < ActionController::Base
  active_helper LinkingHelper
 
  def show
    @post = Post.find params[:id]
  end
end

Helping the view

As soon as the controller did include the LinkingHelper its provided methods are around in the view.

1
2
3
4
5
6
Bookmarkable link: <%= link_for @post.title, @post.url %>
 
<h1><%= @post.title %></h1>
<p>
  <%= @post.body %>
</p>

You used your #link_for to generate a link in line 1.

So… where’s the benefit?

Being a good programmer you instantly decide to extract the markup logic from the LinkingHelper to some separate MarkupHelper.

The latter would provide a method #tag for generating markup.

Your former helper now looks like this.

1
2
3
4
5
6
7
class LinkingHelper < MarkupHelper
  provides :link_for
 
  def link_for(title, url)
    tag(:a, title, :href => url)
  end
end

Noticed the inheritance in line 1? Wow- that’s OOP features in helpers. Nice.

Reusing old-school helpers

Now what if you still want to use #url_for to setup the link address? It’s hidden somewhere in Rails gloomy mixin magic.

You know it’s floating around in your view. So go and use it.

1
2
3
4
5
6
7
8
9
class LinkingHelper < MarkupHelper
  provides :link_for
  needs :url_for
 
  def link_for(title, url)
    url = url_for(url)
    tag(:a, title, :href => url)
  end
end

Step-by-step, dude. You simply use #url_for in line 6. Your helper knows about it since you defined the dependency (line 3).

That’s gonna work- but… why?

Well, any ActiveHelper simply delegates dependencies back to where it was imported into. In our case, any call to #url_for is routed to the ActionView instance which has to care about that.

Delegation for a better world

The new keywords in the Helper World are delegation and interfaces.

Besides that, we got back OOP and inheritance to helpers.

Lemme end this boring post by quoting Manuel who’s just diving into ActiveHelpers.

It’s funny- ActiveHelpers is nothing more than 80 lines of code. Anyway, it’s forcing you to write clean code… in a gentle way!

Isn’t that great?

Making a Rails Helper predictable

Thursday, April 1st, 2010

Rails helpers are pretty messy. Everyone who’s ever tried to simply include, say, the UrlHelper into an object did instantly crash with some NoMethodError either complaining about

  • a missing method that should be included in the helper already, but isn’t
  • an undefined method for nil:NilClass
target = Object.new
target.extend ActionView::Helpers::UrlHelper
target.url_for('/fix/me')
 
#=> url_helper.rb:94:in `url_for': undefined method `escape_once' for #<Object:0xb7496300> (NoMethodError)

That’s due to dependencies between different helpers and the instances they’re mixed into.

Wrapping the UrlHelper

I’m gonna explain how ActiveHelper can help solving this problem by wrapping the UrlHelper step-by-step.

This will make the UrlHelper a predictable, well-behaving class. Helpers should be good kids, nothin’ more.

Here’s a first version for the new helper.

module Rails
  class UrlHelper < ActiveHelper::Base
    include ActionView::Helpers::UrlHelper
 
    provides :url_for, :link_to, :button_to # add more if you need.
  end
end

We derive the new helper from ActiveHelper::Base, include the original module and define our interface with provides.

A rails scenario setup

In Rails, this is what roughly happens when a view is rendered.

controller = ActionController::Base.new
view       = ActionView::Base.new([], {}, controller)
 
view.extend ActiveHelper
view.use Rails::UrlHelper

Usually the use call would happen in the controller’s action processing, or in #render.

view.url_for('fix/me')
# => url_helper.rb:85:in `send': undefined method `url_for' for nil:NilClass (NoMethodError)

Ok, send is accessing nil. Let’s inspect the original helper.

  def url_for(options = {})
    ...
    @controller.send(:url_for, options)

The rails helpers blindly accesses an instance var @controller, it simply assumes the instance it is mixed into does provide it.
Even worse, it uses send to invoke a method on the controller. That’s one of rails major drawbacks and must be fixed.

Providing the @controller

Unfortunately, we won’t touch the original UrlHelper and open pandora’s box.

Remember, we got a helper instance which mixed-in the original UrlHelper module. That means we’d just have to provide the @controller instance variable in our helper instance.

module Rails
  class UrlHelper < ActiveHelper::Base
    include ActionView::Helpers::UrlHelper
 
    provides :url_for, :link_to, :button_to # add more if you need.
 
    def initialize(*args)
      super(*args)
      @controller = parent.controller
    end

We setup an ivar @controller by accessing parent, that’s the object which is using us, so it’s the ActionView and that itself does provide a public accessor to its controller.

Fixing another helper dependency

Not nice, but anyway, cleaner than things were before.

view.url_for('fix/me')
url_helper.rb:94:in `url_for': undefined method `escape_once' for #<Rails::UrlHelper:0xb747b794> (NoMethodError)

Oh. #escape_once is needed in rails’ original helper, too. I will spare the details about the code now, the method is defined in TagHelper. However it is not mixed in UrlHelper. Shitty helpers.

That’s another broken dependency we’ll have to fix.

We now have three possibilities

  • we could wrap TagHelper in an ActiveHelper as well, and derive UrlHelper
  • wrapping and use‘ing it inside UrlHelper would be the second solution
  • or simply include the original module, hoping all will go well

For now I’ll choose the third, anyway, that’s far from good architecture.

module Rails
  class UrlHelper < ActiveHelper::Base
    include ActionView::Helpers::UrlHelper
    include ActionView::Helpers::TagHelper
 
    provides :url_for, :link_to, :button_to # add more if you need.
 
    def initialize(*args)
      super(*args)
      @controller = parent.controller
    end

Let’s see if it works.

view.url_for('fix/me') #=> "fix/me"
view.button_for('fix/me')
#=> url_helper.rb:298:in `button_to': undefined method `protect_against_forgery?' for #<Rails::UrlHelper:0xb74a7eac> (NoMethodError)

Now what’s that again? Looks as if #button_for needs protect_against_forgery?.

Delegating dependency to the ActionView

Let’s express that dependency.

module Rails
  class UrlHelper < ActiveHelper::Base
    include ActionView::Helpers::UrlHelper
    include ActionView::Helpers::TagHelper
 
    provides :url_for, :link_to, :button_to # add more if you need.
 
    needs :protect_against_forgery?
    ...

Having that defined, calls to protect_against_forgery? will be delegated back to the view. That should work.

view.button_for("Click me!", 'fix/me')
#=> <form method="post" action="fix/me" class="button-to"><div><input type="submit" value="Click me!" /></div></form>

Yeah!

Where to go from here?

Now that you know how easy it is to clean up the Rails helpers (or Merb, or …!!!), go and do it. I’d love to see ActiveHelper being used to replace nasty modules and bringing back the power of OOP to your views.

ActiveHelper 0.1.0 released

Tuesday, March 30th, 2010

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 FormHelper

The 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!