Articles Feed

Authors

Categories

Ruby DSL Blocks

by: micah | May 20th, 2007 | 4 comments »

There’s a common pattern I’ve seen for developing DSLs (Domain Specific Language) in Ruby. It’s used in RSpec, the Statemachine Gem, and Unclebob’s Clean Code talk at RailsConf 2007. I haven’t seen a name for this pattern so I’ll call it the DSL Block Pattern.

RSpec

  1. describe "Bowling Game" do
  2. it "should score 0 on a gutter game" do
  3. game = Game.new
  4. 20.times { game.roll(0) }
  5. game.score.should eql(0)
  6. end
  7. end

Statemachine

  1. sm = Statemachine.build do
  2. trans :locked, :coin, :unlocked
  3. trans :locked, :pass, :locked
  4. trans :unlocked, :pass, :locked
  5. trans :unlocked, :coin, :unlocked
  6. end

Parser

  1. parser = Args.expect do
  2. boolean "l"
  3. number "p"
  4. string "d"
  5. end

Here’s the problem. You’ve got to write code for specific domain such as writing specifications (RSpec), defining a Statemachine, or defining command line arguments (Unclebob’s Clean Code talk). These domains have a contained and well defined terminology set. Often the cleanest, most elegant way to express this code is to create a DSL.

Before diving into the example, let me say that I like coffee as much as the next guy. But I feel lost when ever I go to a Starbucks. As you know, Starbucks has a it’s own language, DSL if you will, for ordering coffee. What follows is a DSL Block for ordering Starbucks coffee.

The general grammar for ordering coffee is: Size, Adjective (optional), Type of Coffee. This is by no means comprehensive but it’s sufficient for the example. So if you wanted to order a large coffee, for example, you would say, Grande Coffee. A small espresso: Short Americano. An extra large mixture of regular and decaffeinated coffee with some half and half: Venti Breve Half Caff.

Given the task to code these coffee orders, I’d like to be able to code it like this:

  1. Starbucks.order do
  2. grande.coffee
  3. short.americano
  4. venti.breve.half_caff
  5. end

Ok that looks good, but as you look closely, you’ll start to wonder about those methods, grande, short, and venti “Do they have to be defined on the Kernel?” you may ask. Defining them on the Kernel is a scary prospect. And that may convince you to clutter the syntax by passing an object into the block like this:

  1. Starbucks.order do |order|
  2. order.grande.coffee
  3. order.short.americano
  4. order.venti.breve.half_caff
  5. end

This would allow you to define the grande, short, and venti methods on the object passed into the block. Although you do need an object where grande, short, and venti will be defined, you don’t need to add an argument to the block. You’ll find code out there, such as Migrations, that uses this less optimal route. It’s not necessary. The trick to get rid of the argument is below:

  1. module Starbucks
  2. def self.order(&block)
  3. order = Order.new
  4. order.instance_eval(&block)
  5. return order.drinks
  6. end
  7. class Order
  8. attr_reader :drinks
  9. def initialize
  10. @drinks = []
  11. end
  12. def short
  13. @size = "small"
  14. return self
  15. end
  16. def grande
  17. @size = "large"
  18. return self
  19. end
  20. def venti
  21. @size = "extra large"
  22. return self
  23. end
  24. def coffee
  25. @drink = "coffee"
  26. build_drink
  27. end
  28. def half_caff
  29. @drink = "regular and decaffeinated coffee mixed together"
  30. build_drink
  31. end
  32. def americano
  33. @drink = "espresso"
  34. build_drink
  35. end
  36. def breve
  37. @adjective = "with half and half"
  38. return self
  39. end
  40. private
  41. def build_drink
  42. drink = "#{@size} cup of #{@drink}"
  43. drink << " #{@adjective}" if @adjective
  44. @drinks << drink
  45. @size = @drink = @adjective = nil
  46. end
  47. end
  48. end

