- Use Module methods
Here's where I disagree with something Ryan said in his 101st Railscast, in which he suggests using Class (or instance) methods with variables over using Module methods. I prefer to use Module methods. Here's a typical (truncated) example:
module DisplayHelper
def self.get_stylesheets_by_request(request)
user_agent = request ? request.user_agent : nil
self.get_stylesheets_by_ua(user_agent)
end
def self.get_stylesheets_by_ua(some_user_agent)
return STYLESHEETS_FOR[:palm] if some_user_agent =~ %r[Palm]
STYLESHEETS_FOR[:normal]
end
end
Then, in the RSpec file:
describe %q[get_stylesheets_by_request] do
mobile = %w[mobile]
it %Q(should add the stylesheet #{mobile} for Palm browsers) do
user_agent = %q[Some Palm User Agent]
request = mock_model(Object, :user_agent => user_agent)
DisplayHelper.get_stylesheets_by_request(request).should == mobile
end
normal_browser = %w[normal_browser]
it %Q(should add the stylesheet #{normal_browser} for all other browsers) do
user_agent = %q[Some User Agent]
request = mock_model(Object, :user_agent => user_agent)
DisplayHelper.get_stylesheets_by_request(request).should == normal_browser
end
end
As I said above, Ryan and I have different preferences regarding Class vs. Module. I like having helper methods that are testable in a more "purely functional" paradigm, wherein we mock a request, but don't need to mess around within the base controller object or the like. We just pass our mock request model into the get_stylesheets_by_request method and take it from there. Either approach works, you may find that one or the other matches your own thought process more closely.- Use Constants
In the code example above, notice the use of the STYLESHEETS_FOR Constant, which we can define as follows:
STYLESHEETS_FOR = {
:normal => %w[normal_browser],
:palm => %w[mobile]
}
It gives us access to either normal_browser.css or mobile.css, as appropriate. This is a somewhat contrived example, but you could use whatever list of stylesheets you want for the values in each pair, expanding to customize for Safari, Epiphany, Opera, etc.
This technique could be used for any data that is unlikely to change in the course of an app running, but is not tied specifically enough to a given model to be stored in the DB. This allows you to avoid continually reconstructing never-changing local variables inside a method call. Some more realistic examples appear again in the MessageHelper tip below.- Return guards can simplify flow control
I loath if - elseif - else - end flow control, and strongly prefer return guards. It's a personal bigotry that I freely admit, because I think return guards make code more readable, shorter, and more in line with how I think. Here's a rewrite of Ryan's star_type method from Railscast #101 that uses return guards rather than an if block.
def star_type(value)
return 'full' if value > 1
return 'half' if value == 1
'empty'
end
You save 4 lines, and also comply with my enraged, semi-coherent rantings. You can also get some additional shrinkage with a ternary. Here's star_type again, with a ternary:
def star_type(value)
return 'full' if value > 1
value == 1 ? 'half' : 'empty'
end
You save a line. I find it more readable than the if block. That may be because I like reading Haskell code. Who knows. Again, your flow control preference mileage (or kilometrage) may vary.- DRY up your messages in a MessageHelper
I like to create a MessageHelper.rb file that contains my messages. I usually have several Constant Hashes that vary depending on the purposes of the message.
module MessageHelper
DESCRIPTION_OF_RESOURCE = {
:name_of_resource => %q[My description...]
}
EXPLAIN = {
:not_empowered_to_delete => lambda { |type| %Q[You are not empowered to delete a #{type}.] }
}
ERROR = {
:overlapping_blocks => %q[Overlapping blocks of time]
}
MESSAGE = {
:confirm_short => %q[Are you sure?],
:confirm_long => lambda { |x| %Q[Are you sure you want to destroy this #{x}?] },
:logout_successful => %q[You have been logged out.]
}
TITLE = {
:new => lambda { |x| %Q[Make a new #{x}] },
:owner => lambda { |x| %Q[Owner of #{x}] },
:show => lambda { |x| %Q[Show #{x}] },
}
end
These are obviously truncated. I use DESCRIPTION_OF_RESOURCE and EXPLAIN for hover explanations and the like. Notice also that the EXPLAIN and MESSAGE[:confirm_long] values are Procs, allowing you to reuse them abstractly. The purpose of the ERROR, MESSAGE and TITLE hashes should be obvious from their names.1 The main benefit of this practice is that you can conform to DRY principles, while also separating your messages logically according to what they'll be needed for.
Of course, you can wrap the use of each of these hashes inside helper methods, like explain, message, or what have you.
1(Maybe the purpose of TITLE isn't obvious: It's to facilitate link differentiation with <a> titles as per http://www.w3.org/TR/WCAG10-HTML-TECHS/#link-text.)- Use parallel (pattern-matching) assignments
Whenever assigning into multiple variables, it can often be helpful to do simultaneous parallel assignments using pattern matching. Here's an example from an RSpec file. I have a resource called Preceptor which has many Rotations, and a helper method called create_preceptor_and_rotations that does what you (hopefully) expect:
@preceptor, @preceptor_rotations = create_preceptor_and_rotations
@preceptor, discard__rotations = create_preceptor_and_rotations
In the first case, I want to use both @preceptor and @preceptor_rotations for some purpose. In the 2nd case, I want to keep the @preceptor, but the name of the rotations variable informs anyone reading the code that I don't care about the rotations in this particular spec instance. In both cases, I've done assignments into two variables simultaneously.
This sort of pattern is common in situations like this:
def some_method_that_takes_a_list(*args)
head, *tail = *args
# do some operations, maybe something recursive
return [head, tail]
end
Which returns as follows:
some_method_that_takes_a_list(7) -> [7, []]
some_method_that_takes_a_list(7, 8) -> [7, [8]]
some_method_that_takes_a_list(7, 8, 9) -> [7, [8, 9]]
some_method_that_takes_a_list([7, 8, 9]) -> [[7, 8, 9], []]
some_method_that_takes_a_list(*[7, 8, 9]) -> [7, [8, 9]]
So there's my list. These are all obviously fairly subjective points, but I find that these approaches work well for me, and my co-workers seem pretty agreeable to them. Maybe they'll work for you, too.