Rails and Domain Specific Languages

From my coworker Ryan, while I was complaining about figuring out yet another DSL in Rails (YADSLIR?):

When you're working with Rails, you don't just get to experience the pleasure of using Ruby, you also get to experience the pleasure of learning 40 fucking different DSLs on top of it

(Ruby) Corrupted by JavaScript

I have been corrupted by JavaScript. Ruby really annoyed me when I could not just add properties and functions totally willy-nilly to instances. Ruby makes it easy to reopen classes, or even instances, but it is not so obvious how to do so without creating a new scope (and hence losing your closure).

The ~standared mechanism is to do something vicious like this:

foo = "hello"
msg = "woot!"
class< "woot!"

Aside from the fact that this is a vicious hack, it relies on the fact that send lets you invoke private methods (define_method) on the singleton class (more recently going by the alias of eigenclass) of foo. This is pretty much a bug and a violation of the object. It's like object rape, or something.

A nicer way to do it, to my mind, is to use anonymous modules. The really nice part of using anonymous modules is that the Module constructor takes a block which is evaluated in the context of the new module, letting you legally call private methods, like define_method.

moo = "TOOW!"
foo.extend(Module.new { attr_accessor :baz })

foo.extend(Module.new { define_method(:toow) { puts moo }} )

foo.baz = 7
foo.toow # => "TOOW!"

moo = "MOOO!!"

foo.toow # => "MOOO!!"

This has the same effect and no nasty exploitation of the send bug!

Of course, with javascript, it is just...

foo = "woot"
foo.bar = 7
foo.stuff = function(a, b, c) { a * b * c }

I still like ruby more, though ;-)

Ruby Orchestration Language Fun

After the Silicon Valley Ruby Conference I wanted to hack some at a declarative language for web service orchestration. Tests pass, but the web service bindings don't exist yet =( Right now it can be used as a nice declarative process for... er, ruby?

class TestProcess < Test::Unit::TestCase
  
  def test_example
    p = Processor::process :wombats do
      receive :message

      forward :message, :to => :provisioning, 
                        :save_reply => :provision_response
            
      forward :message, :to => :payroll
      
      if_test( proc { provision_response =~ /example/ } ) do
        transform :message, :with => :some_transformer,
                            :save_in => :transformed_message

        forward :transformed_message, :to => :server_group    
      end
                            
      forward :provision_response, :to => :helpdesk
    end    

The rest of the test case just mocks out the services needed and tests that the things works. Not as exciting but here it is for completeness =)

  
    p.services[:provisioning] = lambda do |process, msg| 
      @provision_msg = msg 
      assert process.expecting_response?
      "address@example.com"
    end
    
    p.services[:payroll] = lambda do |process, msg|
      @payroll_msg = msg
    end
    
    p.services[:server_group] = lambda do |process, msg| 
      @server_group_msg = msg
    end
    
    p.services[:helpdesk] = lambda do |process, msg|
      process.suspend
    end
    
    msg = OpenStruct.new
    
    p.start msg
    
    assert_equal @provision_msg, msg    
    assert_equal @payroll_msg, msg
    assert_equal @server_group_msg, "address@example.com"
    assert_equal p.transformed_msg, "address@example.com"
    assert p.suspended?
    
    p.resume "back from helpdesk"
    
    assert p.completed?
  end
end

For the most part I am kind of happy with it. The nasty wart of

if_test( proc { provision_response =~ /example/ } ) do
  transform :message, :with => :some_transformer,
                      :save_in => :transformed_message

  forward :transformed_message, :to => :server_group    
end

annoys me. It's needed to lazily evaluate the construct, using a real if there evaluates it at the wrong time. Astute code readers will also notice I am not testing the transform. Haven't done that yet -- will probably remove it as a first class concept if I do anything more with the code, a transform is just a local service, and services are all wrapped in ruby methods, so instead of importing the service, just provide the transform and voila, your toast is burnt.

Wish ruby continuations were migratable/serializable.

Why drbunix:// is Cool

So, ruby threads have issues. Multiprocessing, on the other hand, works great. I have been doing stuff like:

#!/usr/bin/env ruby

require 'drb'
URI = "drbunix:///tmp/foopie"

class Master
    def initialize(count=100)
        @jobs = []
        count.times do |i|
            @jobs << "Job ##{i}"
        end
    end
    
    def take(n)
      taken = []
      while taken.length < n && !@jobs.empty?
        taken << @jobs.shift
      end
      taken
    end
end

DRb.start_service URI, Master.new

pids = []
10.times do |i|
    pids << fork do
        DRb.start_service
        master = DRbObject.new_with_uri URI
        until (jobs = master.take(2)).empty?
          jobs.each do |j| 
            puts j
            sleep rand
          end
        end
    end
end

puts "started children" 
pids.each { |pid| Process.wait(pid) }

To slam though jobs in ruby. Doing it over unix sockets instead of tcp is just nicer =) It just spreads the workload across processes instead of threads.

DRb over Unix Sockets

So, it is right in the docs, but somehow I overlooked it... You can do DRb over unix sockets dirt easily =)

require 'drb'

class Logger
  def log m
    puts m
  end
end

DRb.start_service "drbunix:///tmp/logger", Logger.new

Note the URI.

require 'drb'

DRb.start_service

logger = DRbObject.new_with_uri "drbunix:///tmp/logger"

logger.log "Woot!"

=)

Consultants, Startups, Students, and Rails

As I hang out at Rails user groups, chat with friends using Rails, and others at conferences and the like, I have seen a few groups of Rails users out there. Consultants Ah the consultants. If you have been to a show like No Fluff Just Stuff you would have noticed that last years season had more questions on Rails than Java (or so it felt, which was a little painful!).Many of the people up on the panel were able to answer the questions (or at least give their opinion) since they had actually used Rails. Many of them were talking about JSF and other Java web frameworks the year before, but had moved on for some of their projects. Why have these alpha geeks moved on? Positive view: Consultants often get to start new projects. They do not have the same baggage (baggage in this case == legacy code that runs and makes the business money!) that IT departments often have. If you have a million lines of code in Java, WebWork, Spring, Hibernate, would it make sense to jump over to Rails? You have the knowledge of those frameworks inside and out. You have helper libraries to make your life easier. You have nailed deployment. Does it make sense to change? The consultant moving on to client.next gets to start a new project from scratch and wants to play with the new toy. He has heard about the potential of Rails and wants to test out the hype. This has always happened, and is the reason that many have deployed Struts, WebWork, JSF, and Tapestry applications in a fairly short time period. For many of the current crop, they enjoyed the Rails experience, and they haven't jumped to another framework..... yet! Negative view: Consultants need new technology to master to sell themselves! The yet from above means that this year they gamble on Rails, but who knows what it will be next year. I personally think that Rails has legs, and the length of time that a consultant spends with a technology shows how changing that technology is. For example, I had used Spring on every Java project for the last 1.5+ years. Spring was useful enough to me that it made sense to use it on all of those projects. Therefore, major points for Spring. This can be said for JUnit (until TestNG comes in ;) and many other tools (even IntelliJ IDEA!). Startups Startups such as ODEO, of course 37 Signals, and the like have been strong users of Rails. They get the benefit of.... being startups! The green field is what they see, and their goal is to come up with technology a s fast as possible to start to make some $ somehow (or the business model may be to do a beta and get bought out by G/Y/MSFT). They do not have the luxury of legacy code (again: code that runs and makes $) but they do have the luxury of making a choice from scratch. Rails thrives on green field projects. Students About half of my local Rails group consists of students at the local college. The first presentation was from a student. Rails fits them nicely as it is a quick environment to whip out their projects. I remember using this same advantage back when I was at university. The class was using C++ to build a web app, and I hacked together something using Perl, and had far more time to enjoy the fun parts of college life. The head Java instructor is very much in the Rails camp. He talks about how hard it is to teach Java in comparison. There is so much to teach in public static void main(String args[]) and he has tried top down, bottom down, andevery other way (using BlueJ and ...). Of course... This isn't a definitive list and of course it doesn't mean that any other type isn't using Rails, but it was a little interesting.

Rails Recipes: Stuff you need

If you have used Rails for non-trivial applications, you have probably run into a lot of "Hmm, what would be the best way to do this?" moments. As soon as you tread off of the simple path, you need to think :) Rails Recipes has been a saviour for a few of the items that I care about. The Wiki doesn't cut it. A co-worker spent a few hours working on ideas from the Wiki. Then he cracked open Rails Recipes, found an exact solution, and it actually worked right away :) The book is in beta form now, and will shortly be finished. I am sure there will be several versions in the future as Chad finds more and more useful ideas.

Enterprisey Astronauts

Chris Petrilli writes what I think is an insightful post on getting things done in a software project. An underlying issue – though not really stated outright – is the whole Java/J2EE vs. Ruby/Rails debate that will never really have an end.

But I think his most salient points transcend language wars and hit on some key issues.

If you deliver an “enterprise solution” in 2 years, and I deliver what you degrade as a “non-enterprise solution” next week, whose solution do you think earns more money for the organization? Who do you think abates the costs faster? Most importantly, who do you think learns where the mistakes are and the real requirements exist.

It’s all about making software that works and is delivered on time. If I get that done, maybe I can have some money in the budget to scale it if I need to. But so much is really unknown, and to act as though you can actually forsee the future is sometimes a fools folly.

Not all problems are knowable. Until you accept this perspective, you will continually pursue the perfect to the deficit of the good.

Until then, I plan on testing first, taking small steps, and delivering something that works every couple of weeks.

Metaprogramming All Over!

So, Martin released a first showing at his wicked cool Atom binding library for ruby:

require 'atom'
require 'net/http'
require 'uri'

str = Net::HTTP::get(URI::parse('http://blog.ning.com/atom.xml'))
feed = Atom::Feed.new(str)

feed.entries.each { |entry|
  puts "'#{entry.title}' by #{entry.authors.first.name}"
}

I like this example more, though ;-)

Meanwhile, Obie apparently leaked ActiveMessagingM-Del a13g. Woot! It's good to see Stomp getting wider usage.

class HelloWorldProcessor < ActiveMessaging::Processor
 
 subscribes_to :hello_world
 publishes_to :hello_world
 
 def on_message(message)
   puts "received: " + message.body
   publish :message => "Hello world!"
 end
 
end

Cool stuff all around!

Atom 1.0 parser for Ruby

I've been looking for a Ruby library for parsing Atom 1.0. The only one I could find was FeedTools. Unfortunately, it's intended to be a universal feed-parsing library and not quite what I need.

As a result, I decided to build my own. The project is hosted at RubyForge and the source code is available under the MIT license.

It is still in its early stages, so don't expect much at this point. So far, it understands most atom:* elements and attributes but doesn't perform validation.

The interface is fairly simple. For example, you can do this:


require 'atom'
require 'net/http'
require 'uri'

str = Net::HTTP::get(URI::parse('http://blog.ning.com/atom.xml'))
feed = Atom::Feed.new(str)

feed.entries.each { |entry|
puts "'#{entry.title}' by #{entry.authors.first.name} on #{entry.published.strftime('%m/%d/%Y')}"
}


Give it a try, play with it. Comments are welcome!