RubyConf, Day 1, Plenary Part 2: Advanced Ruby Class Design
No coffee outside. How do they expect me to survive? However, the signs are out for the box lunches and they look to not suck.
Jim starts with a slide of a Flame Maple guitar back. Very beautiful.
Jim’s journey: FORTRAN (Physics), Lisp (in a fortran class taught by Daniel Friedman), C, Modula 2, C++, Eiffel, Java, FORTH, TCL, and Perl.
On learning Lisp: “Yes, these are your father’s parentheses”
Out to show interesting techniques that aren’t Java-esque.
Box 1: Master of Disguise, Rake::FileList
It’s essentially a specialized array which can be init’d with a GLOB, has some extra methods including a custom to_s, and is a lazy evaluator. First cut inherited from Array (very non-Java, this inheriting from a concrete class)
Be sure to call super in subclass initializers.
Overrides Array’s various accessor methods to cause them to all auto-resolve on access. This makes fl + […] work, but doesn’t help with […] + fl … so implement to_ary which is automatically called. Which would be peachy except that inheriting from Array causes this not to be called. So don’t inherit from Array; instead contain one (and cause all your previously overridden accessors to access the contained array)
def [](index)
resolve unless @resolved
@items[index]
end
(and he showed an excellent metaprogramming method to handle defining all the accessors)
Box 2: The Art of Doing Nothing, Builder::XmlMarkup
uses method_missing to construct tag accessors, but what about name collisions (e.g. class)?
Inherits from BlankSlate (not Object):
class BlankSlate
instance_methods.each do |name|
undef_method name unless name =~ /^__/
end
end
Kernel is a module included on all Objects, so this is icky:
require ‘blank_slate’
class Kernel
def foo
‘foo’
end
end
And now BlankSlate is not blank. So use method_added hook (in Kernel) to catch when anyone reopens the class, then use that to have BlankSlate hide/remove the method if someone adds something to Kernel. Excellent, until:
module Foo
dev foo
‘foo’
end
end
class Object
include Foo
end
@#%&! Time for the append_features hook and a nearly identical implementation.
Box 3: Parsing without Parsing,
Wouldn’t it be nice to do this:
User.select { |user|
user.name == ‘jim’
}
The Naive Implementation
class User
def self.select(&block)
find(:all).select(&block)
end
end
… perhaps a bit inefficient as you do a full table scan. ;] Might be good to offload the work to the database (but how to avoid SQL?)
class User
def self.select(&block)
cond = block_to_condition(block)
find(:all, :conditions => cond)
end
end
Use ParseTree: a la Ambition
user = TableNode.new(“users”)
result = user.name
puts result.to_s
users.name # <– that’s the SQL fragment to reference the db column
class TableNode < Node
def initialize(table_name)
@table_name = table_name
end
def method_missing(sym, *args, …
[I can’t keep up. We’ll find his slides. I’m going to nap at lunch.]
blog comments powered by Disqus