Lots of Ruby learning this week, including two tricks with arrays and why you might want to freeze a value, plus some surprising behavior (and how to work around it) with ActionView’s
Why You Might Use
Mat Sadler at Globaldev has a fantastically detailed look at all of the changes in Ruby 2.1. There’s plenty to learn from it, but I was most glad to finally understand what String#freeze does and why you might want to use it.
Remember that strings in Ruby are mutable, meaning they can always be changed. For this reason, every time Ruby evaluates a string, it has to create a new object. Mat illustrates:
def env 'development' end # returns new String object on each call env.object_id #=> 70329318373020 env.object_id #=> 70329318372900
If you’re wondering why, consider the following additional method:
def env! env << '!' end
If env returned the same string object (
'development') each time it was called, then the first time you called
env! that object would get an exclamation point appended, and after that even calling plain old env would return ‘development!’. So it prevents some of unexpected behavior that Ruby creates a new object each time a string is evaluated, however that comes with a cost. All of these new objects clutter up ruby’s object space, incerasing RAM usage and requiring more garbage collection to clean them up.
This is where String#freeze steps in. Again, as Mat demonstrates:
def env 'development'.freeze end # returns the same String object on each call env.object_id #=> 70365553080120 env.object_id #=> 70365553080120
'development' has two handy effects:
- It lets ruby retain the same internal object each time env is called, eliminating the need for that additional RAM and garbace collection.
- It raises
RuntimeError: can't modify frozen stringif you accidentally try to modify the string.
I didn’t think I’d seen freeze used often, but sure enough once I went looking for it, here it turned up in Rails for freezing constant values.
How to Convert a Range to an Array of Integers in Ruby
Earlier this week I needed an array in ruby containing a bunch of sequential integers.
(1..9).to_a would do the trick, as would
Array 1..9 in one less character, but a quick search pointed me to an interesting use of the multitalented splat operator to instantiate an array from a range:
[*1..9] => [1, 2, 3, 4, 5, 6, 7, 8, 9]
How to Use Ruby’s
Array() Constructor to Guard Against nil.each
This week’s Ruby Weekly pointed me to a useful technique fom the folks at Dockward for simplifying code that iterates over an array or hash that might be nil. Your first instinct might be to first check for nil:
if params[:ids] # make sure it's not nil params[:ids].each do |id| puts id end end
Array(nil) in ruby returns an empty array, you can replace the nil check with:
Array(params[:ids]).each do |thing| ... end
cycle Method Retains State Across Collections
While rendering out two sets of employees using the same partial in a Rails view, I was surprised to learn that the
cycle method I was using to add ‘even’ and ‘odd’ classes to list items was starting with ‘even’ in the first set, but with ‘odd’ in the second.
<!-- app/views/employees/_employee.html.erb --> <li class="employee <%= cycle('even', 'odd') %>">
I soon realized it was because the first collection had an odd number of employees, and
cycle in the second rendering was simply picking up where it left off in the first. Fortunately, this can be fixed by giving the cycle a name and manually resetting it after the first render:
<!-- app/views/employees/_employee.html.erb --> <li class="employee <%= cycle('even', 'odd', name: :employees) %>"> # app/views/employees/index.html.erb <!-- Starts with 'even' --> <%= render @special_employees %> <% reset_cycle :employees %> <!-- Now also starts with 'even' --> <%= render @normal_employees %>