You can see that the Order object is doing all the work. It’s got the responsibility of interpreting the DSL, so let’s call it the Interpreter Object. The Module::order method simply creates an instance of Order and calls istance_eval on it. This causes the block to execute using the binding of the Order instance. All of the methods on Order will be accessible to the block.

The Interpreter Object can do any number of things as it interprets the DSL. In this case it simply generates a translation for Starbucks newbies. But, the sky’s the limit really.

Show all the source code.

Lost in Portland, and Thinking

by: eric | May 20th, 2007 | 0 comments »

Did I say I was tired yesterday? That wasn’t tired, this is tired. I’m currently training for the marathon, and today I was scheduled for a 13 mile run. Sadly marathon training does not wait for RailsConf so I was up at five this morning, viewing the fair city of Portland. Say what you will about running, but it’s a great way to see a new city.

It’s also a great way to get lost. I used the great website at USA track and field where people post their runs in order to find a place to go and promptly wrote down a street name incorrectly. I ended up wandering around Portland’s lovely Willamette river, which fortunately provides a great landmark for a poor lost tourist. This led to running, and running, and running…and finally a Runners High.

Runners High is a phenomenon cause by your brain releasing endorphins, as a defense mechanism against the way your body feels. When I run my brain feels extremely active, and jumps from topic to topic making associations, breaking down problems, etc. It’s not unheard of for me to fix a bug I’d been struggling with while on a run, in a sudden burst of insight. Today, when I wasn’t wondering where the hell I was, I started thinking about quite a few things one of which is related to the new IDE’s being developed and the barrier to entry on Rails. Bear with me because this is a little out there, and I haven’t quite figured out how to articulate the concepts.

Right now Ruby and Rails are still strange and unusual things to most developers. When introduced to the language they want an IDE, and upon hearing there is no visual debugger or that they have to use Textmate, Eclipse or god-forbid emacs they turn away. They stick with Java or C++ and wizards and comfortable development, and will wait until they need to use Ruby.

Then one day their boss hears about Rails. They tell the developer, ‘you should be using this!’ and so he goes and he gets an IDE, real slick one with lot’s of bells and whistles. He goes to generate a model - only he doesn’t use script/generate. He uses a wizard, he sees he can add his methods and data members right there, so he does. Test? What test? Then he goes to the controller and has his controller manipulate the data all day, and develops a view in a WYSIWYG editor. He spends all day in the debugger, and his methods get bigger and bigger. Of course that’s no big deal, because he uses cold folding to hide their size. Then testers write Test::Unit code to test his code, and he’s done, because developers don’t test. Meanwhile we’ve all left Ruby because, “so much of the code is crap.” Haven’t we done this cycle before?

Chad Fowler described us as arrogant in the introduction, and clearly if you look above we are. I say we because nobody reading this had a problem with my rant, so you’re just as arrogant as I am. Caught you. We think we’re smarter than everybody else don’t we? Actually I don’t and most of you probably don’t either. I just know that there is a right and wrong way to do things, and so does Rails. That’s why it’s so opinionated. An attendee, Jack Canty, put it better than I can by saying “Rails is opinionated, and this takes away those opinions.”

So how do we fix it? Well let me steal some of Micah’s DSL thunder by using an analogy I thought of while running. It’s morning, and you want a coffee. So you go to a certain ubiquitous chain and say, “I’d like a big coffee please,” only said ubiquitous chain doesn’t have big. They have Tall, Grande and Venti. If you do this there will be a noticeable pause while the guy behind the counter takes a second to think about what you just said. He’s not a moron, take off his green smock and he knows what a big coffee is, but he’s got to translate it. What’s big - is it grande? Venti? That’s because you’re not speaking his language. You eventually do get your coffee, because he can figure out what you mean, but it takes just a little bit longer. While you wait in line you hear other people say, “I’ll have a grande half-caf no-foam latte,” and the guy behind the counter immediately repeats it. Eventually you learn the language they speak, and you get your coffee just a little bit faster. You’ve adapted to the idioms of the coffee shop, and you’ve been rewarded for it.

