![]() |
Articles Feed |
Categories
Archives
- July 2010 (5)
- June 2010 (4)
- April 2010 (3)
- March 2010 (2)
- February 2010 (2)
- January 2010 (1)
- December 2009 (1)
- October 2009 (2)
- September 2009 (2)
- August 2009 (1)
- July 2009 (5)
- June 2009 (2)
- May 2009 (2)
- April 2009 (8)
- March 2009 (7)
- January 2009 (2)
- December 2008 (3)
- November 2008 (5)
- October 2008 (4)
- September 2008 (6)
- August 2008 (4)
- July 2008 (5)
- June 2008 (5)
- May 2008 (4)
- April 2008 (2)
- February 2008 (4)
- January 2008 (2)
- December 2007 (2)
- November 2007 (2)
- October 2007 (2)
- September 2007 (1)
- August 2007 (3)
- July 2007 (1)
- June 2007 (4)
- May 2007 (7)
- April 2007 (2)
- February 2007 (3)
- January 2007 (3)
- November 2006 (3)
- October 2006 (3)
- September 2006 (17)
- November 2004 (1)
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
- describe "Bowling Game" do
- it "should score 0 on a gutter game" do
- game = Game.new
- 20.times { game.roll(0) }
- game.score.should eql(0)
- end
- end
Statemachine
- sm = Statemachine.build do
- trans :locked, :coin, :unlocked
- trans :locked, :pass, :locked
- trans :unlocked, :pass, :locked
- trans :unlocked, :coin, :unlocked
- end
Parser
- parser = Args.expect do
- boolean "l"
- number "p"
- string "d"
- 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:
- Starbucks.order do
- grande.coffee
- short.americano
- venti.breve.half_caff
- 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:
- Starbucks.order do |order|
- order.grande.coffee
- order.short.americano
- order.venti.breve.half_caff
- 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:
- module Starbucks
- def self.order(&block)
- order = Order.new
- order.instance_eval(&block)
- return order.drinks
- end
- class Order
- attr_reader :drinks
- def initialize
- @drinks = []
- end
- def short
- @size = "small"
- return self
- end
- def grande
- @size = "large"
- return self
- end
- def venti
- @size = "extra large"
- return self
- end
- def coffee
- @drink = "coffee"
- build_drink
- end
- def half_caff
- @drink = "regular and decaffeinated coffee mixed together"
- build_drink
- end
- def americano
- @drink = "espresso"
- build_drink
- end
- def breve
- @adjective = "with half and half"
- return self
- end
- private
- def build_drink
- drink = "#{@size} cup of #{@drink}"
- drink << " #{@adjective}" if @adjective
- @drinks << drink
- @size = @drink = @adjective = nil
- end
- end
- 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.
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:
- index = 0
- while index < array.length do
- puts index
- 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:
- Keynote. The tone here is very different from your typical stuffy conference, and it’s great. Chad Fowler playing the ukulele, DHH referring to ‘unicorns’, I doubt you see these at a Windows Vista conference. Well you probably do, but it’s forced and trite. There’s a genuine enthusiasm here, because what we have here are 1600 developers who are all passionate craftsman who would do this for free. To any potential customers: That last part was a joke. We like to be paid. Speaking of paying, Dave Thomas gave a talk (which Gilberto attended) on Rails for charity, and that charity is still open at:
http://pragmaticstudio.com/dontate
Charity good, give some.
Clean Code. Uncle Bob is a “friend of the program” as they say in college and rather closely related to my boss, so my opinion is biased admittedly, but his speech today was packed and as always well received. If you want to see what he talked about hold the apple or ctrl key and click http://www.objectmentor.com. That will open the site in a new window, so you don’t go away.
Spam I have known. The presentations I saw the rest of the day were up/and/down. Nothing was bad exactly, and I know these people put a lot of effort into them so I won’t name names, but there wasn’t much memorable. I was beginning to think I’d have to make up something to blog today, until seeing Jim Weirich’s presentation on spam. Ruse has a really nice algorithm for detecting spam which has quite a few features but it centers around an idea so obvious you’ll wish you’d thought of it, the Tarpit. Spammers routinely defeat sites with one thing - feedback! They attack a site trying different things until they figure out why they were rejected. A Tarpit takes spam and doesn’t give them an error message, it puts it in the Tarpit. They think that their spam was successful, so they don’t change the spam. I think I’m going to grab it and try it for my personal wiki, which has never been replaced since I left the evils of corporate America.
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:
Deployment with JRuby on Rails is a snap, assuming you’ve already got some sort of existing Java Enterprise setup. Using the GoldSpike plug-in creates a deployable WAR file. While I don’t see a reason for doing this on any current projects, for those of you out there using servers that might be resistant to setting up Apache/Mongrel this was a nice alternative. Install plug-in, run rake task and you have a deployable WAR file.
NetBeans for Ruby fairly stole the show. I’m a big TextMate fan, especially because I no longer debug with anything other than puts statements, so I don’t need a fully fledged IDE for Rails. That said the features they demonstrated for Ruby were great, including code completion, jumping to methods, and my personal favorite clicking a method call and jumping to it’s source code even if it was in a Gem. I’ve done a lot of hunting in Gems and this would be enormously helpful. Rudimentary refactoring support is expected as well, including support for rename method. I asked how this would work in Ruby and the plan is to show places where the rename could take place and leave the ultimate decision up to the user. I didn’t get to see this in action, and I’d like to because I fail to see how that is better than just using find. I’d be hard-pressed to switch from TextMate, I’m writing this post in it, but I could be convinced if NetBeans could be the “editor you live in.”
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:
- Net::SFTP.start( 'localhost', :registry_options => { :logs => { :levels => { "sftp.*" => :debug } } }) do |sftp|
- sftp.put_file "test.data", "temp/blah.data"
- puts "getting remote file to local location..."
- sftp.get_file "temp/blah.data", "new.data"
- 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:
- module Net ; module SFTP
- def start( *args, &block )
- end
- module_function :start
- end ; end
- 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:
- def Spec.get_mock_ftp_objects
- return @@mock_starter, @@mock_session
- 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:
- module Net
- module SFTP
- def start( *args, &block )
- @mock_starter, @mock_session = Spec.get_mock_ftp_objects
- @mock_starter.start args[0], args[1], args[2]
- yield @mock_session
- end
- module_function :start
- end
- 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:
- require 'net/sftp'
- require File.expand_path(File.dirname(__FILE__) + "/ftp_client")
- module Net
- module SFTP
- def start( *args, &block )
- @mock_starter, @mock_session = Spec.get_mock_ftp_objects
- @mock_starter.start args[0], args[1], args[2]
- yield @mock_session
- end
- module_function :start
- end
- end
- context "FTP Client" do
- setup do
- @client = FtpClient.new("test_server", "user", "password", "directory")
- @@mock_starter = mock('mock_starter')
- @@mock_session = mock('mock_session')
- @@mock_session.stub!(:opendir)
- @@mock_session.stub!(:close_handle)
- end
- specify "makes ftp connection, to proper place" do
- @@mock_starter.should_receive(:start).with("test_server", "user", "password")
- @client.read_from_server
- end
- specify "changes to ftp_directory, better close that handle" do
- @@mock_starter.should_receive(:start).with("test_server", "user", "password")
- @@mock_session.should_receive(:opendir).with("directory").and_return("fake handle")
- @@mock_session.should_receive(:close_handle).with("fake handle")
- @client.read_from_server
- end
- def Spec.get_mock_ftp_objects
- return @@mock_starter, @@mock_session
- end
- end
Maybe this isn’t the best way to do this, but I like it. I’m looking forward to comments.
#