Idiomatic Ruby
Dan Hancu sent us a nice, bite-sized snippet of code, asking if there was a more elegant way to do what he was doing. His code was from a controller, part of a larger application that included a system for tracking an inventory of lenses. When creating a new type of lens, you just input a few parameters and the system then autogenerates all relevant lenses for that type of lens.
His code (in essense) is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
class TypeOfLensController < ApplicationController def create @type_of_lens = TypeOfLens.new(params[:type_of_lens]) if @type_of_lens.save flash[:notice] = 'TypeOfLens was successfully created.' populate(@type_of_lens) redirect_to :action => 'list' else render :action => 'new' end end private def populate(type_of_lens) x_count = type_of_lens.minimum_cylinder y_count = type_of_lens.maximum_sphere increment_step_size = 0.25 while y_count >= type_of_lens.minimum_sphere while x_count <= type_of_lens.maximum_cylinder lens = Lens.new lens.lens_type_id = type_of_lens.id lens.sphere = y_count lens.cylinder = x_count lens.quantity = 0 lens.save x_count += increment_step_size end x_count = type_of_lens.minimum_cylinder y_count -= increment_step_size end end end |
Now, naturally, since I’m not acquainted with the domain of the application in question, it’s hard for me to judge the bigger picture, such as whether the model associations and such are appropriate or not. I’m going to assume that Dan, who is much more acquainted with the domain than myself, did all that right, and I’ll just focus on some of the smaller mechanical details of the code in question.
First off, the populate method really ought to be a method on TypeOfLens, called via an after_create hook:
1 2 3 4 5 6 7 |
class TypeOfLens < ActiveRecord::Base after_create :populate def populate #... end end |
By setting up populate as an after_create hook, we can rest easily knowing that every time a new TypeOfLens is instantiated, that populate method will be auto-invoked. That knowledge lets us simplify the controller significantly:
1 2 3 4 5 6 7 8 9 10 11 |
class TypeOfLensController < ApplicationController def create @type_of_lens = TypeOfLens.new(params[:type_of_lens]) if @type_of_lens.save flash[:notice] = 'TypeOfLens was successfully created.' redirect_to :action => "list" else render :action => "new" end end end |
Now, we can turn our attention to the populate method itself. Although Dan’s original code certainly suffices, it has the flavor of C code, simply modified to use basic Ruby syntax. By taking advantage of standard Ruby methods and patterns, we can not only compact the code, we can also make it look like idiomatic Ruby code instead of C code.
First of all, note the loop idiom being used:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def populate(type_of_lens) x_count = type_of_lens.minimum_cylinder y_count = type_of_lens.maximum_sphere increment_step_size = 0.25 while y_count >= type_of_lens.minimum_sphere while x_count <= type_of_lens.maximum_cylinder #... x_count += increment_step_size end x_count = type_of_lens.minimum_cylinder y_count -= increment_step_size end end |
All this is doing is iterating from some upper bound, maximum_sphere, to a lower bound, minimum_sphere, by some constant increment. The inner loop does the same thing, from a lower bound to an upper bound. Ruby, predictably, has a method for that specific use case, Numeric#step:
1 2 3 4 5 6 7 8 9 |
STEP_SIZE = 0.25 def populate maximum_sphere.step(minimum_sphere, -STEP_SIZE) do |sphere| minimum_cylinder.step(maximum_cylinder, STEP_SIZE) do |cylinder| #... end end end |
The Numeric#step method simply calls the associated block once for every increment of the second parameter, from the receiver to the first parameter. In other words, it “steps” (in the case of the outer loop) from maximum_sphere to minimum_sphere in increments of -STEP_SIZE.
Lastly, let’s address the process of creating a new Lens instance. In Dan’s original code, this was done by first creating an empty Lens object, then assigning the relevant attributes one-by-one, and saving:
1 2 3 4 5 6 |
lens = Lens.new lens.lens_type_id = type_of_lens.id lens.sphere = y_count lens.cylinder = x_count lens.quantity = 0 lens.save |
The Rails way of doing this employs the create method, passing it a hash of the attributes you wish the new object to have. It then returns the newly created (and saved) object. In our version, we ignore the return value, since it is not relevant to what we’re doing—it suffices us to know that the Lens object was created.
1 2 |
Lens.create :lens_type_id => type_of_lens.id, :sphere => y_count, :cylinder => cylinder, :quantity => 0 |
The final version of the TypeOfLens model:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class TypeOfLens < ActiveRecord::Base STEP_SIZE = 0.25 after_create :populate # ... def populate maximum_sphere.step(minimum_sphere, -STEP_SIZE) do |sphere| minimum_cylinder.step(maximum_cylinder, STEP_SIZE) do |cylinder| Lens.create :lens_type_id => id, :sphere => sphere, :cylinder => cylinder, :quantity => 0 end end end # ... end |
In the end, I think this exercise illustrates nicely the value of learning Ruby, as well as Rails. Certainly, we all must start some place, and Rails offers plenty to learn for newcomers. But once you know the basics, it pays in a big way to keep stretching, learning Ruby idioms for common patterns.
The Rails way, after all, is only a specialized subset of the greater Ruby way.