So what the heck does that have to do with code? I kind of gave it away by using the phrase idioms, didn’t I? Well let’s say you’re a C++ programmer and you start on Ruby. You don’t write Unit Tests, which despite what we sometimes think most developers do NOT do, and you sit down to iterate through an array. So you write:

  1. index = 0
  2. while index < array.length do
  3. puts index
  4. end

Much like ordering a big coffee at the Green Angel, that will work, but it’s completely incorrect. It’s not speaking Ruby even if it’s in the Ruby language, and unfortunately I believe that if we do not work hard to enforce and teach the best practices of the Rails and Ruby ways, we will witness this kind of thing propagated. I’ve done the C++ without test thing and lived in the debugger, and it drove me nuts. Unfortunately many developers don’t think anything is wrong with that. If they did they’d be here already. That’s what programming is to them. The good news is that we are the early adopters, and when they come around they will adapt to our rules but only if we make them. Remember what I said about the pause at Starbucks? Well the code above won’t pause. Ruby will execute it just as nicely as anything else, and maybe it shouldn’t, I’m not sure. What I do know is that the IDE’s now being created should not be catering to the developer that wants to do the things the way they’ve always done them. Instead of opening ‘choices’ up and making things ‘easier’, our IDEs should enforce the opinions of Rails. You see the late adopters aren’t lazy, or stupid, they’re just being rewarded improperly. If it’s easy to make a model without tests, they’ll make one, even if they are hurt by it later on. They’ll even stay until 1 AM to debug it, and their boss will think ‘gosh this Rails thing isn’t so great after all, thank God my programmers are so dedicated.’ Fortunately we control this. There’s a reason the IDE makers are here, and that’s because they know that late adopters ask the early adopters what they use. Tell the IDE makers that you want an IDE that does not allow bad habits. I want an IDE that is integrated with subversion, but I also want it to yell at me if I have untested code, and run ZenTest. If it gives me a wizard the first question it should ask is “what is the name of the test?.” Instead of code folding to hide bad code, it should identify refactoring opportunities. Finally, I’m not sure I even want a debugger. Debugging sucks, and it’s a crutch. I can test behavior rather than step through it and look at it. I realize people do want debuggers, I just worry for the day when Rails code can’t be properly understood without stepping through it.

Tell the IDE developers that you want an IDE that helps create good code, not easy code. If they don’t do that, then when developers ask you what you use say emacs. If the developer still turns away, we probably aren’t losing much.

Day Three at Rails

by: eric | May 19th, 2007 | 0 comments »

Well it’s day three and I’m exhausted. As you may have noticed I posted at 3 AM last night, and getting up at 8 AM wasn’t foremost on my list of “things to do today.” I did it anyway and I’m enjoying a talk on Rails helpers at the moment. While my posting last night was late I actually wrote it much earlier in the day so naturally some things happened after I wrote the post, and not all of them involved beverages that should not be consumed by those that are under 21.

Last night’s keynotes said a lot about the Rails community, and almost nothing about Rails ironically enough. The first was Avi Bryant who challenged us to make Ruby as good as Smalltalk. Avi is clearly a bright guy and an energetic speaker, and his opinions are essentially the opposite of the ‘Rails’. While I certainly wouldn’t say I agree with everything he said, the challenge to make Ruby and thereby Rails as fast as Smalltalk is one that we as a community should certainly consider.

The other was Ze Frank. He probably needs no introduction to much of the web, but he didn’t say the words “Ruby” or “Rails” once. He did nearly make us wet ourselves with laughter. I’m going to have to spend some time at www.zefrank.com.

