Skip to content

Instantly share code, notes, and snippets.

@mudge
Created March 9, 2012 17:31
Show Gist options
  • Save mudge/2007669 to your computer and use it in GitHub Desktop.
Save mudge/2007669 to your computer and use it in GitHub Desktop.
Are filters abused in Rails?
class MyController < ApplicationController
def show
@model = load_model
end
def edit
@model = load_model
end
private
def load_model
Model.find(params[:id])
end
end
class MyController < ApplicationController
before_filter :load_model, :only => [:show, :edit, :update, :destroy]
def show
end
def edit
end
private
def load_model
@model = Model.find(params[:id])
end
end
class MyController < ApplicationController
helper_method :model
def show
# Use +model+ freely
end
def edit
# Use +model+ freely
end
private
def model
@model ||= Model.find(params[:id])
end
end
class MyController < ApplicationController
def show
load_model
end
def edit
load_model
end
private
def load_model
@model = Model.find(params[:id])
end
end
@bravoecho
Copy link

It's also interesting to see how Ramaze approaches the problem, namely relying on calls to super http://ramaze.net/documentation/file.controllers.html
I make no mystery of my predilection for Ramaze over Sinatra.

@mudge
Copy link
Author

mudge commented Mar 12, 2012

@ohthatjames I agree that a test should fail in this case but I'm not sure about using the presence of a safety net as your sole way of informing developers of the design, viz. someone could ostensibly create a new action and you'd be entirely reliant on your test coverage to teach them how the system works. Can we not hope for greater clarity in the code itself?

I'm reminded of another comment of yours regarding avoiding deeply nested contexts in tests for this same reason: the lack of transparency about what set up is actually occurring.

@mudge
Copy link
Author

mudge commented Mar 22, 2012

Following the Ruby Rogues discussion about Objects in Rails, I've added another approach: using helper_method to avoid exposing instance variables altogether. This has the added benefit of working across views.

@bravoecho
Copy link

In the common quest of a more better approach, the helper methods are a quite a interesting approach.

The helper methods though, as intended by Rails, break the encapsulation.

They also represent a problem for the single responsibility, because their presence is not always required by the controller instance, but dictated by the needs of the template.

As "cross object" instance variables which belong to 2 different objects at the same time, they are somehow pseudo-global entities instead of being injected.

When it comes to testing, they still need to be stubbed out as part of the context.

Also, if they assign instance variables, the latter will be again available in the view, representing a further temptation that only the developer's discipline can prevent :-)

Unfortunately all this is due to the very nature of how Rails works.

Furthermore, it's also related to the legacy of the original ERB implementation from the standard library. An ERB object only accepts a Binding object, and raises an error if the argument is of a different type. That probably encouraged this historical instance variable based approach in Rails.

Erubis (the default ERB engine as of Rails 3.0) does better in this respect, because you can also pass a Hash to the result.

template = Erubis::Eruby.new("<%= omg %>, this is a <%= message %>!!!")
object_to_be_rendered = {omg: "ZOMG!", message: "different kind of template"}
template.result(object_to_be_rendered)
# => "ZOMG!, this is a different kind of template!!!"

This allows to decide to rely on only one simple context object for the view, and this encourages to build that object as dumb as possible, probably a presenter on top of a database-backed model in the Rails common scenario.

This approach seems closer to the one we find in the Javascript templating systems. For me it has the advantage that I can easily stub it out not as pseudo-global context (difficult) but as a parameter to pass to the subject under test (easy).

It's also interesting that in we can get something similar in Rails making explicit use of the :locals option

render :template => "my/template", :locals => {omg: "ZOMG!", message: "different kind of template"}

@parhs
Copy link

parhs commented Sep 1, 2017

The issue is how would you deal with global data. Like menu/cart etc if not using before filter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment