Showing posts with label Ruby Newbie. Show all posts
Showing posts with label Ruby Newbie. Show all posts

Friday, January 16, 2009

Testing Values Returned From ActiveRecord



Be really careful about testing values returned from the db by ActiveRecord. How you fetch it affects the data type. ActiveRecord typecasts the column values returned from the database... sometimes.

This bit me hard today:

If you get a model back from active record e.g. with a MyClazz.find() ActiveRecord::Base will read the dictionary tables and typecast the returned attribute to what you expect. If you use one of the connection class methods, it doesn't do this, it just returns everything as a string.
Comparisons to integer, for example, will FAIL.

Watch this:


# run the console

[chrisa@ibs-chrisa-ux1 lims_m4]$ script/console
Loading development environment (Rails 2.1.1)

# fetch an arbitrary slide

>> s = Slide.find(81247588)
=> #<slide id: 81247588,
slide_group_id: 81246827,
...>

# now, I'm expecting an integer for slide_group_id, so I test it with the == operator:

>> s.slide_group_id == 81246827
=> true

# how nice, principle of least astonishment and all...
# now let's get that same record using the Base.connection method:

>> hash = ActiveRecord::Base.connection.select_one("SELECT * FROM slides WHERE id = 81247588")
=> { "id"=>"81247588",
"slide_group_id"=>"81246827",
...}
>> sgi = hash['slide_group_id']
=> "81246827"

# Whoa. It's a string:

>> sgi == 81246827
=> false
>>

Monday, June 30, 2008

A word of warning


And now a "a word of warning" (sic) from the ActiveRecord API docs:


Don‘t create associations that have the same name as instance methods of ActiveRecord::Base. Since the association adds a method with that name to its model, it will override the inherited method and break things. For instance, attributes and connection would be bad choices for association names.


Reading that was another one of those "No. Really." moments.


The second time an undetected collision of this type got me (note to self: don't name a database column "errors" unless you have an afternoon to spare) I really considered some shell (better yet Perl, heh) scripts to grep out all the method names from all the plugins + my code and check for collisions. I now instinctively avoid any obvious or intuitive sounding name for a column or method. Since we're up to a dozen+ plugins, it's probably in use already.


The interpreter could issue a warning of some kind when overloading and a test flag was set, I suppose. HEY! Agile means no whining. Suck it up and KEEP TYPING!

Wednesday, May 7, 2008

Ruby Constants Aren't

I don't know how I missed this but I did. This morning it bit us, hard.

Ruby constants aren't constant. Well, actually, the constant is a reference to an object. That's immutable, but you can use it to change the object it will refer to. Really. It's a feature:

"Ruby, unlike less flexible languages, lets you alter the value of a constant, although this will generate a warning message." - from Programming Ruby, by Dave Thomas

Umm, isn't that what's usually meant by "variable?" So you mean that...


irb(main):001:0> CONST = "foo"
=> "foo"
irb(main):002:0> CONST = "bar"
(irb):2: warning: already initialized constant CONST
=> "bar"


Yeah. Well, I vote we get rid of that annoying error message. It kinda spoils the thrill.

Wednesday, April 9, 2008

Tweaking XML Rendering For ActiveScaffold

Another day, another few hours of googling Ruby blogs to figure out how to accomplish some seemingly trivial task. This time it's making some minor adjustments to the XML rendering of an ActiveRecord model when the controller is being supplied by ActiveScaffold. Don't get me wrong, I love automagically created applications. It's just that, sometimes, all you want to do is change... this one... freaking detail...

API says you can pass options to to_xml. As in:

my_model.to_xml :except => [:ugly_field]

Problem is, the to_xml method is getting called by the ActiveScaffold ApplicationController and you don't really want to edit that. If you just want to make a small adjustment and not rewrite the entire rendering method the best option you have is to overload this method in the model class, add the options to the passed array and call super()

def to_xml(options={})
  options[:methods] = [:get_formatted_value, :other_value]
  options[:except] = [:irrelevant_accession_number]
  super(options)
end

Should have been obvious, I suppose, but I spent some of my (dwindling) wattage on it. Oh well, here's hoping the next rubie finds this on a google search for "activescaffold xml rendering."

Wednesday, March 19, 2008

Ruby Method References In A One-Pass Interpreter

Ruby allows for some very clever techniques involving run-time code evaluation. I find these particularly handy when writing table driven code (e.g. file parsers). But coder beware. There's been a lot of discussion about whether Ruby blocks are truly closures but the fact that it's a one-pass interpreter (at least the reference implementation I'm using, ruby 1.8.5) can cause some unexpected results as well.

Consider this simple case: initializing a class property using a method reference. The Object.method call will succeed or fail (return nil) depending on when it's called in the class definition. First, let's put the initialization at the top of the class (where most of us would normally put it):

class TestMeth

@@method_ref = self.method(:meth)

def self.meth
  puts "executing meth()"
end

def run_method
  @@method_ref.call
end

end

tm = TestMeth.new
tm.run_method

Sorry, no can do:

[chrisa@doppio ~]$ ruby t.rb
t.rb:3:in `method': undefined method `meth' for class `Class' (NameError)
from t.rb:3

Now move the assignment to the other side of the method definition:

class TestMeth

def self.meth
  puts "executing meth()"
end

@@method_ref = self.method(:meth)

def run_method
  @@method_ref.call
end

end

tm = TestMeth.new
tm.run_method

That works:

[chrisa@doppio ~]$ ruby t.rb
executing meth()

The behavior is the same if you make @@method_ref a constant (i.e. 'METHOD_REF'). The interpreter hasn't gotten to that line of the file when the assignment is executed. If you're using method references, you either have to position them correctly in the file or assign them at run-time in an initializer method. Works, but not as you might expect.

Tuesday, March 4, 2008

Nil desperandum!

Which means literally, "Despair of Nothing!" Makes you wonder if Horace ever worked in software.

When is a null object reference not a null object reference? Why, when you can dereference it, of course.

If a Ruby method returns nil and I try to dereference it thinking I have a valid object reference, it's going to throw, right? Raise an exception? Hurl?

Erm, no. Actually, nil is an object. Let's have some fun with irb:

[chrisa@doppio ~]$ irb

Can I convert it to an integer?

irb(main):001:0> nil.to_i
=> 0

Yep. At least it's 0. How 'bout a string?

irb(main):002:0> nil.to_s
=> ""

Of course, it's an object, silly! And an array, etc.

irb(main):003:0> nil.to_a
=> []

This next one is really cool. Let's say you got this object reference back from ActiveRecord using MyModel.find(:first). Since you've dutifully followed the convention, you expect to get it's accession number by dereferencing the automagic property "id":

irb(main):004:0> nil.id
(irb):2: warning: Object#id will be deprecated; use Object#object_id
=> 4

Love that deprecation warning. We all hunt down and fix those right away, right? I spent a pleasant afternoon staring at the source of that one. Remember to read those web server logs carefully, kids!

Fun with Ruby Conditional Evaluation

The first in a series of postings for the unwary Ruby Newbie (Rubie?).

Is a numeric value of 0 true or false? To a C or Perl programmer it's obviously false. In Java it's a trick question (try it). But even those who've never programmed in C or C++ seem to share a general expectation of "false, duh."

What about Ruby? It's easy enough to find out. We'll use irb (for "interactive ruby") to put the question to the interpreter directly:


[chrisa@doppio ~]$ irb
irb(main):001:0> if (0) then puts "true" end
true

Which is to say that any numeric value will test true. How about an empty string?

irb(main):001:0> if ("") then puts "true" end
(irb):1: warning: string literal in condition
true

Well, that's a little bit friendlier but still, I can see getting quietly horked by that one. Contrast this with Perl's behavior. The interactive debugger is a bit less friendly, so we just execute the test directly from the command line:


[chrisa@doppio ~]$ perl -e 'if (0) {print "true\n"} ;'
[chrisa@doppio ~]$

[chrisa@doppio ~]$ perl -e 'if ("") {print "true\n"} ;'
[chrisa@doppio ~]$

Doesn't quite match the "Principle of Least Astonishment" pattern...