Tuesday, April 24, 2007

Peepcode is your very own Rails personal trainer

If you are developing with Ruby on Rails and haven't tried Geoffrey Grosenbach's (topfunky) Peepcode screencasts, then you are missing out on the world's most affordable Rails training.

At a mere 7 clams a pop (I went with the 10-pack subscription), you get over an hour of high quality, step-by-step, hands-on instruction in full size or iPod format. Each episode is chock-full of nifty tips, techniques, and practices that'll hone you into a stellar Rails developer. You can even post support questions on his Google groups mailing list.

It's like having your very own Rails personal trainer.

The pig in me wants to rush through all the screencasts at once in a marathon orgy of glorious geek-out, while the cartoon fox in me wants to pace myself, slowly savoring each episode like a haiku running at 5 fps.

Kudos to Geoffrey for this top-notch service. I've no doubt the knowledge gleaned from each of these will likely save my chunky bacon many times over.

Labels:

Metaprogramming and DSL Resources?

As a newcomer to Ruby and metaprogramming, I'm still looking for good resources on this and the art of creating DSLs.

I'm picking up a little here and there by reading blog posts and articles like this one on InfoQ, as well as pouring over code from Rails and other Ruby projects, but it seems like there would be a market for a book written by someone who has done a lot in this area.

p.s. Here's a modification of an implementation from the aforementioned article that supports passing in a lambda expression, a block, or both. I suspect there may be better ways to do some of this and would love to hear it. Regardless, I'm continually astounded by the power of mixins and the reflection capabilities built into Ruby.
module Properties

def self.extended(base)
base.class_eval do
def fire_event_for(sym, arg)
@listener[sym].each {|l| l.call(arg) }
end
end
end

def property(sym, predicate=nil, &block)
define_method(sym) do
instance_variable_get("@#{sym}")
end

define_method("#{sym}=") do |arg|
if block_given? and predicate
return if !predicate.call(arg) and !block.call(arg)
elsif predicate
return if !predicate.call(arg)
elsif block_given?
return if !block.call(arg)
end
instance_variable_set("@#{sym}", arg)
fire_event_for(sym, arg)
end

define_method("add_#{sym}_listener") do |x|
@listener ||= {}
@listener[sym] ||= []
@listener[sym] << x
end

define_method("remove_#{sym}_listener") do |x|
@listener[sym].delete_at(x)
end
end

def is?(test)
lambda {|val| test === val }
end

def includes?(*test)
lambda {|val| test.include? val }
end
end

class CruiseShip
extend Properties

# property :direction
# property :direction, includes?('north', 'south', 'east', 'west')
property :speed, is?(0..300)
property(:direction, includes?('north', 'south', 'east', 'west')) {|v| v == 'thataway' }
# property(:speed) {|v| v >= 0 && v <= 300 }
end

h = CruiseShip.new

h.add_direction_listener(lambda {|x| puts "Oy... someone changed the direction to #{x}"})
h.direction = "north"

h.add_speed_listener(lambda {|x| puts "Oy... someone changed the speed to #{x}"})
h.add_speed_listener(lambda {|x| puts "Yo, dude... someone changed the speed to #{x}"})

h.speed = 200
h.speed = 300
h.speed = 301
h.speed = -1

h.direction = "south"

puts h.direction
puts h.speed

h.remove_speed_listener(1)

h.speed = 200
h.speed = 350

h.direction = "thataway"
h.direction = "whatever"

puts h.direction
puts h.speed
Output:
Oy... someone changed the direction to north
Oy... someone changed the speed to 200
Yo, dude... someone changed the speed to 200
Oy... someone changed the speed to 300
Yo, dude... someone changed the speed to 300
Oy... someone changed the direction to south
south
300
Oy... someone changed the speed to 200
Oy... someone changed the direction to thataway
thataway
200

Labels: