Most of the projects I've worked with grow in similar ways. Code often ends up where it is easiest to place it. For code in our Rails "Views", under
app/views, that's either in the controller (with lots of instance variables assigned), in ApplicationHelper (shared with every view), or in the view templates themselves.
I'm going to try to address each of these scenarios, and show you how to refactor them into much simpler code, using SimplestView. But first, a little background.
The Rails Way
In Rails, our ERB templates go under
app/views. There is a logical structure to how they are organized. This is The Rails Way. By convention, the views for a controller
SalesController goes inside the folder
app/views/sales. Further, each action gets a file inside the sales folder, using the name of the action as the filename.
The tricky bit is these Rails "Views" aren't really view objects. The ERB templates we build, are rendered inside the context of an instance of an anonymous class, which inherits it's rendering abilities from ActionView::Base.
But why an anonymous class? Why does Rails hide this away from us completely?
Now, if we set up our Rails application to use SimplestView, we get clear, conventional access to these previously hidden view classes. We give them a name. By convention, that name will match the action, and be placed inside a folder matching the controller name.
#show we would have classes named
Sales::ShowView (which inherit from ActionView::Base) inside of a folder in
app/views/sales. Note: SimplestView has you move your ERB into a more aptly named
app/templates folder to keep things cleanly separated, and to give you a nice Rails-y place to put your new views.
Now that we have that out of the way, let's get into some refactorings!
How often have you had to dig through an
ApplicationHelper that simply contains every single helper in the whole project? I've seen projects with an
ApplicationHelper thousands of lines long.
Let's look at a specific method that we can refactor, and get it out of there!
def format_tax_rate(value) '%.2f%' % (value.to_f * 100) end
And it might be used in our ERB like so:
<%= format_tax_rate(@sale.state_sales_tax) %>
This is some very simple code. It's a naive approach to formatting the decimal value as a percentage to two decimal places.
More importantly, it's only really needed in one or two views to display a formatted sales tax rate. Why then, should it clutter up our
ApplicationHelper in order to be shared? And what happens when you have 10, 20, or more small methods just like this one?
With SimplestView, we can move the method to a better place. If we were to handle this in our
Sales::IndexView, for example, we could do something like this:
class Sales::IndexView < ActionView::Base def formatted_state_sales_tax '%.2f%' % (@sale.state_sales_tax.to_f * 100) end end
So, what did we do here?
- We made our view match the conventions. For the
SalesController#indexcontroller and action, we'll use
Sales::IndexViewas the context for the template now found in
- We made a nicely named method which contains just the formatting we need.
- We use the instance method for
@sale, which we assigned in our controller, and is accessible to us inside of the view.
What are the benefits? Well, we get nicer encapsulation. We don't have to expose the instance variable inside of the ERB for this particular case
After this section, I want to make a quick aside to point out something important. With SimplestView, we're back to using Ruby as the Object-Oriented language that it is! If we need to share code, we make a
module to mixin. Or, if it makes sense, we can use inheritence.
If we have another method
formatted_county_sales_tax, we can extract a method to do the formatting. Then, if that new formatting method is used in multiple views, it's as easy as extracting a module like
In addition, we can now easily test, and refactor this view because it's just a Ruby class.
One of the more troubling ways I've seen developers cope with growing complexity in their ERB templates has been to move calculations and logic BACK into the controller.
What started out as an action that assigns a single instance variable, baloons into methods full of
before_filter) calls to assign and calculate many instance variables.
Let's refactor an example:
class SalesController < ApplicationController def show @sale = Sale.find(params[:id]) @purchase_count = @sale.purchases.count end end
Now, we can output both data about our
@sale, but we can also output our
@purchase_count number, too.
Instead, let's do it with SimplestView.
class Sales::ShowView < ActionView::Base def purchase_count @purchase_count ||= @sale.purchases.count end end
Now, we can still output our
purchase_count in our template, without cluttering our controller!
By now, it may be clear what I'm going to say. But, I'll say it anyway.
Extraneous logic in your ERB templates is very likely to be a code smell. Extensive branching logic, local variable assignment, etc. Time to get it out!
Inside of our
SalesController#show action, we'll likely render the template found in
show.html.erb. It might have some code like:
<% if @sale.starts_on <= Date.today && @sale.ends_on > Date.today %> On Sale! <% else %> Sale Starts On: <%= @sale.starts_on.strftime("%B %d, %Y") %> <% end %>
There are two things in this code that we really don't need. Both the
if conditional and the date formatting code can be encapsulated in our
class Sales::ShowView < ActionView::Base def active_sale? @sale.starts_on <= Date.today && @sale.ends_on > Date.today end def formatted_start_date @sale.starts_on.strftime("%B %d, %Y") end end
This is probably my favorite refactoring. And, it is likely to be the one you will use on a regular basis.
The benefits of these changes might not be the biggest pain you feel in your code, day to day. But together, all of the little changes you get to make, the added ease of refactoring, and the extra convention, make it a whole lot easier to write well-factored, maintainable code, every day.
I'm using SimplestView daily, in as many of my projects as possible. Not because the code I already have is horrendous, and not because there are no alternatives to refactor and clean up my code. I'm using it because it enables functionality in Rails that should be available already, and it gives me the conventions I kept searching for.
If SimplestView doesn't strike your fancy, you can go the decorator route with the fantastic Draper gem. Both Draper and SimplestView accomplish very similar things. Regardless, you should try using one of them, and see how it helps you clean up your code.