This post originated from an RSS feed registered with Ruby Buzz
by Obie Fernandez.
Original Post: Default Values for ActiveRecord Attributes
Feed Title: Obie On Rails (Has It Been 9 Years Already?)
Feed URL: http://jroller.com/obie/feed/entries/rss
Feed Description: Obie Fernandez talks about life as a technologist, mostly as ramblings about software development and consulting. Nowadays it's pretty much all about Ruby and Ruby on Rails.
The list of attributes associated with an ActiveRecord model class is not coded explicitly. At runtime, the ActiveRecord model examines the database schema directly from the server. Adding, removing, and changing attributes and their type is done by manipulating the database itself, either directly using SQL commands or GUI tools, or ideally via ActiveRecord migrations.
No matter how you modify your database schema, the practical implication of the ActiveRecord pattern is that you have to define your database table structure and make sure it actually exists in the database prior to working with the model.
Default Attribute Values
Sometimes you want to set default attribute values at the model layer, not the database layer. A common example is the case when your model should return ?n/a? instead of a nil (or empty) string for an attribute that has not been populated yet. Seems simple enough and it's a good place to start looking into how attributes exist at runtime.
To begin, let's whip up a quick test case describing the desired behavior.
class SpecificationTest < Test::Unit::TestCase
def test_default_string_for_tolerance_should_be_na
spec = Specification.new
assert_equal 'n/a', spec.tolerance
end
end
We run that test and it fails, as expected. ActiveRecord doesn't provide us with any class-level methods to define default values for models declaratively. So it seems we'll have to create an explicit attribute accessor that provide a default value.
Normally, attribute accessors are handled magically by ActiveRecord's internals, but in this case we're overriding the magic with an explicit getter. All we need to do is to define a method with the same name as the attribute and use Ruby's or operator, which will short-circuit if @tolerance is not nil.
class Specification < ActiveRecord::Base
def tolerance
@tolerance or 'n/a'
end
end
Now we run the test and it passes. Great. Are we done? Not quite. We should test a case when the real tolerance value should be returned. I'll add another test for a specification with a not-nil tolerance value and also go ahead and make my test method names a little more descriptive.
class SpecificationTest < Test::Unit::TestCase
def test_default_string_for_tolerance_should_return_na_when_nil
spec = Specification.new
assert_equal 'n/a', spec.tolerance
end
def test_tolerance_value_should_be_returned_when_not_nil
spec = Specification.new(:tolerance => '0.01mm')
assert_equal '0.01mm', spec.tolerance
end
end
Uh-oh. The second test fails. Seems our default 'n/a' string is being returned no matter what. That means that @tolerance must not get set. Should we even know that it is getting set or not? It is an implementation detail of ActiveRecord, is it not?
The fact that Rails does not use instance variables like @tolerance to store the model attributes is in fact an implementation detail. But model instances have a couple of methods, write_attribute and read_attribute, conveniently provided by ActiveRecord for the purposes of overriding default accessors, which is exactly what we're trying to do.
Let's use that knowledge to fix our Specification class.
class Specification < ActiveRecord::Base
def tolerance
read_attribute(:tolerance) or 'n/a'
end
end
Now the test passes. How about a simple example of using write_attribute?
class SillyFortuneCookie < ActiveRecord::Base
def message=(txt)
write_attribute(:message, txt + ' in bed')
end
en
d
Alternatively, both of these examples could have been written with the shorter forms of reading and writing attrbutes, using square brackets.
class Specification < ActiveRecord::Base
def tolerance
self[:tolerance] or 'n/a'
end
end
class SillyFortuneCookie < ActiveRecord::Base
def message=(txt)
self[:message] = txt + ' in bed'
end
end
While working on my book I am occasionally ending up with snippets of writing that don't fit for some reason or another. This little article is one of those cases, which I plan to simply format in HTML and dump here for the benefit of random Googlers in the future.