I mentioned that they said a lot about us, as a community, and to illustrate this I want you to think of an ASP conference, put on by Microsoft. Do you think they’d invite somebody from the Rails community to come in and tell them their framework bites? No, of course not. Large corporate-backed frameworks do not accept challenges. They spend their time trying to sell you things to debug their already perfect technology. We, as a group, invite people to challenge us because it requires us to think about and defend our own position. If we’re wrong, we admit we’re wrong. If we’re right than that position must be defensible. We all came from different backgrounds, and came to Rails because we continually challenged our assumptions. Let’s continue to do so.

What Ze Frank as a keynote said about us is simple, we’re fun. I’m sure you’ve been to a big corporate event where a few suits tell some in-jokes or make a skit that’s supposed to be funny, but it’s not because nobody can actually say anything that might upset the big honchos. This is a group that has no problem with keynote speakers dropping an F-bomb, and I haven’t seen a single tie. Man it’s refreshing.

Had a lot of fun meeting a bunch of you last night. Looking forward to that today too.

Day Two at RailsConf

by: eric | May 18th, 2007 | 0 comments »

Hello again from RailsConf. I’ve gone through five presentations. I don’t have any enormous revelations, and no good pictures yet, but I do have some observations. I’ll do this Larry King style.

…Uncle Bob and Jim Weirich are great public speakers. I’ve seen Uncle Bob do most of the CleanCode talk before, and I’ll go again at Agile 2007…Went for a run yesterday, Portland is quite pretty…Everyone I’ve met here has been unfailingly interesting, with nobody trying to ‘network’ just meet people like normal human beings…Spider Man 3 was mildly disappointing …If you get a chance to download the slides from Spam I Have Known, do so. Hugely entertaining…If you add videos to your presentation it’s fun, but not as much fun as genuine enthusiasm about your topic.

Okay I can’t do that for very long, how did Larry do it for 20 years? The highlights from today would have to be three things:

http://pragmaticstudio.com/dontate

Charity good, give some.

Hello from RailsConf

by: eric | May 17th, 2007 | 0 comments »

Of course if you’re reading this you’re probably at RailsConf so hello from here. I’m right next to you actually, to your right. Yep that’s me, hi. There’s a coffee stain on your shirt.

I plan to blog as much as possible from here, and hopefully bring a little of the experience back home to those of you that couldn’t make it. I just came out of my first tutorial, “your first day on JRuby on Rails”, and found it quite enjoyable. Admittedly we’ve been playing with JRuby for a little while know here at 8th Light, so much of it was stuff I already knew, but it was great to talk to Charles Nutter and get some of my questions answered directly. Some highlights:

In other news the Portland Conference center is really nice, with pretty good wireless network connections. One thing I’d ask for would be more tables outside the ballrooms, as there are only about 6 and a lot of people have their Macs open on the floor. Speaking of which I’ve never been anywhere that the Mac so dominated. 80-90% of the people here are carrying MacBooks.

Now if you’ll excuse me, I’m hungry.

Chirb Statemachine Talk

by: micah | May 8th, 2007 | 1 comments »

Last night I presented the Ruby Statemachine Gem to the Chicago Ruby Users Group (Chirb) Below are links to download the slides and coding example used.

Finite State Machines and the Statemachine Ruby Gem (Slides):

Statemachine Exercises:

Test Driving Module Methods

by: eric | May 3rd, 2007 | 0 comments »

Recently I had the pleasure and frustration of working the net-sftp gem for Ruby. Pleasure because it’s a well written library, with an easy to use syntax that looks something like this:

  1. Net::SFTP.start( 'localhost', :registry_options => { :logs => { :levels => { "sftp.*" => :debug } } }) do |sftp|
  2. sftp.put_file "test.data", "temp/blah.data"
  3. puts "getting remote file to local location..."
  4. sftp.get_file "temp/blah.data", "new.data"
  5. end

