This is the first article in a series which will cover various refactoring techniques that are specific to Rails. Refactoring is used to improve the design of existing code without changing its functionality.
The first refactoring I will be discussing involves taking a bit of logic out of the view and placing it in the model. This can clean up the views quite nicely.
Let's start out with a very simple example. We have a Person model with first_name, last_name, and middle_initial attributes. In our view we have this code:
Code : ruby -
fold
-
unfold
- Full Name:
- <%= @person.first_name %>
- <% unless @person.middle_initial.blank? %>
- <%= @person.middle_initial %>.
- <% end %>
- <%= @person.last_name %>
Not very pretty. Even worse, this code is repeated several times throughout the application. To remove this duplication and to clean up the views, we can define a method in the Person class to handle this logic.
Code : ruby -
fold
-
unfold
- class Person < ActiveRecord::Base
- def full_name
- result = first_name + ' '
- unless middle_initial.blank?
- result += middle_initial + '. '
- end
- result += last_name
- result
- end
- end
This makes the view much nicer:
Code : ruby -
fold
-
unfold
- Full Name: <%= person.full_name %>
We successfully shoved the logic into the Person model, but I'm still not satisfied. I think we can clean up the full_name method, but before we do that, let's create some tests to make sure everything continues to work as we do the cleaning. Tests are very important when refactoring to make sure we don't break anything as we improve the code.
Code : ruby -
fold
-
unfold
- # in person_test.rb
- def setup
- @person = Person.new(:first_name => 'John', :last_name => 'Smith')
- end
-
- def test_full_name
- assert_equal 'John Smith', @person.full_name
- end
-
- def test_full_name_with_middle_initial
- @person.middle_initial = 'A'
- assert_equal 'John A. Smith', @person.full_name
- end
Good, the tests are passing so now it's time to improve the full_name method. I would like to remove that temporary "result" variable and merge it all onto one line. I can use an array to help me out. Maybe something like this:
Code : ruby -
fold
-
unfold
- def full_name
- [first_name, middle_initial, last_name].join(' ')
- end
That looks nice and tidy, but unsurprisingly it fails both of the tests. It fails the second test because there's no period, so let's create a new method to return a middle initial with a period:
Code : ruby -
fold
-
unfold
- def middle_initial_with_period
- middle_initial + '.' unless middle_initial.blank?
- end
-
- def full_name
- [first_name, middle_initial_with_period, last_name].join(' ')
- end
The second test is working now, but the first test still fails because there's an extra space in there when no middle initial is defined. We can remove that by using array's "compact" method like this:
Code : ruby -
fold
-
unfold
- def full_name
- [first_name, middle_initial_with_period, last_name].compact.join(' ')
- end
Our tests are all passing now. Yay!
Wait one minute, I hear someone say, the compact method just removes nil values, what if the middle initial is an empty string? Well let's add a test and find out:
Code : ruby -
fold
-
unfold
- def test_full_name_with_empty_middle_initial
- @person.middle_initial = ''
- assert_equal 'John Smith', @person.full_name
- end
Surprisingly this passes successfully without us changing anything. Why? Because the middle_initial_with_period method already takes care of empty strings and will return nothing (nil) if middle_initial is blank.
In this tutorial you learned how to refactor code from the view into the model, but this can just as easily be applied to code in the controller or any other part of the application. Your job is to look through your current rails projects for code that can be moved into a model. Usually if it can be moved into a model, it should, but just be careful to keep view/controller specific code out of the model. For example, don't move HTML code into the model.
If you found this useful, keep an eye out for the next Refactoring on Rails article. In the meantime, check out Martin Fowler's excellent book: Refactoring.
Check out the next article in this series: Multiple Scopes in Controller
Last edited by ryanb (2006-11-16 16:29:48)
Railscasts - Free Ruby on Rails Screencasts