The above is just a shortened version from one of the examples in the GEM itself. It’s simple to use and easy to read. Having written similar code in C++, for Windows no less, I can really appreciate how quickly this can get an FTP application off the ground. The frustration came when I went to test drive this guy. Net::SFTP.start is a module method, not a class member. I can’t stub it in the traditional way using the RSpec stub! command or use should_receive. On top of that it passes back a block, which needs to be tested to make sure it’s being called correctly. After a few shots at mocking it out Paul and I test drove it with an actual FTP server. In the short term that was necessary anyway, as a hazard of frequent mocking can be that you are only testing how well you read the API. You see that when the tests pass and the first shot at actually running the code blows up. In the long term the customer asked for a few new small features, changing directories and what not, and I really want to get this under long-term test to do that. So how do we do it?

Well we could stop using the .start command entirely. We could pass in a mock Net::SFTP object and test it, making sure to close it manually. Unfortunately that eliminates the clean code we see above, and if possible I’d like to keep it. The solution is to intercept the start method.

The first thing I do is monkey patch the code like so:

  1. module Net ; module SFTP
  2. def start( *args, &block )
  3. end
  4. module_function :start
  5. end ; end
  6. context "My Context" do

I’ve put it before the context, to make sure it’s redefined before the object I’m testing is created. Next we need to expose our mock objects in the context to the monkey patch. This isn’t done with traditional writers and readers, because that would require finding the specific specification running for each time through the monkey patched start. Instead we make our mocks class members in the setup method, and create a class method in the context to retrieve the variables. The class method looks like this:

  1. def Spec.get_mock_ftp_objects
  2. return @@mock_starter, @@mock_session
  3. end

This reveals a bit of the underworkings of RSpec. Each block in the context block is turned into a class method using class_eval, as part of the Spec object. Making the method static allowed this new monkey patched method:

  1. module Net
  2. module SFTP
  3. def start( *args, &block )
  4. @mock_starter, @mock_session = Spec.get_mock_ftp_objects
  5. @mock_starter.start args[0], args[1], args[2]
  6. yield @mock_session
  7. end
  8. module_function :start
  9. end
  10. end

The code gets the two mock objects via our new method. Isn’t it grand how Ruby lets you return multiple objects? The call to start allows me to make sure that the arguments passed to the real start are correct. The real interesting call is the yield. By yielding the mock back to the object it will replace the sftp in the original code. Now I can test it! In fact I’ve already realized a bug in my code (in stopping) just by the process of doing this. I love it when a plan comes together. The final code example is here, I ended up extracting out a new class, so the names have changed. This one tests both the starting object and the block yielded:

  1. require 'net/sftp'
  2. require File.expand_path(File.dirname(__FILE__) + "/ftp_client")
  3. module Net
  4. module SFTP
  5. def start( *args, &block )
  6. @mock_starter, @mock_session = Spec.get_mock_ftp_objects
  7. @mock_starter.start args[0], args[1], args[2]
  8. yield @mock_session
  9. end
  10. module_function :start
  11. end
  12. end
  13. context "FTP Client" do
  14. setup do
  15. @client = FtpClient.new("test_server", "user", "password", "directory")
  16. @@mock_starter = mock('mock_starter')
  17. @@mock_session = mock('mock_session')
  18. @@mock_session.stub!(:opendir)
  19. @@mock_session.stub!(:close_handle)
  20. end
  21. specify "makes ftp connection, to proper place" do
  22. @@mock_starter.should_receive(:start).with("test_server", "user", "password")
  23. @client.read_from_server
  24. end
  25. specify "changes to ftp_directory, better close that handle" do
  26. @@mock_starter.should_receive(:start).with("test_server", "user", "password")
  27. @@mock_session.should_receive(:opendir).with("directory").and_return("fake handle")
  28. @@mock_session.should_receive(:close_handle).with("fake handle")
  29. @client.read_from_server
  30. end
  31. def Spec.get_mock_ftp_objects
  32. return @@mock_starter, @@mock_session
  33. end
  34. end

Maybe this isn’t the best way to do this, but I like it. I’m looking forward to comments.

#######