<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>O&apos;Reilly Ruby</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/" />
    <link rel="self" type="application/atom+xml" href="http://www.oreillynet.com/ruby/blog/atom.xml" />
   <id>tag:www.oreillynet.com,2008:/ruby/blog//3</id>
    <updated>2008-05-12T09:46:24Z</updated>
    <subtitle>The O&apos;Reilly Ruby group weblog covers all the latest in the world of the Ruby programming language with some of the languages leading programmers.  From personalities, articles, developments, books, conferences, and exciting applications, O&apos;Reilly Ruby is the site you&apos;ll want to watch to be ahead of the Ruby curve.</subtitle>    <generator uri="http://www.sixapart.com/movabletype/">Movable Type 3.21</generator>
 
<entry>
    <title>Is QWERTY harming language design?</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/2008/05/is_qwerty_harming_language_des.html" />
    <id>tag:www.oreillynet.com,2008:/ruby/blog//3.23709</id>
    
    <published>2008-05-12T05:13:47Z</published>
    <updated>2008-05-12T09:46:24Z</updated>
    
    <summary>One of the rather unique features of the Fortress1 programming language is that it has builtin support for Unicode operators. For example, instead of using &quot;==&quot; you would use U+2261. After reading the spec, and a recent thread on ruby-core,...</summary>
    <author>
        <name>Daniel Berger</name>
            </author>
            <category term="Opinion" />
        <content type="html">
&lt;p&gt;One of the rather unique features of the Fortress&lt;sup&gt;1&lt;/sup&gt; programming language is that it has builtin support for Unicode operators. For example, instead of using &amp;#8220;==&amp;#8221; you would use U+2261. After reading the spec, and a recent thread on ruby-core, I&amp;#8217;ve been wondering something . Are language designers too limited in their decisions about syntax because of the limitations of the QWERTY&lt;sup&gt;2&lt;/sup&gt; keyboard?&lt;/p&gt;
&lt;p&gt;Larry Wall&amp;#8217;s first rule of computer language design is, &amp;#8220;Everybody wants the colon&amp;#8221;&lt;sup&gt;3&lt;/sup&gt;. Maybe the problem is that we just don&amp;#8217;t have enough symbols on our darned keyboards. The result is that we&amp;#8217;re left fighting over the scraps that QWERTY gives us, e.g. the colon. My opinion is that a limited number of usable characters limits our thinking and our expressiveness. Perhaps it&amp;#8217;s time for computer programmers to consider whether or not we should have special keyboards. I mean, plumbers and electricians and carpenters have special tools that your average homeowner doesn&amp;#8217;t own, right? Why should computer programmers use the same tools (i.e. keyboard) that your average computer owner uses?&lt;/p&gt;
&lt;p&gt;Maybe it&amp;#8217;s time that we accepted the fact that programmers need a different keyboard from your average, non-programming computer user. Another row of keys above the current QWERTY keyboard, with two characters per key, gives us another 30+ code points. Or even just a redefinition of the current function keys would suffice. With these at our disposal, language designers can then write grammars &amp;#038; parsers that actually use these characters.&lt;/p&gt;
&lt;p&gt;Are there issues? Sure there are. First and foremost is that it will take a new keyboard layout that programmers can agree on.&lt;sup&gt;4&lt;/sup&gt; Second, there are differences between typical desktop and laptop keyboard layouts. Third, vendor specific keyboard layouts with special, predefined keys will have to be considered.&lt;sup&gt;5&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;But none of those issues changes my opinion that we&amp;#8217;re being hampered by QWERTY.&lt;sup&gt;6&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;1&lt;/sup&gt;http://research.sun.com/projects/plrg/fortress0618.pdf&lt;br /&gt;
&lt;sup&gt;2&lt;/sup&gt;http://en.wikipedia.org/wiki/QWERTY&lt;br /&gt;
&lt;sup&gt;3&lt;/sup&gt;http://www.perl.com/pub/a/2001/04/02/wall.html&lt;br /&gt;
&lt;sup&gt;4&lt;/sup&gt;I thought the W3C might have something to say on this topic but I was unable to find anything. Links welcome.&lt;br /&gt;
&lt;sup&gt;5&lt;/sup&gt;Mainly Microsoft and Logitech I imagine, but probably others.&lt;br /&gt;
&lt;sup&gt;6&lt;/sup&gt;Yes, this is a reworked entry from an older, personal blog post that some of you may recognize.
&lt;/p&gt;
    </content>
</entry>
<entry>
    <title>Ruby Mendicant: Hadean Prawn</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/2008/05/ruby_mendicant_hadean_prawn.html" />
    <id>tag:www.oreillynet.com,2008:/ruby/blog//3.23636</id>
    
    <published>2008-05-03T18:34:05Z</published>
    <updated>2008-05-03T18:47:29Z</updated>
    
    <summary>Back in March, I announced the Ruby Mendicant project after several readers of this blog encouraged me to pursue the idea. For those who didn&apos;t see the follow up details elsewhere, here&apos;s the readers digest version: Thanks to 70 donors,...</summary>
    <author>
        <name>Gregory Brown</name>
            </author>
            <category term="Technical" />
        <content type="html">
&lt;p&gt;Back in March, I announced the &lt;a href="http://www.oreillynet.com/ruby/blog/2008/03/the_ruby_mendicant_project.html"&gt;Ruby Mendicant&lt;/a&gt; project after several readers of this blog encouraged me to pursue the idea.  For those who didn&amp;#8217;t see the follow up details elsewhere, here&amp;#8217;s the readers digest version:&lt;/p&gt;
&lt;p&gt;Thanks to 70 donors, and donation matching from &lt;a href="http://rubycentral.org/"&gt;Ruby Central, Inc.&lt;/a&gt; and &lt;a href="http://mtnwestruby.org/"&gt;MountainWest Ruby LLC&lt;/a&gt;, I am now able to take 22 weeks off from my commercial work to focus on open source development in Ruby.  I had a number of project ideas, but the general consensus is that my time would be best spent building a fast, sleek, and simple PDF library for Ruby.  I&amp;#8217;ve decided to call this library Prawn, for the time being. &lt;/p&gt;
&lt;p&gt;Since I have just reached my first checkpoint on the project, I figured I&amp;#8217;d give folks an update on where things are.   &lt;/p&gt;
&lt;p&gt;Development on the project officially began on April 15th, but admittedly, it has been a slow ramp-up from there.  I decided to give myself a few days to acquaint myself with the massive 1310 page specification Adobe provides for PDF.  However, within a week I was able to put together a rough outline of my &lt;a href="http://prawn.lighthouseapp.com/projects/9398/development-plans"&gt;development plans&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m happy to say I&amp;#8217;m already getting a lot of help.  The good folks at &lt;a href="http://activereload.net/"&gt;Active Reload&lt;/a&gt; hooked me up with a free Lighthouse instance for Prawn, which has been a dream to work with so far.  Beyond infrastructure, I&amp;#8217;ve also been getting other help.   I ran a few of my initial design ideas by David Black, and have been working actively on high level design with James Edward Gray II.  If I want to claim that Prawn will be a clean and beautiful library to work with, I figured I&amp;#8217;d need to get the insight of some Ruby masters.&lt;/p&gt;
&lt;p&gt;Implementation-wise, it&amp;#8217;s unrealistic to expect contributions this early in the game.  Nevertheless, I&amp;#8217;ve been receiving a &lt;b&gt;ton&lt;/b&gt; of help from James Healy, &lt;a href="http://rubyreports.org"&gt;Ruport&lt;/a&gt;&amp;#8217;s resident PDF expert and maintainer of &lt;a href="http://software.pmade.com/pdfreader"&gt;PDF::Reader&lt;/a&gt;.  He even prototyped line drawing for me, which was a great way to get started on Prawn.&lt;/p&gt;
&lt;p&gt;So after a couple weeks of development, what can Prawn do?  The answer is best shown by a picture:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://stonecode.org/hexagon.png"/&gt;&lt;/p&gt;
&lt;p&gt;The code to generate this hexagon looks like this:&lt;/p&gt;
&lt;pre&gt;
pdf = Prawn::Document.new
pdf.fill_color "ff0000"
pdf.fill_polygon [100, 250], [200, 300], [300, 250],
                 [300, 150], [200, 100], [100, 150]
pdf.render_file "hexagon.pdf"
&lt;/pre&gt;
&lt;p&gt;Though it&amp;#8217;&amp;#8217;s not terribly exciting to look at, Prawn now has all the primitives you&amp;#8217;ll need to do basic drawings.  It supports Bezier curves, lines, circles, and polygons.  It has full color support, though you&amp;#8217;ll find it to be a whole lot more barebones than &lt;a href="http://rubyforge.org/projects/ruby-pdf"&gt;PDF::Writer&lt;/a&gt; (it only supports HTML/CSS formatted RGB color strings e.g.&amp;#8221;f0f2cc&amp;#8221;). I&amp;#8217;ve used PDF::Writer&amp;#8217;s page geometries to allow you to set the page sizes to a huge amount of different formats, from US Letter to A4 and god-knows-what-else.  Banded together, these features make up Hadean Prawn, the first checkpoint on the geologic timescale of the project.&lt;/p&gt;
&lt;p&gt;Prawn is still missing a lot of higher level graphical functions, implementing only a tiny fraction of what you might find in other libraries.  It is intentionally dumb about a lot of things, and I assume I&amp;#8217;ll need to circle back and extend functionality substantially down the line.  Right now the focus is on being tiny, and keeping things as simple as possible.&lt;/p&gt;
&lt;p&gt;There are already several examples in the source to get you started, if you&amp;#8217;re the adventurous type.  There is also a full spec suite, which I plan to make as comprehensive as possible to make Prawn easily hackable and a bit more stable than the current PDF libraries available in Ruby.&lt;/p&gt;
&lt;p&gt;In the coming weeks, I&amp;#8217;ll be working on the next checkpoint, which is Archean Prawn.  This will be approached in the same way Hadean Prawn was, but with a focus on text primitives rather than graphics.  I have no idea if I will focus on internationalization just yet, but it is definitely an important goal of Prawn.  I will do some reading about this and figure out the details, and let folks know what I find out.&lt;/p&gt;
&lt;p&gt;If you want to get involved with Prawn at this point, my best suggestion is to go &lt;a href="http://github.com/sandal/prawn"&gt;track the project on github&lt;/a&gt;.  You can also keep an eye on &lt;a href="http://prawn.lighthouseapp.com/projects/9398-prawn/overview"&gt;Prawn&amp;#8217;s Lighthouse&lt;/a&gt; for updates.  I&amp;#8217;ve been using the wiki and tracker actively, so it&amp;#8217;s worth keeping an eye on.  Finally, you can come look for me (&amp;#60;sandal&amp;#62;) in #ruport or #prawn on Freenode if you&amp;#8217;d like to chat.&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re interested more in the meta-aspects of things, i.e. the Ruby Mendicant project itself, please come join our &lt;a href="http://groups.google.com/group/rubymendicant"&gt;google group&lt;/a&gt; and share your thoughts.  Because this an effort funded by the community, for the community, I&amp;#8217;d like the give everyone the opportunity to voice their thoughts and opinions.&lt;/p&gt;
&lt;p&gt;I plan on doing a status update like the one you&amp;#8217;ve just read once every couple weeks or so, but in the mean time, if you&amp;#8217;d like to get involved, please do so!&lt;/p&gt;
&lt;p&gt;See you in a couple weeks when Prawn has basic text operations and maybe more.
&lt;/p&gt;
    </content>
</entry>
<entry>
    <title>A Ruby WTF</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/2008/04/a_ruby_wtf.html" />
    <id>tag:www.oreillynet.com,2008:/ruby/blog//3.23592</id>
    
    <published>2008-04-28T16:54:45Z</published>
    <updated>2008-04-28T18:37:07Z</updated>
    
    <summary>While working on Prawn, I ran into this (not-so) fun little gotcha: &gt;&gt; 1.to_sym =&gt; nil &gt;&gt; 101241.to_sym =&gt; nil Anyone cool enough to tell me what this feature is all about? Update: I guess it isn&apos;t totally clear what...</summary>
    <author>
        <name>Gregory Brown</name>
            </author>
            <category term="Technical" />
        <content type="html">
&lt;p&gt;While working on &lt;a href="http://prawn.lighthouseapp.com/projects/9398/home"&gt;Prawn&lt;/a&gt;, I ran into this (not-so) fun little gotcha:&lt;/p&gt;
&lt;pre&gt;
&gt;&gt; 1.to_sym
=&gt; nil
&gt;&gt; 101241.to_sym
=&gt; nil
&lt;/pre&gt;
&lt;p&gt;Anyone cool enough to tell me what this feature is all about?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;I guess it isn&amp;#8217;t totally clear what I was asking here.  I&amp;#8217;m not actually trying to convert integers to Symbols. In fact, my specs were failing because I expected some_number.respond_to?(:to_sym) to be false!&lt;/p&gt;
&lt;p&gt;As it turns out, Fixnum#to_sym does have a purpose, but it is quite different than something like String#to_sym.&lt;/p&gt;
&lt;pre&gt;
&gt;&gt; :foo.to_i
=&gt; 14369
&gt;&gt; 14369.to_sym
=&gt; :foo
&lt;/pre&gt;
&lt;p&gt;I knew about the existence of &lt;tt&gt;#id2name&lt;/tt&gt; and it didn&amp;#8217;t surprise me much, given the way Symbol objects are implemented.   Still, for the folks who keep reminding me about what Symbol objects are, please save your comments because that&amp;#8217;s not the point here.&lt;/p&gt;
&lt;p&gt;The point is that when I see &amp;#8220;foo&amp;#8221;, I can easily say.  Oh&amp;#8230; &amp;#8220;foo&amp;#8221;.to_sym will give me :foo.&lt;br /&gt;
When I see [16393, 16401, 16409], I don&amp;#8217;t think &amp;#8220;Gee&amp;#8230; that must be: [:cat, :monkey, :tomato], I just need to map it with to_sym&amp;#8221;.  &lt;/p&gt;
&lt;p&gt;So what this boils down to is an API clarity thing.   Even if a Symbol is closely bound to an integer implementation-wise, I think it&amp;#8217;s a bit of a flaw to assume that to be important conceptually.  Among our readers, has anyone used Fixnum#to_sym in real code?  I&amp;#8217;d be very interested in seeing a common use case for this feature.   If there isn&amp;#8217;t one, maybe a less ambiguous name, such as &lt;tt&gt;id2sym&lt;/tt&gt; might be more appropriate.&lt;/p&gt;
&lt;p&gt;What&amp;#8217;s more, even with the existing name, it&amp;#8217;d be nice if some_number_that_has_no_symbol.to_sym would blow up with an error rather than return nil.   &lt;/p&gt;
&lt;p&gt;Sorry for the earlier confusion, hopefully this update clarifies that I was talking about a design peculiarity., and not some burning desire to get myself back a :1.&lt;/p&gt;
    </content>
</entry>
<entry>
    <title>Help Lessig Tag Congress</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/2008/04/help_lessig_tag_congress_1.html" />
    <id>tag:www.oreillynet.com,2008:/ruby/blog//3.23585</id>
    
    <published>2008-04-27T15:39:37Z</published>
    <updated>2008-04-29T21:44:39Z</updated>
    
    <summary>Sorry, it isn&apos;t entirely Ruby related... it is Python Django to be specific, but it is a message aimed at you (&quot;the wiki-worker types&quot;) from Lawrence Lessig recruiting people to tag members of Congress at Change-Congress.org: Today we&apos;re launching the...</summary>
    <author>
        <name>Tim O&apos;Brien</name>
            </author>
            <category term="News" />
        <content type="html">
&lt;p&gt;Sorry, it isn&amp;#8217;t entirely Ruby related&amp;#8230; it is Python Django to be specific, but it is a message aimed at you (&amp;#8221;the wiki-worker types&amp;#8221;) from Lawrence Lessig recruiting people to tag members of Congress at &lt;a href="http://change-congress.org"&gt;Change-Congress.org&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;
Today we&amp;#8217;re launching the second stage of our project. We&amp;#8217;re asking wiki-worker-types (and that includes you) to help us tag all candidates and Members of Congress, by tracking for each whether they support the planks of reform in the &lt;a href="http://change-congress.org/"&gt;Change Congress&lt;/a&gt; movement or not. We&amp;#8217;ve built a set of tools that you can use to document &amp;#8212; for each plank of reform &amp;#8212; whether a candidate supports that plank or not. After that information is verified by a volunteer administrator, we&amp;#8217;ll add it to a map of reform that we&amp;#8217;re building. After we&amp;#8217;re done, we&amp;#8217;ll have a picture of the level of support for fundamental reform of Congress. And with that map, we&amp;#8217;ll launch stage 3 of our project &amp;#8212; raising money to support candidates who support reform
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;b&gt;Django has the edge in the civic-Web20-computing space&lt;/b&gt; as Holovaty&amp;#8217;s Django was always more focused on the &lt;a href="http://chicago.everyblock.com/"&gt;public square&lt;/a&gt; from the beginning.   You would think Rails would be a no brainer for this, but in my brief encounter with the world of political web sites, many of the people I was talking to thought that PHP was king (ick).  (In related news, &lt;a href="http://www.djangobook.com/en/1.0/chapter02/"&gt;this is the best online book interface I&amp;#8217;ve seen yet&lt;/a&gt;.)&lt;/p&gt;
    </content>
</entry>
<entry>
    <title>Surprising Contender: NetBeans as a Ruby+MySQL IDE</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/2008/04/surprising_contender_netbeans_1.html" />
    <id>tag:www.oreillynet.com,2008:/ruby/blog//3.23583</id>
    
    <published>2008-04-27T01:35:33Z</published>
    <updated>2008-05-01T22:11:16Z</updated>
    
    <summary>I&apos;m sure you don&apos;t believe it, doesn&apos;t seem like NetBeans is going to take the Ruby developer world by storm, but Sun seems to be pouring money into Ruby support. I&apos;m skeptical that the Ruby community is going to embrace...</summary>
    <author>
        <name>Tim O&apos;Brien</name>
            </author>
            <category term="News" />
        <content type="html">
&lt;p&gt;I&amp;#8217;m sure you don&amp;#8217;t believe it, doesn&amp;#8217;t seem like NetBeans is going to take the Ruby developer world by storm, but Sun seems to be pouring money into Ruby support.  I&amp;#8217;m skeptical that the Ruby community is going to embrace Netbeans, but in this entry, I present some hints that NetBeans may be well on its way to becoming the Ruby IDE of choice.  The idea that an IDE traditionally associated with Java development is going to take the Ruby world by storm might seem insane at first glance, but read on&amp;#8230;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Sun is focused on developer market share&amp;#8230;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;#8230;nothing else.   Pouring money into Ruby support and acquiring MySQL for a cool billion play into this perfectly.   While everyone has been moaning about the Death of Java for years, it remains the biggest draw on Safari, and the biggest book market.  Ruby has the fastest growing community.   My conspiracy theory is that the MySQL acquisition coupled with the HUGE Netbeans focus means that Sun is after you.  Sun sees Ruby as having the energy, they want to channel your mojo.   (JavaOne should really be renamed at this point, I predict they are going to talk about Ruby almost as much as Java this year.)&lt;/p&gt;
&lt;p&gt;Which brings me to NetBeans&amp;#8230;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Kovacs Pitches a Curveball&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Sun relentlessly pushes NetBeans at every conference, to the point where every time I&amp;#8217;m in a Sun presentation I expect someone to relate it back to The Great IDE: NetBeans.    Almost every time I talk to some Sun PR operative they are talking to me about NetBeans.   I&amp;#8217;ve met some people that use NetBeans, but most tend to use either Eclipse or IntelliJ.    Michael Kovacs writes about his past and present view of NetBeans over on his blog in &lt;a href="http://javathehutt.blogspot.com/2008/04/so-long-textmate-hello-netbeans-really.html"&gt;&amp;#8220;So long TextMate?&amp;#8230; Hello NetBeans? Really? Yeah, really.&amp;#8221;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;
Michael Kovacs: I&amp;#8217;ll admit it, I&amp;#8217;m one of many folks that used to treat NetBeans as a whipping boy. Questioning why Sun would bother dumping money into the horse that so obviously lost the race to Eclipse and IntelliJ to win the hearts and minds of Java developers around the world.
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="http://javathehutt.blogspot.com/2008/04/so-long-textmate-hello-netbeans-really.html"&gt;He continues:&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;
Michael Kovacs: I grabbed a nightly build of 6.1 and installed it. Right away I noticed that they made a special build for ruby where they&amp;#8217;ve stripped it down to where it is focused on supporting ruby and rails apps. They added a theme that looks similar to textmate to make me feel more at home. Many keybindings are similar to Textmate (though not all but they are easily configurable). You can easily debug your rails apps with the graphical debugger. The generators work. Code completion works okay at times. API docs are easily visible inline during code completion. Navigating around the project is now easy where before that was the single biggest outage that kept me from even considering it. You can easily jump to type definitions. Simple refactoring is supported&amp;#8230;..
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Right, last time I installed Netbeans was just after last year&amp;#8217;s JavaOne, and I came away with the idea that it was weighty, not very agile.    I forget how big the download was, but it was more than Eclipse.   I&amp;#8217;m convinced there are some great things about the tool, and that it has come a long way, but I&amp;#8217;m still somewhat incredulous.    Although, Kovacs is no Sun fanboy, so his post got my attention.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;So, let&amp;#8217;s just assume that Kovacs is right and NetBeans is a real contender now&amp;#8230; enter MySQL..&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;James Hints at NetBeans 6.5: MySQL Integration&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;You&amp;#8217;ll probably warm up to NetBeans 6.5 once it becomes MySQL Administrator and MySQL Query Analyzer.   &lt;a href="http://blogs.sun.com/branajam/entry/big_database_changes_planned_for"&gt;James from Sun Discusses MySQL Support in NetBeans 6.5&lt;/a&gt; and you can see  &lt;a href="http://wiki.netbeans.org/DatabaseFeaturesForNB65"&gt;list of planned database features for NetBeans 6.5&lt;/a&gt;. Maybe we&amp;#8217;ll see the existing MySQL tools integrated in situ&amp;#8230; from the wiki:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;
&amp;#8230;MySQL WorkBench will solve this if we can integrate with it in some way&amp;#8230;
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Now that might give me a real reason to start using the tool.   If Sun really wanted to be draconian about it they would absorb MySQL Administrator and MySQL Query Browser into NetBeans and make NetBeans the only way to run the tools.   Now, if NetBeans became the supported way to manage MySQL, many Rails programmers would find themselves with an installation of NetBeans (with integrated MySQL Administrator, Query Browser, and Workbench).    Then they can start attracting you to NetBeans Ruby support&amp;#8230;..  then after that they start to convince you to start deploying applications using JRuby&amp;#8230;. then they dangle something like Glassfish integration in front of you and XA transactions (because everyone ends up needing them).&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m still skeptical, but I&amp;#8217;m going to RubyOne&amp;#8230;.. err&amp;#8230; JavaOne with open eyes.  If Kovacs is right, then NetBeans might just turn into the logical choice.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Netbeans Rails Support&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;If you are interested in seeing what Netbeans has to offer wrt Rails, take a look at &lt;a href="http://blogs.sun.com/tor/entry/netbeans_javascript_ruby"&gt;Tor Norbye&amp;#8217;s blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;(Time to stop) Stubbornly Clinging to Emacs&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;I still use Emacs for Ruby programming, everytime I&amp;#8217;ve tried to get into something like Aptana, I&amp;#8217;ve been frustrated by this or that.   Eclipse can become a bloated hog very quickly, and Aptana&amp;#8217;s array of confusingly named products doesn&amp;#8217;t help.  I often wish that I could go back in time and dissuade the Aptana guys from contacting the RadRails project, Aptana tries to do too much IMO - All I want is a Ruby IDE, and I get some sort of crazy &amp;#8220;Ajax server&amp;#8221; called Jaxer.&lt;/p&gt;
&lt;p&gt;Although I use Eclipse &lt;a href="http://www.onjava.com"&gt;with success in other languages&lt;/a&gt;, I&amp;#8217;ve never seen the need for an IDE in Ruby.    I said the same thing about Java up until 2002 when it became abundantly clear that the language complexity was forcing me to adopt an IDE.    That hasn&amp;#8217;t happened to me yet with Ruby.    Even with greater MySQL integration, I&amp;#8217;m still not convinced I would make the jump to an IDE for Rails.   &lt;b&gt;It is likely time to revisit Netbeans.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://feeds.feedburner.com/TimOBrien-OReilly"&gt;Subscribe to Tim&amp;#8217;s O&amp;#8217;Reilly Network Feed&lt;/a&gt;
&lt;/p&gt;
    </content>
</entry>
<entry>
    <title>mod_rails for Apache is now a Reality</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/2008/04/mod_rails_for_apache_is_now_a.html" />
    <id>tag:www.oreillynet.com,2008:/ruby/blog//3.23430</id>
    
    <published>2008-04-13T01:30:22Z</published>
    <updated>2008-04-13T01:33:07Z</updated>
    
    <summary>The long-awaited mod_rails for apache was released yesterday by Phusion, a Netherlands based IT company. As many of you have already done, I hopped on board to see how it worked and was amazed on how easy it was to...</summary>
    <author>
        <name>Eric Berry</name>
            </author>
            <category term="Reviews" />
        <content type="html">
&lt;p&gt;The long-awaited mod_rails for apache was released yesterday by &lt;a href="http://www.phusion.nl"&gt;Phusion&lt;/a&gt;, a Netherlands based IT company.  As many of you have already done, I hopped on board to see how it worked and was amazed on how easy it was to get up and going.&lt;/p&gt;
&lt;p&gt;Ryan Bates, a guy whose voice many of us Rails coders have grown to know and love, assisted the Phusion team in creating a short screencast on how to install Passenger onto your Mac.  &lt;/p&gt;
&lt;p&gt;It was basically 3 steps: &lt;/p&gt;
&lt;p&gt;1. Install the RubyGem&lt;br /&gt;
2. Run the passenger installer script&lt;br /&gt;
3. Modify your apache conf file to include the new virtual host&lt;/p&gt;
&lt;p&gt;Can it really get easier than that? I submit that it can not.&lt;/p&gt;
&lt;p&gt;In my opinion, Passenger is filling a void that has haunted the Ruby on Rails community for some time.  Matz is quoted as saying &amp;#8220;It is often said that Rails is weak on deployment; PHP runs fairly fast just by uploading scripts. Rails is slow on development mode, and requires restarting on production mode (and bit complex to configure). modrails might be the answer for it.&amp;#8221;.&lt;/p&gt;
&lt;p&gt;To get Passenger, go to &lt;a href="http://www.modrails.com"&gt;http://www.modrails.com&lt;/a&gt;&lt;/p&gt;
    </content>
</entry>
<entry>
    <title>Gobi: A Ruby Implementation for Go Enthusiasts</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/2008/04/gobi_a_ruby_implementation_for.html" />
    <id>tag:www.oreillynet.com,2008:/ruby/blog//3.23339</id>
    
    <published>2008-04-01T23:00:27Z</published>
    <updated>2008-04-01T23:00:43Z</updated>
    
    <summary>Reposting from the official announcement on RubyTalk Gobi version 1.0.0 has been released! * http://gobi.stonecode.org * http://metametta.blogspot.com * gregory.t.brown@gmail.com I am happy to announce the first release of my new fork of Ruby called Gobi. The goal of Gobi is...</summary>
    <author>
        <name>Gregory Brown</name>
            </author>
            <category term="Technical" />
        <content type="html">
&lt;p&gt;&lt;em&gt;Reposting from the official announcement on RubyTalk&lt;/em&gt;&lt;/p&gt;
&lt;p&gt; Gobi version 1.0.0 has been released!&lt;/p&gt;
&lt;p&gt;* http://gobi.stonecode.org&lt;br /&gt;
* http://metametta.blogspot.com&lt;br /&gt;
* gregory.t.brown@gmail.com&lt;/p&gt;
&lt;p&gt; I am happy to announce the first release of my new fork of Ruby called&lt;br /&gt;
 Gobi. The goal of Gobi is to implement features that I have noticed to&lt;br /&gt;
 be completely missing.&lt;/p&gt;
&lt;p&gt; For example, Ruby&amp;#8217;s standard library does not even implement a&lt;br /&gt;
 datastructure that can easily represent a Go board.  Gobi has this&lt;br /&gt;
 built in as an NArray based, highly efficient structure:&lt;/p&gt;
&lt;pre&gt;
 &gt;&gt; x = Goban.new
 &gt;&gt; x.place_stone(:black, :at =&gt; "a4")
 &gt;&gt; x.place_stone(:white, :at =&gt; "c16")
 &gt;&gt; x.place_stone(:black, :at =&gt; "a4")
 StoneCollisionError: There is already a stone at a4.
   from (irb):3 in `place_stone`
   from (irb):6
&lt;/pre&gt;
&lt;p&gt; As you can see from the example above, Gobi is very friendly to those&lt;br /&gt;
 writing computer Go applications.  For those wishing to write AI bots&lt;br /&gt;
 to play the game, Gobi also goes through a lot of effort to make Ruby&lt;br /&gt;
 more efficient.&lt;/p&gt;
&lt;p&gt; A major improvement in performance was gained through the removal of&lt;br /&gt;
 automatic garbage collection.  This means that programmers need to be&lt;br /&gt;
 sure to clean up after themselves, but any Rubyist who also has an&lt;br /&gt;
 interest in Go will surely be sufficiently skilled to design programs&lt;br /&gt;
 that avoid memory leaks.&lt;/p&gt;
&lt;p&gt; The implementation of object destructors in Gobi is simple, due to the&lt;br /&gt;
 addition of a release_resources hook in Object.  A delete keyword has&lt;br /&gt;
 also been added, which will explicitly start up the garbage collection&lt;br /&gt;
 process.&lt;/p&gt;
&lt;p&gt; Here&amp;#8217;s an example of manual garbage collection in Gobi:&lt;/p&gt;
&lt;pre&gt;
 class Foo
   def initialize
     @board = Goban.new
   end
   def release_resources
     delete @board
   end
 end
&lt;/pre&gt;
&lt;p&gt; Please keep in mind that although the built in classes all have&lt;br /&gt;
 sensible release_resources implementations, that if you&amp;#8217;re feeling&lt;br /&gt;
 adventurous, you can of course override them.  A current fun game in&lt;br /&gt;
 Gobi is to run a stopwatch and see how quickly memory runs out when&lt;br /&gt;
 you write some code like this:&lt;/p&gt;
&lt;pre&gt;
 class String
   def release_resources; end
 end
&lt;/pre&gt;
&lt;pre&gt;
 string = 'a'
 1_000_000_000.times do
   string = string.succ
 end
&lt;/pre&gt;
&lt;p&gt; Of course, though humorous, this should serve as a warning to you: Be&lt;br /&gt;
 sure to properly discard your objects!&lt;/p&gt;
&lt;p&gt; This announcement just scratches the tip of the iceberg of what Gobi offers.&lt;/p&gt;
&lt;p&gt; Other cool features include:&lt;/p&gt;
&lt;p&gt; - The removal of Ruby 1.9&amp;#8217;s giant interpreter lock, as Go programs&lt;br /&gt;
 tend to benefit from the power of true concurrency.  (Unfortunately,&lt;br /&gt;
 these patches are very platform dependent)&lt;/p&gt;
&lt;p&gt; - A major reshifting of Ruby&amp;#8217;s standard library.  Things like option&lt;br /&gt;
 parsing, zlib support, and fileutils aren&amp;#8217;t really that useful for&lt;br /&gt;
 programming computer Go applications, so they have been removed.  Many&lt;br /&gt;
 new libraries have been added, including an SGF analysis tool and a&lt;br /&gt;
 GTP network implementation.&lt;/p&gt;
&lt;p&gt; - An interface to a special (Proverb Semantics Parsing) PSP tool,&lt;br /&gt;
 which allows you to train Go playing robots by simply reminding them&lt;br /&gt;
 of proverbs such as &amp;#8220;Hane at the head of two stones&amp;#8221;, and &amp;#8220;The empty&lt;br /&gt;
 triangle is bad&amp;#8221;, rather than resorting to low level, complicated AI&lt;br /&gt;
 programming.  This system can be used via irb while a game is under&lt;br /&gt;
 review in Gobi&amp;#8217;s built in Tk based SGF editor. Gobi shows that by&lt;br /&gt;
 mindlessly memorizing proverbs, Go playing bots can decrease their&lt;br /&gt;
 rank by two stones in half the time that an average human can.&lt;/p&gt;
&lt;p&gt; - The removal of all lesser data structures such as the Array, the&lt;br /&gt;
 Hash, and the Set.  In Gobi, all of these structures could trivially&lt;br /&gt;
 be built as a subclass of the Goban, so there is no need to keep them&lt;br /&gt;
 around.&lt;/p&gt;
&lt;p&gt; Though I will be taking off 6 months from Gobi development to work on&lt;br /&gt;
 the Ruby Mendicant project, I hope that people enjoy this early&lt;br /&gt;
 experimental release and that soon Ruby will be free from the core&lt;br /&gt;
 team&amp;#8217;s shackles to do what it truly deserves to: Reach 30 kyu on KGS&lt;br /&gt;
 with a Gobi based bot!&lt;/p&gt;
&lt;p&gt; Though only time will tell, I am considering reworking Gobi to fork&lt;br /&gt;
 Aaron Patterson&amp;#8217;s excellent Brobinius implementation, as Gobi deserves&lt;br /&gt;
 some high quality Grosenbach screencasts.&lt;/p&gt;
&lt;p&gt; * http://gobi.stonecode.org&lt;br /&gt;
 * http://metametta.blogspot.com&lt;br /&gt;
 * gregory.t.brown@gmail.com&lt;/p&gt;
    </content>
</entry>
<entry>
    <title>CruiseControl Progress Charts</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/2008/03/cruisecontrol_charts.html" />
    <id>tag:www.oreillynet.com,2008:/ruby/blog//3.23260</id>
    
    <published>2008-03-18T16:14:15Z</published>
    <updated>2008-03-18T16:20:06Z</updated>
    
    <summary>CruiseControl.rb, by ThoughtWorks, is an elegant Continuous Integration dashboard for Ruby projects. This article shows how to call the RoR command &quot;rake stats&quot; once per build, capture the results, and chart them to track trends over time.</summary>
    <author>
        <name>Phlip</name>
        <uri>http://www.oreilly.com/catalog/9780596510657/</uri>    </author>
        <content type="html">
&lt;p&gt;&lt;a href=
  'http://cruisecontrolrb.thoughtworks.com/'&gt;CruiseControl.rb&lt;/a&gt;,
  by &lt;a href='http://www.thoughtworks.com/'&gt;ThoughtWorks&lt;/a&gt;, 
  is an elegant 
  &lt;a href='http://en.wikipedia.org/wiki/Continuous_integration'
  &gt;Continuous Integration&lt;/a&gt; dashboard for 
  &lt;a href='http://www.ruby-lang.org/en/'&gt;Ruby&lt;/a&gt;
  projects. This article shows how to call the &lt;a
  href='http://www.rubyonrails.org/' title='Ruby on Rails'&gt;RoR&lt;/a&gt; command 
  &amp;#8220;&lt;code&gt;&lt;a href='http://www.google.com/search?q=%22rake+stats%22'&gt;rake stats&lt;/a&gt;&lt;/code&gt;&amp;#8221;
  once per build, capture the results, and chart them to track
  trends over time.&lt;/p&gt;
  &lt;p&gt;Our goal is this chart:&lt;/p&gt;
  &lt;img alt="cc_gnuplot.png" src="http://www.oreillynet.com/ruby/blog/images/cc_gnuplot.png" width="616" height="493" /&gt;


  &lt;p&gt;If you think of more trends to display, our architecture will
  make them easy to add. We start by counting the lines of tests
  &amp;#38; code, and calculating their ratio. These signals occupy
  different orders of magnitude, so a logarithmic scale reveals their
  common slopes. We use &lt;a 
  href='http://www.gnuplot.info/'&gt;Gnuplot&lt;/a&gt; to make such charts easy; 
  this paltry output
  is only the beginning of Gnuplot&amp;#8217;s abilities.&lt;/p&gt;

  &lt;h3&gt;CruiseControl.rb&lt;/h3&gt;

  &lt;p&gt;This exemplary Ruby on Rails application provides these
  important files and folders:&lt;/p&gt;

  &lt;table&gt;
    &lt;tr&gt;
      &lt;td valign='top'&gt;
      &lt;code&gt;app/controllers/projects_controller.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td valign='top'&gt;we will add a &lt;code&gt;statistics&lt;/code&gt; view
      here&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td valign='top'&gt;&lt;code&gt;app/models/project.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td valign='top'&gt;we will add supporting methods here&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td valign='top'&gt;
      &lt;code&gt;builder_plugins/installed/&lt;em&gt;statistician.rb&lt;/em&gt;&lt;/code&gt;&lt;/td&gt;
      &lt;td valign='top'&gt;when the builder encountered significant
      events, such as successful builds, it calls registered
      methods in here. We add a new &lt;code&gt;statistician.rb&lt;/code&gt;
      class to call &lt;code&gt;rake stats&lt;/code&gt; and record its
      results&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td valign='top'&gt;&lt;code&gt;lib/&lt;/code&gt;&lt;/td&gt;
      &lt;td valign='top'&gt;you might plop &lt;code&gt;gnuplot.rb&lt;/code&gt; in here; 
      otherwise, get it with &lt;strong&gt;&lt;code&gt;gem install gnuplot&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td valign='top'&gt;&lt;code&gt;projects/your_application/&lt;em&gt;statistics.yaml&lt;/em&gt;&lt;/code&gt;&lt;/td&gt;
      &lt;td valign='top'&gt;we add a database of your application&amp;#8217;s statistics&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td valign='top'&gt;&lt;code&gt;public/images/&lt;em&gt;charts/your_application.svg&lt;/em&gt;&lt;/code&gt;&lt;/td&gt;
      &lt;td valign='top'&gt;to view a chart, we render its 
      &lt;a href='http://www.w3.org/Graphics/SVG/' 
      title='Scalable Vector Graphics'&gt;SVG&lt;/a&gt; file into here&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;

  &lt;h3&gt;&lt;code&gt;builder_plugins/installed/statistician.rb&lt;/code&gt;&lt;/h3&gt;
  &lt;p&gt;Add this file there, and bounce your server:&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color: #000080;"&gt;require&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'fileutils'&lt;/span&gt;
&lt;span style="color: #000080;"&gt;require&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'code_statistics'&lt;/span&gt;

&lt;span style="font-weight: bold;color: #000000;"&gt;class&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800000;"&gt;Statistician&lt;/span&gt;

&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;def&lt;/span&gt;&lt;span style="color: #000000;"&gt; initialize(project)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;    &lt;/span&gt;&lt;span style="color: #008000;"&gt;@project&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; project&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;

&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;def&lt;/span&gt;&lt;span style="color: #000000;"&gt; build_finished(build)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;    run_in_here &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #008000;"&gt;@project&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.path&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;+&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'/work'&lt;/span&gt;
&lt;span style="color: #000000;"&gt;    &lt;/span&gt;&lt;span style="color: #800000;"&gt;FileUtils&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.cd&lt;/span&gt;&lt;span style="color: #000000;"&gt;(run_in_here)&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;{&lt;/span&gt;&lt;span style="color: #000000;"&gt;  append_stats(&lt;/span&gt;&lt;span style="color: #008000;"&gt;@project&lt;/span&gt;&lt;span style="color: #000000;"&gt;)  &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;}&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;
&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;

&lt;span style="font-weight: bold;color: #bb1188;"&gt;STATS_FOLDERS&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;[&lt;/span&gt;
&lt;span style="color: #008000;"&gt;  %w(&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;Controllers        app/controllers&lt;/span&gt;&lt;span style="color: #008000;"&gt;)&lt;/span&gt;&lt;span style="color: #000000;"&gt;,&lt;/span&gt;
&lt;span style="color: #008000;"&gt;  %w(&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;Helpers            app/helpers&lt;/span&gt;&lt;span style="color: #008000;"&gt;)&lt;/span&gt;&lt;span style="color: #000000;"&gt;, &lt;/span&gt;
&lt;span style="color: #008000;"&gt;  %w(&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;Models             app/models&lt;/span&gt;&lt;span style="color: #008000;"&gt;)&lt;/span&gt;&lt;span style="color: #000000;"&gt;,&lt;/span&gt;
&lt;span style="color: #008000;"&gt;  %w(&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;Libraries          lib/&lt;/span&gt;&lt;span style="color: #008000;"&gt;)&lt;/span&gt;&lt;span style="color: #000000;"&gt;,&lt;/span&gt;
&lt;span style="color: #008000;"&gt;  %w(&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;APIs               app/apis&lt;/span&gt;&lt;span style="color: #008000;"&gt;)&lt;/span&gt;&lt;span style="color: #000000;"&gt;,&lt;/span&gt;
&lt;span style="color: #008000;"&gt;  %w(&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;Components         components&lt;/span&gt;&lt;span style="color: #008000;"&gt;)&lt;/span&gt;&lt;span style="color: #000000;"&gt;,&lt;/span&gt;
&lt;span style="color: #008000;"&gt;  %w(&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;Integration\ tests test/integration&lt;/span&gt;&lt;span style="color: #008000;"&gt;)&lt;/span&gt;&lt;span style="color: #000000;"&gt;,&lt;/span&gt;
&lt;span style="color: #008000;"&gt;  %w(&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;Functional\ tests  test/functional&lt;/span&gt;&lt;span style="color: #008000;"&gt;)&lt;/span&gt;&lt;span style="color: #000000;"&gt;,&lt;/span&gt;
&lt;span style="color: #008000;"&gt;  %w(&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;Unit\ tests        test/unit&lt;/span&gt;&lt;span style="color: #008000;"&gt;)&lt;/span&gt;
&lt;span style="color: #ff00ff;"&gt;]&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.freeze&lt;/span&gt;

&lt;span style="font-weight: bold;color: #000000;"&gt;class&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800000;"&gt;CodeStatistics&lt;/span&gt;&lt;span style="color: #000000;"&gt;;  &lt;/span&gt;&lt;span style="color: #008000;"&gt;attr_reader&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #d40000;"&gt;:statistics&lt;/span&gt;&lt;span style="color: #000000;"&gt;;  &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;

&lt;span style="font-weight: bold;color: #000000;"&gt;def&lt;/span&gt;&lt;span style="color: #000000;"&gt; collect_stats&lt;/span&gt;
&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="font-style: italic;color: #808080;"&gt; #  code callously ripped from statistics.rake !&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  folders &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="font-weight: bold;color: #bb1188;"&gt;STATS_FOLDERS&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.select&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;{&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt;name, dir&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800000;"&gt;File&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.directory?&lt;/span&gt;&lt;span style="color: #000000;"&gt;(dir) &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;}&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  cs      &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800000;"&gt;CodeStatistics&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.new&lt;/span&gt;&lt;span style="color: #000000;"&gt;(&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;*&lt;/span&gt;&lt;span style="color: #000000;"&gt;folders)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  statz   &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; cs&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.statistics&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  tyme    &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800000;"&gt;Time&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.now.to_i&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  yaml    &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;{&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd0000;"&gt;"build_&lt;/span&gt;&lt;span style="color: #008000;"&gt;#{&lt;/span&gt;&lt;span style="color: #000000;"&gt; tyme &lt;/span&gt;&lt;span style="color: #008000;"&gt;}&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;"&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&amp;#62;&lt;/span&gt;&lt;span style="color: #000000;"&gt; statz &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;}&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.to_yaml&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; yaml&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.sub&lt;/span&gt;&lt;span style="color: #000000;"&gt;(&lt;/span&gt;&lt;span style="color: #4a5704;"&gt;/^---/&lt;/span&gt;&lt;span style="color: #000000;"&gt;, &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;''&lt;/span&gt;&lt;span style="color: #000000;"&gt;) &lt;/span&gt;&lt;span style="font-style: italic;color: #808080;"&gt; #  abrogate that pesky document marker!&lt;/span&gt;
&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;

&lt;span style="font-weight: bold;color: #000000;"&gt;def&lt;/span&gt;&lt;span style="color: #000000;"&gt; append_stats(project)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  yaml &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; collect_stats&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  plop &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; project&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.path&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;+&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'/statistics.yaml'&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="color: #800000;"&gt;File&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.open&lt;/span&gt;&lt;span style="color: #000000;"&gt;(plop, &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'a+'&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;{&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt;f&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt; f&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.write&lt;/span&gt;&lt;span style="color: #000000;"&gt;(yaml) &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;}&lt;/span&gt;
&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;

&lt;span style="color: #800000;"&gt;Project&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.plugin&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #d40000;"&gt;:statistician&lt;/span&gt;

&lt;/pre&gt;

  &lt;p&gt;That code registers a callback, to call when any project ends. It uses 
  &lt;code&gt;code_statistics.rb&lt;/code&gt; to collects line counts for your project,
  then it appends them to the end of 
  &lt;code&gt;projects/your_application/&lt;em&gt;statistics.yaml&lt;/em&gt;&lt;/code&gt;. 
  Install that module, trigger a few builds,
  and examine that &lt;a href='http://www.yaml.org/' 
  title='YAML Ain&amp;apos;t Markup Language'&gt;YAML&lt;/a&gt; file to 
  see your trends in their raw format.
  &lt;/p&gt;
  
  &lt;h3&gt;&lt;code&gt;project.rb&lt;/code&gt;&lt;/h3&gt;
  
  &lt;p&gt;The callback indexed the database using a timestamp - 
  the number of seconds
  since 1970. To read the database, we pull everything into an 
  &lt;a href='http://www.ruby-doc.org/core/classes/Array.html'&gt;Array&lt;/a&gt; 
  and sort it by that timestamp.&lt;/p&gt;
  
  &lt;p&gt;Add this to &lt;code&gt;app/models/project.rb&lt;/code&gt;:&lt;/p&gt;
  
&lt;pre&gt;
&lt;span style="color: #000080;"&gt;require&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'fileutils'&lt;/span&gt;
&lt;span style="color: #000080;"&gt;require&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'gnuplot'&lt;/span&gt;

&lt;span style="font-weight: bold;color: #000000;"&gt;class&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800000;"&gt;Project&lt;/span&gt;

&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;def&lt;/span&gt;&lt;span style="color: #000000;"&gt; get_stats&lt;/span&gt;
&lt;span style="color: #000000;"&gt;    statistics &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800000;"&gt;File&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.read&lt;/span&gt;&lt;span style="color: #000000;"&gt;(path &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;+&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'/statistics.yaml'&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;    statistics &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="font-weight: bold;color: #bb1188;"&gt;YAML&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;::&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;load&lt;/span&gt;&lt;span style="color: #000000;"&gt;(statistics)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;    statistics &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; statistics&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.map&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;{&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt;k,v&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;[&lt;/span&gt;&lt;span style="color: #000000;"&gt;k&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.sub&lt;/span&gt;&lt;span style="color: #000000;"&gt;(&lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'build_'&lt;/span&gt;&lt;span style="color: #000000;"&gt;, &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;''&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.to_i&lt;/span&gt;&lt;span style="color: #000000;"&gt;, v&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;]&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;}&lt;/span&gt;
&lt;span style="color: #000000;"&gt;    &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; statistics&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.sort&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;
&lt;span style="color: #888888;"&gt;...&lt;/span&gt;
&lt;/pre&gt;

  &lt;p&gt;That method took out the prefix &amp;#8220;&lt;code&gt;build_&lt;/code&gt;&amp;#8220;, 
  and converted the timestamp back into an integer.&lt;/p&gt;

  &lt;h3&gt;&lt;code&gt;gnu_plot_stats&lt;/code&gt;&lt;/h3&gt;

  &lt;p&gt;Now that we have an array of statistics, we need to convert them into
  a chart. You can add the following code anywhere that 
  &lt;code&gt;projects_controller.rb&lt;/code&gt; can see; I happened to add mine 
  to the end of &lt;code&gt;app/models/project.rb&lt;/code&gt;: &lt;/p&gt;

  &lt;pre&gt;&lt;span style="color: #000080;"&gt;require&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'gnuplot'&lt;/span&gt;

&lt;span style="font-weight: bold;color: #000000;"&gt;def&lt;/span&gt;&lt;span style="color: #000000;"&gt; timefmt;  &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'%Y/%d/%m-%H:%M'&lt;/span&gt;&lt;span style="color: #000000;"&gt;;  &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;

&lt;span style="font-weight: bold;color: #000000;"&gt;def&lt;/span&gt;&lt;span style="color: #000000;"&gt; fetch_codelines(stat, fields)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; stat&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.values_at&lt;/span&gt;&lt;span style="color: #000000;"&gt;(&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;*&lt;/span&gt;&lt;span style="color: #000000;"&gt;fields)&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.map&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;{&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt;values&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt; values&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;[&lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'codelines'&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;]&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;}&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.sum&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;
&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;

&lt;span style="font-weight: bold;color: #000000;"&gt;def&lt;/span&gt;&lt;span style="color: #000000;"&gt; ftime(timestamp)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="color: #800000;"&gt;Time&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.at&lt;/span&gt;&lt;span style="color: #000000;"&gt;(timestamp)&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.strftime&lt;/span&gt;&lt;span style="color: #000000;"&gt;(timefmt)&lt;/span&gt;
&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;

&lt;span style="font-weight: bold;color: #bb1188;"&gt;ALL_TESTS&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #008000;"&gt; %w(&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;Unit\ tests Functional\ tests&lt;/span&gt;&lt;span style="color: #008000;"&gt;)&lt;/span&gt;
&lt;span style="font-weight: bold;color: #bb1188;"&gt;ALL_CODE&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #008000;"&gt; %w(&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;Components Libraries Helpers Controllers Models&lt;/span&gt;&lt;span style="color: #008000;"&gt;)&lt;/span&gt;

&lt;span style="font-weight: bold;color: #000000;"&gt;def&lt;/span&gt;&lt;span style="color: #000000;"&gt; gnu_plot_stats(project, signals &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;{}&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  signals &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;{&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'Code'&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&amp;#62;&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="font-weight: bold;color: #bb1188;"&gt;ALL_CODE&lt;/span&gt;&lt;span style="color: #000000;"&gt;, &lt;/span&gt;
&lt;span style="color: #000000;"&gt;              &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'Test'&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&amp;#62;&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="font-weight: bold;color: #bb1188;"&gt;ALL_TESTS&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;}&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.merge&lt;/span&gt;&lt;span style="color: #000000;"&gt;(signals)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  name &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #008000;"&gt;@project&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.name&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  path &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd0000;"&gt;"&lt;/span&gt;&lt;span style="color: #008000;"&gt;#{&lt;/span&gt;&lt;span style="font-weight: bold;color: #bb1188;"&gt;RAILS_ROOT&lt;/span&gt;&lt;span style="color: #008000;"&gt;}&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;/public/images/charts"&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="color: #800000;"&gt;Dir&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.mkdir&lt;/span&gt;&lt;span style="color: #000000;"&gt;(path) &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;unless&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800000;"&gt;File&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.directory?&lt;/span&gt;&lt;span style="color: #000000;"&gt;(path)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  output_file &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd0000;"&gt;"&lt;/span&gt;&lt;span style="color: #008000;"&gt;#{&lt;/span&gt;&lt;span style="color: #000000;"&gt;path&lt;/span&gt;&lt;span style="color: #008000;"&gt;}&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;/&lt;/span&gt;&lt;span style="color: #008000;"&gt;#{&lt;/span&gt;&lt;span style="color: #000000;"&gt;name&lt;/span&gt;&lt;span style="color: #008000;"&gt;}&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;.svg"&lt;/span&gt;
&lt;span style="color: #000000;"&gt;   &lt;/span&gt;&lt;span style="font-style: italic;color: #808080;"&gt; #  drastically prevent false positives in manual tests&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="color: #800000;"&gt;File&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.unlink&lt;/span&gt;&lt;span style="color: #000000;"&gt;(output_file) &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800000;"&gt;File&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.exist?&lt;/span&gt;&lt;span style="color: #000000;"&gt;(output_file)&lt;/span&gt;

&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="color: #800000;"&gt;Gnuplot&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.open&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;do&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt;gp&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;
&lt;span style="color: #000000;"&gt;    &lt;/span&gt;&lt;span style="color: #800000;"&gt;Gnuplot&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;::&lt;/span&gt;&lt;span style="color: #800000;"&gt;Plot&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.new&lt;/span&gt;&lt;span style="color: #000000;"&gt;( gp ) &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;do&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt;plot&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;
&lt;span style="color: #000000;"&gt;       &lt;/span&gt;&lt;span style="font-style: italic;color: #808080;"&gt; #  decorate the chart&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      plot&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.xdata&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'time'&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      plot&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.key&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'outside title "   Code Lines   "'&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      plot&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.grid&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'ytics'&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      plot&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.timefmt&lt;/span&gt;&lt;span style="color: #000000;"&gt; timefmt&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.inspect&lt;/span&gt;&lt;span style="font-style: italic;color: #808080;"&gt; # for quote marks&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      plot&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.term&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'svg'&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      plot&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.output&lt;/span&gt;&lt;span style="color: #000000;"&gt; output_file&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      plot&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.title&lt;/span&gt;&lt;span style="color: #000000;"&gt; name&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      plot&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.logscale&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;'y'&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      stats &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; project&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.get_stats&lt;/span&gt;
&lt;span style="color: #000000;"&gt;       &lt;/span&gt;&lt;span style="font-style: italic;color: #808080;"&gt; #  set the time range&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      timestamps &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; stats&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.map&lt;/span&gt;&lt;span style="color: #000000;"&gt;(&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#38;&lt;/span&gt;&lt;span style="color: #d40000;"&gt;:first&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      statistics &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; stats&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.map&lt;/span&gt;&lt;span style="color: #000000;"&gt;(&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#38;&lt;/span&gt;&lt;span style="color: #d40000;"&gt;:last&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      mini &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; timestamps&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.first&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;-&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;60&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      maxi &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; timestamps&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.last&lt;/span&gt;&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;+&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;60&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      plot&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.xrange&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd0000;"&gt;"['&lt;/span&gt;&lt;span style="color: #008000;"&gt;#{&lt;/span&gt;&lt;span style="color: #000000;"&gt; ftime(mini) &lt;/span&gt;&lt;span style="color: #008000;"&gt;}&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;':'&lt;/span&gt;&lt;span style="color: #008000;"&gt;#{&lt;/span&gt;&lt;span style="color: #000000;"&gt; ftime(maxi) &lt;/span&gt;&lt;span style="color: #008000;"&gt;}&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;']"&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      times &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; timestamps&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.map&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;{&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt;v&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt;  ftime(v&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.to_i&lt;/span&gt;&lt;span style="color: #000000;"&gt;)  &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;}&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      maxi &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;0&lt;/span&gt;

&lt;span style="color: #000000;"&gt;       &lt;/span&gt;&lt;span style="font-style: italic;color: #808080;"&gt; #  collect each signal and add its plot line&lt;/span&gt;

&lt;span style="color: #000000;"&gt;      plot&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.data&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; signals&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.keys.sort.map&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;do&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt;legend&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;
&lt;span style="color: #000000;"&gt;        fields &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; signals&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;[&lt;/span&gt;&lt;span style="color: #000000;"&gt;legend&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;]&lt;/span&gt;
&lt;span style="color: #000000;"&gt;        &lt;/span&gt;
&lt;span style="color: #000000;"&gt;        values &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; statistics&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.map&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;{&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt;stat&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;
&lt;span style="color: #000000;"&gt;                    &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; fields&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.respond_to?&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #d40000;"&gt;:call&lt;/span&gt;
&lt;span style="color: #000000;"&gt;                      fields&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.call&lt;/span&gt;&lt;span style="color: #000000;"&gt;(stat)&lt;/span&gt;
&lt;span style="font-weight: bold;color: #000000;"&gt;                    else&lt;/span&gt;
&lt;span style="color: #000000;"&gt;                      fetch_codelines(stat, fields)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;                    &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;
&lt;span style="color: #000000;"&gt;                  &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;}&lt;/span&gt;
&lt;span style="color: #000000;"&gt;        maxi &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;[&lt;/span&gt;&lt;span style="color: #000000;"&gt;maxi, &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;*&lt;/span&gt;&lt;span style="color: #000000;"&gt;values&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;]&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.max&lt;/span&gt;
&lt;span style="color: #000000;"&gt;        &lt;/span&gt;
&lt;span style="color: #000000;"&gt;        &lt;/span&gt;&lt;span style="color: #800000;"&gt;Gnuplot&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;::&lt;/span&gt;&lt;span style="color: #800000;"&gt;DataSet&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.new&lt;/span&gt;&lt;span style="color: #000000;"&gt;( &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;[&lt;/span&gt;&lt;span style="color: #000000;"&gt;times, values&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;]&lt;/span&gt;&lt;span style="color: #000000;"&gt; ) &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;{&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt;ds&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;
&lt;span style="color: #000000;"&gt;          ds&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.with&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd0000;"&gt;"linespoints"&lt;/span&gt;
&lt;span style="color: #000000;"&gt;          ds&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.title&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; legend&lt;/span&gt;
&lt;span style="color: #000000;"&gt;          ds&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.using&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'1:2'&lt;/span&gt;
&lt;span style="color: #000000;"&gt;          ds&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.linewidth&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;4&lt;/span&gt;
&lt;span style="color: #000000;"&gt;        &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;}&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;

&lt;span style="color: #000000;"&gt;       &lt;/span&gt;&lt;span style="font-style: italic;color: #808080;"&gt; #  set the chart height&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      next_order_of_magnitude &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;10&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;**&lt;/span&gt;&lt;span style="color: #000000;"&gt; (&lt;/span&gt;&lt;span style="color: #800000;"&gt;Math&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.log10&lt;/span&gt;&lt;span style="color: #000000;"&gt;(maxi) &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;+&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;1.1&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.to_i&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      plot&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.set&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'yrange'&lt;/span&gt;&lt;span style="color: #000000;"&gt;, &lt;/span&gt;&lt;span style="color: #dd0000;"&gt;"[0.01:&lt;/span&gt;&lt;span style="color: #008000;"&gt;#{&lt;/span&gt;&lt;span style="color: #000000;"&gt;next_order_of_magnitude&lt;/span&gt;&lt;span style="color: #008000;"&gt;}&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;]"&lt;/span&gt;
&lt;span style="color: #000000;"&gt;    &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="font-style: italic;color: #808080;"&gt; #  this 'end' writes the output SVG&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  &lt;/span&gt;
&lt;span style="color: #000000;"&gt;   &lt;/span&gt;&lt;span style="font-style: italic;color: #808080;"&gt; #  Gnuplot can't beautify the SVG enough, so we tweak these things&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  svg  &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800000;"&gt;File&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.read&lt;/span&gt;&lt;span style="color: #000000;"&gt;(output_file)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  doc  &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="font-weight: bold;color: #bb1188;"&gt;REXML&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;::&lt;/span&gt;&lt;span style="color: #800000;"&gt;Document&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.new&lt;/span&gt;&lt;span style="color: #000000;"&gt;(svg)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  node &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="font-weight: bold;color: #bb1188;"&gt;REXML&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;::&lt;/span&gt;&lt;span style="color: #800000;"&gt;XPath&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.first&lt;/span&gt;&lt;span style="color: #000000;"&gt;(doc, &lt;/span&gt;&lt;span style="color: #dd0000;"&gt;"//text[ '&lt;/span&gt;&lt;span style="color: #008000;"&gt;#{&lt;/span&gt;&lt;span style="color: #000000;"&gt;name&lt;/span&gt;&lt;span style="color: #008000;"&gt;}&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;' = . ]"&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;unless&lt;/span&gt;&lt;span style="color: #000000;"&gt; node&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  node&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.attributes&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;[&lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'style'&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;]&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'fill: #507ec0; font-family:Arial,sans; font-size: 0.8cm;'&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  node &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="font-weight: bold;color: #bb1188;"&gt;REXML&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;::&lt;/span&gt;&lt;span style="color: #800000;"&gt;XPath&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.first&lt;/span&gt;&lt;span style="color: #000000;"&gt;(doc, &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'/svg'&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  node&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.attributes&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;[&lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'height'&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;]&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;nil&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  node&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.attributes&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;[&lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'width'&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;]&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;nil&lt;/span&gt;
&lt;span style="color: #000000;"&gt;   &lt;/span&gt;&lt;span style="font-style: italic;color: #808080;"&gt; #  and finally write the SVG again&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="color: #800000;"&gt;File&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.open&lt;/span&gt;&lt;span style="color: #000000;"&gt;(output_file, &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;'w'&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;{&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt;f&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt; doc&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.write&lt;/span&gt;&lt;span style="color: #000000;"&gt;(f) &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;}&lt;/span&gt;
&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;
&lt;/pre&gt;
  
  &lt;p&gt;The top-level method, &lt;code&gt;gnu_plot_stats&lt;/code&gt;, can call with 
  more signal commands, as an option hash. A signal&amp;#8217;s key is its
  legend, and its value is either an array of signals to sum, or a
  &lt;code&gt;lambda&lt;/code&gt; to call, for more advanced processing. That&amp;#8217;s how we
  will find the ratio of test to code lines.&lt;/p&gt;

  &lt;h3&gt;&lt;code&gt;project_controller.rb&lt;/code&gt;&lt;/h3&gt;

  &lt;p&gt;To get this chart into our users&amp;#8217; faces, we need a new action in a 
  controller. Providing links to this action is left as an exercise
  for the reader.&lt;/p&gt;
  
  &lt;p&gt;Add this &lt;code&gt;statistics&lt;/code&gt; action to 
  &lt;code&gt;app/controllers/projects_controller.rb&lt;/code&gt;:&lt;/p&gt;
  
&lt;pre&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;class&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800000;"&gt;ProjectsController&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#60;&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800000;"&gt;ApplicationController&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  layout &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'default'&lt;/span&gt;

&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;def&lt;/span&gt;&lt;span style="color: #000000;"&gt; statistics&lt;/span&gt;
&lt;span style="color: #000000;"&gt;    &lt;/span&gt;&lt;span style="color: #008000;"&gt;@project&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800000;"&gt;Projects&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.find&lt;/span&gt;&lt;span style="color: #000000;"&gt;(params&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;[&lt;/span&gt;&lt;span style="color: #d40000;"&gt;:id&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;]&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;    name &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #008000;"&gt;@project&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.name&lt;/span&gt;
&lt;span style="color: #000000;"&gt;    &lt;/span&gt;
&lt;span style="color: #000000;"&gt;    gnu_plot_stats &lt;/span&gt;&lt;span style="color: #008000;"&gt;@project&lt;/span&gt;&lt;span style="color: #000000;"&gt;,&lt;/span&gt;
&lt;span style="color: #000000;"&gt;      &lt;/span&gt;&lt;span style="color: #dd4a4a;"&gt;'Test:Code'&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&amp;#62;&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000080;"&gt;lambda&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;{&amp;#124;&lt;/span&gt;&lt;span style="color: #000000;"&gt;stats&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&amp;#124;&lt;/span&gt;
&lt;span style="color: #000000;"&gt;        &lt;/span&gt;&lt;span style="color: #000080;"&gt;test&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; fetch_codelines(stats, &lt;/span&gt;&lt;span style="font-weight: bold;color: #bb1188;"&gt;ALL_TESTS&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;        code &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; fetch_codelines(stats, &lt;/span&gt;&lt;span style="font-weight: bold;color: #bb1188;"&gt;ALL_CODE&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;        &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000080;"&gt;test&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.to_f&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt; / &lt;/span&gt;&lt;span style="color: #000000;"&gt;code&lt;/span&gt;&lt;span style="color: #4000a7;"&gt;.to_f&lt;/span&gt;
&lt;span style="color: #000000;"&gt;        &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;}&lt;/span&gt;
&lt;span style="color: #000000;"&gt;    render &lt;/span&gt;&lt;span style="color: #d40000;"&gt;:layout&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&amp;#62;&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;true&lt;/span&gt;&lt;span style="color: #000000;"&gt;,&lt;/span&gt;
&lt;span style="color: #000000;"&gt;           &lt;/span&gt;&lt;span style="color: #d40000;"&gt;:inline&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;=&amp;#62;&lt;/span&gt;&lt;span style="color: #008000;"&gt; %(&lt;/span&gt;
&lt;span style="color: #dd0000;"&gt;        &amp;#60;embed src="/images/charts/&lt;/span&gt;&lt;span style="color: #008000;"&gt;#{&lt;/span&gt;&lt;span style="color: #000000;"&gt;name&lt;/span&gt;&lt;span style="color: #008000;"&gt;}&lt;/span&gt;&lt;span style="color: #dd0000;"&gt;.svg" &lt;/span&gt;
&lt;span style="color: #dd0000;"&gt;              type='image/svg+xml' width='100%' height='400'&lt;/span&gt;
&lt;span style="color: #dd0000;"&gt;              style='background: url(/images/big_top_gradient.png);' /&amp;#62;&lt;/span&gt;
&lt;span style="color: #dd0000;"&gt;      &lt;/span&gt;&lt;span style="color: #008000;"&gt;)&lt;/span&gt;
&lt;span style="color: #000000;"&gt;  &lt;/span&gt;&lt;span style="font-weight: bold;color: #000000;"&gt;end&lt;/span&gt;
&lt;span style="color: #888888;"&gt;...&lt;/span&gt;&lt;/pre&gt;  

  &lt;p&gt;That action generates a new SVG file, then &lt;code&gt;render&lt;/code&gt;s it in
  an embedded SVG viewer.&lt;/p&gt;
  
  &lt;p&gt;To collect the ratio of test to production code, we pass in a lambda that 
  calculates this. Note that Gnuplot has a &amp;#8220;math nanny&amp;#8221; feature, which rejects
  logarithmic scales with input data at 0. A finished project would find a way to
  clip these points.&lt;/p&gt;

  &lt;h3&gt;To Do&lt;/h3&gt;
  
  &lt;p&gt;This simple project suggests many cleanups and extensions. We might&amp;#8230;
  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;link&lt;/strong&gt; the dashboard page to our URIs, such as &lt;a href='http://localhost:3333/projects/statistics/lemmings'
&gt;http://localhost:3333/projects/statistics/lemmings&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;productize&lt;/strong&gt; the code as a plugin&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;configure&lt;/strong&gt; different projects with different signals&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;annotate&lt;/strong&gt; a chart with significant integration messages&lt;/li&gt;
    &lt;li&gt;featurize the chart with &lt;strong&gt;time ranges&lt;/strong&gt; &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;stack&lt;/strong&gt; several charts, with different signals in each one&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;roll-up&lt;/strong&gt; all the statistics into a team&amp;#8217;s summaries.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;/p&gt;
  
  &lt;p&gt;The ultimate goal is a &lt;a 
  href='http://www.google.com/search?q=%22big+visible+charts%22'&gt;Big 
  Visible Chart&lt;/a&gt;, or &lt;a 
  href='http://www.google.com/search?q=%22information+radiator%22'&gt;Information 
  Radiator&lt;/a&gt;, for CruiseControl.rb projects.&lt;/p&gt;    </content>
</entry>
<entry>
    <title>Laurent is a champ!</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/2008/03/laurent_is_a_champ_1.html" />
    <id>tag:www.oreillynet.com,2008:/ruby/blog//3.23239</id>
    
    <published>2008-03-14T19:31:58Z</published>
    <updated>2008-03-16T19:10:17Z</updated>
    
    <summary>Though I&apos;m typing this from a Mac right now, I&apos;m hardly a fan boy. I spend a little less than half of my time on OS X, the rest spent on cheap PC hardware and ArchLinux, and honestly, I&apos;m happy...</summary>
    <author>
        <name>Gregory Brown</name>
            </author>
            <category term="Opinion" />
        <content type="html">
&lt;p&gt;Though I&amp;#8217;m typing this from a Mac right now, I&amp;#8217;m hardly a fan boy.  I spend a little less than half of my time on OS X, the rest spent on cheap PC hardware and ArchLinux, and honestly, I&amp;#8217;m happy with both (each for different reasons)&lt;/p&gt;
&lt;p&gt;Still, Apple definitely got some things right, and not the least of which was hiring &lt;a href="http://chopine.be/lrz/"&gt;Laurent Sansonetti&lt;/a&gt;.    He&amp;#8217;s the guy who&amp;#8217;s been working on cool projects such as &lt;a href="http://rubycocoa.sf.net/"&gt;RubyCocoa&lt;/a&gt; and &lt;a href="http://rubyosa.rubyforge.org/"&gt;RubyOSA&lt;/a&gt;, and now has just released &lt;a href="http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/294485"&gt;MacRuby&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;MacRuby sounds cool:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;
MacRuby is a version of Ruby that runs on top of Objective-C. More precisely, MacRuby is currently a port of the Ruby 1.9 implementation for the Objective-C runtime and garbage collector.
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Though I&amp;#8217;m not actively doing Mac specific Ruby development, that doesn&amp;#8217;t mean I can&amp;#8217;t extend thanks to a very cool hacker who has been dropping cool free software projects on our community like it was his job&amp;#8230;. oh wait&amp;#8230; it is. :)&lt;/p&gt;
&lt;p&gt;Thanks Laurent for your contributions!  You&amp;#8217;re one of the reasons why the Ruby community rocks.&lt;/p&gt;
    </content>
</entry>
<entry>
    <title>The Ruby Mendicant Project</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/2008/03/the_ruby_mendicant_project.html" />
    <id>tag:www.oreillynet.com,2008:/ruby/blog//3.23209</id>
    
    <published>2008-03-11T17:37:53Z</published>
    <updated>2008-03-11T19:55:20Z</updated>
    
    <summary>When I wrote the post I&apos;d love to quit my job! (sort of), I was mainly aiming to start a conversation. Still, enough people took me seriously, and now I can announce that the project is officially under way. I&apos;m...</summary>
    <author>
        <name>Gregory Brown</name>
            </author>
        <content type="html">
&lt;p&gt;When I wrote the post &lt;a href="http://www.oreillynet.com/ruby/blog/2008/03/id_love_to_quit_my_job_sort_of.html"&gt;I&amp;#8217;d love to quit my job! (sort of)&lt;/a&gt;, I was mainly aiming to start a conversation.&lt;/p&gt;
&lt;p&gt;Still, enough people took me seriously, and now I can announce that the project is officially under way.  I&amp;#8217;m in the fund raising stages to collect $8000 so that I can take off 6 months of work to focus on important Ruby open source projects.   That sounds even lower than my initial estimate, and the reason for that is because Ruby Central, Inc. decided to back the project!  Here&amp;#8217;s their press release:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;
RUBY CENTRAL, INC. WILL MATCH FUNDS DONATED TO GREG BROWN, &lt;br /&gt; UP TO A TOTAL OF $5,000&lt;/p&gt;
&lt;p&gt;Ruby Central is always on the lookout for interesting ways to help and&lt;br /&gt;
support the Ruby community, and Greg Brown has provided us with one. &lt;/p&gt;
&lt;p&gt;Greg has announced a unique plan for himself. You can see the whole plan at&lt;br /&gt;
http://rubymendicant.wikidot.com/proposal; meanwhile, here&amp;#8217;s Greg&amp;#8217;s capsule&lt;br /&gt;
summary of the idea:&lt;/p&gt;
&lt;p&gt;  ** Through the donations of community members, I would like to put my&lt;br /&gt;
  commercial and personal software projects on the back burner for a&lt;br /&gt;
  prolonged period of time (3-6 months), and focus on working on open&lt;br /&gt;
  source projects that are of high importance to the majority of&lt;br /&gt;
  Rubyists. **&lt;/p&gt;
&lt;p&gt;Greg has already started to receive donations. And now, Ruby Central&lt;br /&gt;
is coming on board to help Greg with matching funds. &lt;/p&gt;
&lt;p&gt;Ruby Central will match gross donations to Greg, dollar for dollar, up&lt;br /&gt;
to $5000 (five thousand) dollars. That means that if you donate $50 to&lt;br /&gt;
Greg&amp;#8217;s effort, we&amp;#8217;ll donate $50. We&amp;#8217;ll stick to the same timeline that Greg&lt;br /&gt;
has set for himself (see the above URL). &lt;/p&gt;
&lt;p&gt;It&amp;#8217;s not quite like anything we&amp;#8217;ve done before, but it sounds like a good idea&lt;br /&gt;
at the right time, and we&amp;#8217;re happy to support Greg&amp;#8217;s unusual and imaginative&lt;br /&gt;
endeavor. &lt;/p&gt;
&lt;p&gt;Please keep in mind that donations to Greg are not donations to Ruby&lt;br /&gt;
Central, and therefore do not fall under our tax-exempt status. &lt;/p&gt;
&lt;p&gt;Ruby Central, Inc. is a 501(c)(3) tax-exempt organization, chartered to&lt;br /&gt;
support Ruby-related events and initiatives that benefit the Ruby community.
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Peter Cooper has also decided to support the project by both donating, and posting &lt;a href="http://www.rubyinside.com/help-fund-a-ruby-developer-to-work-on-open-source-for-6-months-798.html"&gt;a summary&lt;/a&gt; on Ruby Inside.   He does a good job of covering the whole of the story, including relating to those who feel uncomfortable supporting such an effort.  I totally understand people&amp;#8217;s skepticism, and thats why I encourage people to come talk on the &lt;a href="http://groups.google.com/group/rubymendicant"&gt;RubyMendicant google group&lt;/a&gt; if they have any doubts about the project they want cleared up.  The explicit timeline for this project and the fact that I&amp;#8217;ve promised to record public hours will hopefully put some folks minds at ease.&lt;/p&gt;
&lt;p&gt;Peter mentions that I talked about working on PDF::Writer or Ruport, but this doesn&amp;#8217;t really fully explain what I was thinking of doing.   With respect to PDF::Writer, I was pondering a from scratch rewrite with a sexy API and less performance problems.   With Ruport, I was going to work on 1.9 compatibility for all of its dependencies across ruport-util and acts_as_reportable.   Though I haven&amp;#8217;t checked in with the maintainers of these libraries yet, it&amp;#8217;s truly a laundry list of general purpose gems I&amp;#8217;d be working to update.  Here are just a few:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RubyDBI&lt;/li&gt;
&lt;li&gt;Gruff&lt;/li&gt;
&lt;li&gt;Scruffy&lt;/li&gt;
&lt;li&gt;PDF::Writer&lt;/li&gt;
&lt;li&gt;transaction-simple&lt;/li&gt;
&lt;li&gt;roo&lt;/li&gt;
&lt;li&gt;rubyzip&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Outside of Ruport, there are lots of libraries that I&amp;#8217;d like to see working on 1.9, and with enough time, I could work on porting them.&lt;/p&gt;
&lt;p&gt;The &lt;a href="http://rubymendicant.wikidot.com/project-ideas"&gt;ideas list&lt;/a&gt; will be open for suggestions until April 1st when the donations close, the goal is to focus on things that might be hard to reach from traditional open source work that is done in people&amp;#8217;s spare time or as part of a commercial project.&lt;/p&gt;
&lt;p&gt;Anyway, this is going to be my last mooching attempt on the O&amp;#8217;Reilly blog for this project.  If you&amp;#8217;ve read through &lt;a href="http://rubymendicant.wikidot.com/proposal"&gt;the proposal&lt;/a&gt; I&amp;#8217;ve put together and want to support the project, please consider &lt;a href="http://pledgie.com/campaigns/571"&gt;a donation&lt;/a&gt;.  If you&amp;#8217;ve got questions, please &lt;a href="http://groups.google.com/group/rubymendicant"&gt;ask them&lt;/a&gt;.  And perhaps most importantly: If you like this idea, please spread the word.  Having Rubyists show that they back this effort is worth more than cash to me, because it gives me confidence that this might actually work.&lt;/p&gt;
&lt;p&gt;At the time of writing, with the matching from Ruby Central, Inc., I have enough money for at least &lt;strike&gt;two weeks&lt;/strike&gt; one month of work.  That means this project is a &amp;#8216;go&amp;#8217; even if no one else donates.  Of course, a lot more can be done with 26 weeks than with two. &lt;/p&gt;
&lt;p&gt;The next post about this you&amp;#8217;ll see here from me is going to be a progress report of what I&amp;#8217;ve been working on.   Until then, thanks so much to those who have supported and will support this project.  I think it&amp;#8217;ll be a great experiment, and that good things will come from it.&lt;/p&gt;
    </content>
</entry>
<entry>
    <title>Beast acts_as_sphinx</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/2008/03/beast_acts_as_sphinx.html" />
    <id>tag:www.oreillynet.com,2008:/ruby/blog//3.23128</id>
    
    <published>2008-03-04T04:06:22Z</published>
    <updated>2008-03-05T15:09:26Z</updated>
    
    <summary>This project upgrades an online forum to add a search engine, using 
Test 
Driven Development. Our tools are 
RoR&apos;s 
Beast, 
Sphinx, and
assert{ 2.0 }.</summary>
    <author>
        <name>Phlip</name>
        <uri>http://www.oreilly.com/catalog/9780596510657/</uri>    </author>
            <category term="Technical" />
        <content type="html">
&lt;p&gt;This project upgrades an online forum to add a search 
  engine, using 
  &lt;a href="http://c2.com/cgi/wiki?TestDrivenDevelopment"&gt;Test 
  Driven Development&lt;/a&gt;. Our tools are 
  &lt;a href="http://www.rubyonrails.org/"&gt;RoR&lt;/a&gt;&amp;#8217;s 
  &lt;a href="http://beast.caboo.se/"&gt;Beast&lt;/a&gt;, 
  &lt;a href="http://www.sphinxsearch.com/"&gt;Sphinx&lt;/a&gt;, and (naturally) 
  &lt;a href="http://assert2.rubyforge.org/"&gt;&lt;code&gt;assert{ 2.0 }&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

  &lt;p&gt;We follow this 
  &lt;a href="http://en.wikipedia.org/wiki/Model-view-controller"&gt;MVC&lt;/a&gt; 
  guideline:

  &lt;blockquote&gt;
    Anything a user can do to the data through the Views,&lt;br/&gt;
    a unit test can do, the same way, through the Models
  &lt;/blockquote&gt;

  Our test cases simulate a user searching.&lt;/p&gt;

&lt;p&gt;Adding a
  View to this project is left as an exercise for the reader. 
  It will be Rails-easy.&lt;/p&gt;

  &lt;p&gt;Start by learning to install Sphinx, at 
  &lt;a href="http://www.datanoise.com/articles/2007/3/23/acts_as_sphinx-plugin"&gt;Data 
  Noise: acts_as_sphinx plugin&lt;/a&gt;.&lt;/p&gt;
  &lt;h3&gt;&lt;code&gt;sphinx.conf&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;A query like this provides a Sphinx index:
 
&lt;pre&gt;  SELECT posts.id     as id, 
         topics.title as title,
         posts.body   as body, 
         users.login  as user, 
         forums.name  as forum,
         topics.hits  as hits 
    FROM posts 
    LEFT OUTER JOIN topics ON topics.id = posts.topic_id 
    LEFT OUTER JOIN users  ON  users.id = posts.user_id
    LEFT OUTER JOIN forums ON forums.id = posts.forum_id&lt;/pre&gt; 

That yields a table like this:&lt;/p&gt;

  &lt;blockquote&gt;&lt;table&gt;
    &lt;thead&gt;
      &lt;tr align='left'&gt;
      &lt;th&gt;id&lt;/th&gt;
      &lt;th&gt;title&lt;/th&gt;
      &lt;th&gt;body&lt;/th&gt;
      &lt;th&gt;user&lt;/th&gt;
      &lt;th&gt;forum&lt;/th&gt;
      &lt;th&gt;hits&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;

&lt;TR&gt;&lt;TD&gt;1&lt;/TD&gt;
&lt;TD&gt;PDI this!&lt;/TD&gt;
&lt;TD&gt;P D I pdi&lt;/TD&gt;
&lt;TD&gt;aaron&lt;/TD&gt;
&lt;TD&gt;rails&lt;/TD&gt;
&lt;TD&gt;0&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;&lt;TD&gt;2&lt;/TD&gt;
&lt;TD&gt;PDI this!&lt;/TD&gt;
&lt;TD&gt;what? pdi&lt;/TD&gt;
&lt;TD&gt;sam&lt;/TD&gt;
&lt;TD&gt;rails&lt;/TD&gt;
&lt;TD&gt;0&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;&lt;TD&gt;3&lt;/TD&gt;
&lt;TD&gt;PDI this!&lt;/TD&gt;
&lt;TD&gt;you heard me pdi&lt;/TD&gt;
&lt;TD&gt;aaron&lt;/TD&gt;
&lt;TD&gt;rails&lt;/TD&gt;
&lt;TD&gt;0&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;&lt;TD&gt;6&lt;/TD&gt;
&lt;TD&gt;Galactus is coming&lt;/TD&gt;
&lt;TD&gt;galactus gah-lac-tus&lt;/TD&gt;
&lt;TD&gt;aaron&lt;/TD&gt;
&lt;TD&gt;comics&lt;/TD&gt;
&lt;TD&gt;42&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;&lt;TD&gt;7&lt;/TD&gt;
&lt;TD&gt;Galactus is coming&lt;/TD&gt;
&lt;TD&gt;Galactus oh no!&lt;/TD&gt;
&lt;TD&gt;sam&lt;/TD&gt;
&lt;TD&gt;comics&lt;/TD&gt;
&lt;TD&gt;42&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;&lt;TD&gt;8&lt;/TD&gt;
&lt;TD&gt;Agent of SHIELD&lt;/TD&gt;
&lt;TD&gt;agent of SHIELD&lt;/TD&gt;
&lt;TD&gt;sam&lt;/TD&gt;
&lt;TD&gt;comics&lt;/TD&gt;
&lt;TD&gt;19&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;&lt;TD&gt;9&lt;/TD&gt;
&lt;TD&gt;Agent of SHIELD&lt;/TD&gt;
&lt;TD&gt;Blah Blah&lt;/TD&gt;
&lt;TD&gt;aaron&lt;/TD&gt;
&lt;TD&gt;comics&lt;/TD&gt;
&lt;TD&gt;19&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;&lt;TD&gt;10&lt;/TD&gt;
&lt;TD&gt;il8n in rails?&lt;/TD&gt;
&lt;TD&gt;il8n in rails!&lt;/TD&gt;
&lt;TD&gt;aaron&lt;/TD&gt;
&lt;TD&gt;rails&lt;/TD&gt;
&lt;TD&gt;0&lt;/TD&gt;
&lt;/TR&gt;
&lt;tr&gt;&lt;td colspan='6'&gt;&lt;em&gt;etc&amp;#8230;&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;&lt;/blockquote&gt;
  
  &lt;p&gt;That&amp;#8217;s Beast&amp;#8217;s Post fixture data. Our test cases will lead Sphinx to 
  return results from them. To generate
  that big &lt;code&gt;SELECT&lt;/code&gt; statement, I wrote this little test case:
  &lt;pre&gt;
  def test_pre_sphinx
    posts = Post.find(:all, :include =&amp;#62; [:topic, :user, :forum])
    assert 'keep the fixture system on its toes!' do posts.any? end
  end
&lt;/pre&gt;
  Then I read the &lt;code&gt;test.log&lt;/code&gt;, and copied out the list of 
  &lt;code&gt;LEFT OUTER JOIN&lt;/code&gt;&lt;small&gt;s&lt;/small&gt; 
  required to write that big query.
  (Incidentally, other Sphinx plugins, such as 
  &lt;a href="http://agilewebdevelopment.com/plugins/ultrasphinx"&gt;Ultrasphinx&lt;/a&gt;, 
  read your ActiveRecord associations for you, so they might simplify this
  step&amp;#8230;).&lt;/p&gt;

  &lt;p&gt;Now copy the &lt;code&gt;SELECT&lt;/code&gt; statement into your 
  &lt;code&gt;config/sphinx.conf&lt;/code&gt; file, and use it 
  to index and start Sphinx:&lt;/p&gt;

&lt;pre&gt;source posts
{
    type            = mysql
    sql_host        = 127.0.0.1
    sql_user        = root
    sql_pass        = 
    sql_db          = beast_dev
    sql_sock        = /tmp/mysql.sock

    sql_query       = \
      SELECT posts.id     as id,   \
            topics.title as title, \
            posts.body   as body,  \
            users.login  as user,  \
            forums.name  as forum, \
            topics.hits  as hits   \
        FROM posts                  \
        LEFT OUTER JOIN topics ON topics.id = posts.topic_id \
        LEFT OUTER JOIN users  ON  users.id = posts.user_id  \
        LEFT OUTER JOIN forums ON forums.id = posts.forum_id 

    sql_attr_uint = hits  #  for sort_attr
}

index posts
{
    source          = posts
    path            = ../tmp/sphinx/posts
    morphology      = stem_en
}&lt;/pre&gt;

&lt;p&gt;The line &lt;code&gt;sql_attr_uint&lt;/code&gt; declares we 
can sort results by &lt;code&gt;hits&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;&lt;code&gt;ask_sphinx&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Now add &lt;code&gt;acts_as_sphinx&lt;/code&gt; to the 
&lt;code&gt;Post&lt;/code&gt; model, 
and write a test to find some sample records:&lt;/p&gt;

&lt;pre&gt;require 'fileutils'

class PostTest &lt; Test::Unit::TestCase
  include FileUtils
  all_fixtures
  
  def test_aardvark
    system 'rake RAILS_ENV=development db:fixtures:load'
    cd 'config' do
        #  index our fixtures before a test run!
      indexer_response = `indexer --rotate --all --quiet`
      assert{ indexer_response == '' }
    end
    sleep 0.7
  end
  
  def test_ask_sphinx
    response = Post.ask_sphinx('pdi')
    assert{ response[:total] == 3 }
    sphinx_found = response[:matches].keys  # &lt;-- the post IDs!
    posts = Post.find(:all, :conditions =&gt; 'body like "%pdi%"')
    mysql_found = posts.map(&amp;:id)
    assert{ sphinx_found.to_set == mysql_found.to_set }
  end
&lt;span style='color: gray'&gt;...&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;test_aardvark&lt;/code&gt; case runs before all the others. Sphinx likes 
&lt;a href="http://www.google.com/search?q=site%3Aoreillynet.com+mysql"&gt;MySQL&lt;/a&gt;, but 
Beast tests like &lt;a href="http://www.google.com/search?q=sqlite3+rails"&gt;Sqlite3&lt;/a&gt;. 
They must meet each other halfway, to 
ensure any changes to our fixtures immediately appear in our 
Sphinx index. After our &lt;code&gt;fixture&lt;/code&gt; directive pushes
our fixtures into the &lt;code&gt;beast_dev&lt;/code&gt; database, we invoke the 
indexer, and wait for it to &lt;code&gt;SIGHUP&lt;/code&gt; the 
&lt;code&gt;searchd&lt;/code&gt; process.&lt;/p&gt;

&lt;p&gt;(Suggestions for better techniques are welcome!)&lt;/p&gt;

&lt;p&gt;The test itself finds the three posts that discuss &amp;#8220;PDI&amp;#8221; - 
whatever that is. We then challenge Sphinx by finding the posts ourselves, 
and compare each set of returned IDs.&lt;/p&gt;

&lt;h3&gt;&lt;code&gt;find_with_sphinx&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Now we clone the test, and modify it to use &lt;code&gt;find_with_sphinx&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;  def test_find_with_sphinx
    seek = 'agent of shield'
    sphinx_posts = Post.find_with_sphinx(seek)
    assert{ sphinx_posts.total == 2 }  #  &lt;-- find_with_sphinx provides that accessor
    best_match = sphinx_posts[0]
    _2nd_match = sphinx_posts[1]
    sought = /#{seek}/i
    assert{ best_match.topic.title =~ sought and best_match.body =~ sought }
    assert{ _2nd_match.topic.title =~ sought and _2nd_match.body !~ sought }
  end&lt;/pre&gt;

&lt;p&gt;When you clone a test, sometimes you should change its assembled 
sample data. Test cases should diversify, to extend their coverage.&lt;/p&gt;

&lt;p&gt;The test finds two posts. The first one has "agent of SHIELD" in both its topic 
and body, and the second has it only in its title.&lt;/p&gt;

&lt;h3&gt;&lt;code&gt;sort_mode&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Next, suppose our boss requests us to find posts in order of popularity.
Beast ticks 
the &lt;code&gt;Post.hits&lt;/code&gt; attribute each time a user hits a page, but the 
&lt;code&gt;topics.yml&lt;/code&gt; file has no positive &lt;code&gt;hits&lt;/code&gt;, so 
we edit &lt;code&gt;topics.yml&lt;/code&gt;, and
declare the &lt;a href="http://en.wikipedia.org/wiki/Galactus"&gt;Galactus&lt;/a&gt; 
thread the most popular, and the 
&lt;a href="http://en.wikipedia.org/wiki/S.H.I.E.L.D."&gt;Agent of Shield&lt;/a&gt; 
thread second-most popular.&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;galactus:
  id: 6
  &lt;strong&gt;hits: 42&lt;/strong&gt;
  title: Galactus is coming
&lt;span style='color: gray'&gt;...&lt;/span&gt;
shield:
  id: 7
  &lt;strong&gt;hits: 19&lt;/strong&gt;
  title: Agent of SHIELD
&lt;span style='color: gray'&gt;...&lt;/span&gt;
il8n:
  id: 8
  &lt;strong&gt;hits: 55&lt;/strong&gt;
  title: il8n in rails?
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;To order results by the &lt;code&gt;hits&lt;/code&gt; attribute, 
we must overcome a missing feature in 
&lt;code&gt;find_by_sphinx&lt;/code&gt;. It only sorts by &lt;code&gt;weight&lt;/code&gt;, 
but we need our batch of
&lt;code&gt;Post&lt;/code&gt; records sorted by &lt;code&gt;hits&lt;/code&gt;. 
This code works around those issues:&lt;/p&gt;

&lt;pre&gt;  def sort_posts_by_hits(matches)
    posts = Post.find_all_by_id(matches.keys)
    return posts.sort_by{&amp;#124;post&amp;#124; -matches[post.id][:attrs][:hits] }
  end

  def test_sphinx_should_sort_by_hits
    popularity = { :mode =&gt; :extended, :sort_mode =&gt; [ :attr_desc, 'hits'] }
    results = Post.ask_sphinx('Galactus', popularity)
    posts   = sort_posts_by_hits(results[:matches])
    titles  = posts.map(&amp;:topic).map(&amp;:title).uniq
    assert{ titles == ['il8n in rails?', 'Galactus is coming'] }
  end&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;sphinx.conf&lt;/code&gt; line &lt;code&gt;sql_attr_uint = hits&lt;/code&gt; registered 
&lt;code&gt;hits&lt;/code&gt; as an &amp;#8220;attribute&amp;#8221; (not a searchable), 
so we can sort by it. But &lt;code&gt;find_with_sphinx&lt;/code&gt; did not sort using that
variable.&lt;/p&gt;

&lt;p&gt;We have now run off the end of &lt;code&gt;acts_as_sphinx&lt;/code&gt;&amp;#8217;s 
feature set. I chose it because it runs closest to the raw 
Sphinx.&lt;/p&gt;

&lt;p&gt;That &lt;code&gt;ask_sphinx&lt;/code&gt; call sorts our code by hits, but our imaginary boss is not satisfied yet!&lt;/p&gt;

&lt;h3&gt;&lt;code&gt;SPH_SORT_EXPR&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Our boss requests that matches in a &lt;code&gt;Topic.title&lt;/code&gt; rank higher 
than popular posts without matches in the title. That leads to this stray &amp;#8220;Galactus&amp;#8221;,
in an unrelated thread, in &lt;code&gt;posts.yml&lt;/code&gt;&amp;#8230;&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;il8n:
  id: 10
  forum_id: 1  &lt;%# rails, not comics! %&gt;
  body: il8n in rails! &lt;strong&gt;Galactus!&lt;/strong&gt;
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;&amp;#8230;and this failing test:&lt;/p&gt;

&lt;pre&gt;  def test_sphinx_should_sort_by_relevant_hits
    popularity = { :mode =&gt; :extended, :sort_mode =&gt; [:attr_desc, 'hits'] }
    results = Post.ask_sphinx('Galactus', popularity)
    posts   = sort_posts_by_hits(results[:matches])
    titles  = posts.map(&amp;:topic).map(&amp;:title).uniq
    assert{ titles == ['Galactus is coming', 'il8n in rails?'] }
  end&lt;/pre&gt;

&lt;p&gt;The test fails with this diagnostic; the posts are out of order:

&lt;pre&gt;assert{ &lt;strong&gt;titles == ["Galactus is coming", "il8n in rails?"]&lt;/strong&gt; }    --&gt; false - should pass
    titles --&gt; &lt;strong&gt;["il8n in rails?", "Galactus is coming"]&lt;/strong&gt;&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;To pass that test, you may need to first 
edit &lt;code&gt;sphinx.rb&lt;/code&gt;, and add this line:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;  SPH_SORT_RELEVANCE     = 0
  SPH_SORT_ATTR_DESC     = 1
  SPH_SORT_ATTR_ASC      = 2
  SPH_SORT_TIME_SEGMENTS = 3
  SPH_SORT_EXTENDED      = 4
  &lt;strong&gt;SPH_SORT_EXPR          = 5&lt;/strong&gt;
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;Upgrade the test like this:&lt;/p&gt;

&lt;pre&gt;  def sort_posts_by_&lt;strong&gt;weight&lt;/strong&gt;(matches)
    posts = Post.find_all_by_id(matches.keys)
    return posts.sort_by{&amp;#124;post&amp;#124; -matches[post.id][&lt;strong&gt;:weight&lt;/strong&gt;] }
  end
  
  def test_sphinx_should_sort_by_relevant_hits
    popularity = { :mode =&gt; :extended, 
                   &lt;strong&gt;:weights =&gt; [10, 1],
                   :sort_mode =&gt; [:expr, '@weight + hits * 1000']&lt;/strong&gt; }
    results = Post.ask_sphinx('Galactus', popularity)
    posts   = sort_posts_by_&lt;strong&gt;weight&lt;/strong&gt;(results[:matches])
    titles  = posts.map(&amp;:topic).map(&amp;:title).uniq
    assert{ titles[0..1] == ['Galactus is coming', 'il8n in rails?'] }
  end&lt;/pre&gt;

&lt;h3&gt;To Do&lt;/h3&gt;

&lt;p&gt;Now that our tests have taught us how to operate Sphinx, we can 
use Extract Method Refactor to pull out the code we want to use in 
production.&lt;/p&gt;

&lt;p&gt;The next Sphinx feature to explore is its query language. Using 
&lt;code&gt;:mode =&gt; :extended&lt;/code&gt;, a search term of &amp;#8220;Galactus @user (sam)&amp;#8221;
will filter out other users.&lt;/p&gt;

&lt;p&gt;The interfaces between Sphinx and Ruby are young and growing, and
these testing techniques should help them grow!&lt;/p&gt;

&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;    </content>
</entry>
<entry>
    <title>I&apos;d love to quit my job! (sort of)</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/2008/03/id_love_to_quit_my_job_sort_of.html" />
    <id>tag:www.oreillynet.com,2008:/ruby/blog//3.23115</id>
    
    <published>2008-03-03T17:31:50Z</published>
    <updated>2008-03-10T03:13:27Z</updated>
    
    <summary>UPDATE: Okay kids, now it&apos;s 50% fantasy. It&apos;s up to you to cover the other 50% by donating and spreading the word, after reading my proposal. Here&apos;s hoping for the best! UPDATE: Though my original post was 95% a fantasy,...</summary>
    <author>
        <name>Gregory Brown</name>
            </author>
            <category term="Opinion" />
        <content type="html">
&lt;p&gt;&lt;em&gt;UPDATE: Okay kids, now it&amp;#8217;s 50% fantasy.  It&amp;#8217;s up to you to cover the other 50% by &lt;a href="http://pledgie.com/campaigns/571"&gt;donating&lt;/a&gt; and spreading the word, after reading my &lt;a href="http://rubymendicant.wikidot.com/proposal"&gt;proposal&lt;/a&gt;.  Here&amp;#8217;s hoping for the best!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;UPDATE: Though my original post was 95% a fantasy, I&amp;#8217;ve received some funding offers that have brought it down to 75% fantasy.  I will be documenting any planning I&amp;#8217;m doing towards via a wiki called &lt;a href="http://rubymendicant.wikidot.com/"&gt;RubyMendicant&lt;/a&gt;.  If you&amp;#8217;d like to follow this on the bleeding edge, keep an eye out on that wiki.  Otherwise, if you hear an official announcement within the next few weeks, you&amp;#8217;ll know I decided to take the plunge, and if you don&amp;#8217;t, then it&amp;#8217;s safe to assume this idea went the way of the dodo.  If you like this idea, please spread the word through the usual means of the intertubes by sharing this post and the wiki link with others.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s a crazy idea I just had, and I&amp;#8217;m wondering what folks think about it.&lt;/p&gt;
&lt;p&gt;People do open source for a lot of reasons, ranging from pragmatic to idealistic.  Some write a patch every six months or so, others do what they can to dedicate their life to it.  Though I try to have a life outside of software, I&amp;#8217;m definitely more on the obsessed end of the spectrum when it comes to contributing to open source software.&lt;/p&gt;
&lt;p&gt;I find myself in a rather unique situation:  Single, living alone in a small studio apartment, only taking a class or two here and there, and basically living off of small contracts.  It&amp;#8217;s not that I&amp;#8217;m not offered big gigs, or that I couldn&amp;#8217;t go back to school full time if I really wanted to, I just find I enjoy living a simple lifestyle that lets me spend a lot of time on community oriented projects, especially Ruby stuff.&lt;/p&gt;
&lt;p&gt;Right now, I need to do some work each month to pay the rent, and slowly save up to make sure I don&amp;#8217;t get evicted during a slow work month.  Between BTree and Madriska, I could say that I have two of the most open source friendly commercial relationships I&amp;#8217;ve ever seen.  Though I&amp;#8217;m working on real projects with them, things that have to actually fit some sort of business need, they give me a lot of leeway to improve open source software while working on them, Ruport is pushed along heavily by this.&lt;/p&gt;
&lt;p&gt;I could see myself doing that for a while.  Working with a few different clients I trust, who in turn hook me up with interesting projects for a variety of companies in various different domains.  A lot of it might be Rails work, but not all of it is.  Still, in a moment of idealistic fantasy, I thought of another idea:&lt;/p&gt;
&lt;p&gt;What if I could just do open source for a while, non-commercially?&lt;/p&gt;
&lt;p&gt;How much would it cost for me to do at least 80 hours a month of development on software projects such as PDF::Writer, Ruport, and some other projects I wish I had the time to get my hands on?&lt;/p&gt;
&lt;p&gt;I did the math, and the number came out low (subjective).  I could meet all my expenses and save some money for about $2000 a month.  Basically, if 200 people donated $60 right now, I could take 6 months off and do nearly 500 hours of work, and that&amp;#8217;s only if I didn&amp;#8217;t find myself obsessed with and doing extra hours on a project.  I could more-or-less maintain my lifestyle that I have now, but not take on contracting projects that are either too big or too small out of necessity.  Sure, this works out to be a lot lower than my contracting rate, but I could hack entirely on open source projects, maybe write some documentation and articles, and still be able to afford a class or two a semester.  Sounds beautiful to me, though I&amp;#8217;m sure it&amp;#8217;s just a fantasy.&lt;/p&gt;
&lt;p&gt;Indulging me for the moment,  how would I remain accountable to anyone who supported such a venture?   I&amp;#8217;d make it transparent as possible.   I&amp;#8217;d record public hours, with links to changesets, tickets, blog posts, whatever.   Though people would have to accept good faith (with at least a roughly outlined plan) as to &amp;#8216;where I direct the time&amp;#8217;, they&amp;#8217;d get to see every bit of &amp;#8216;where the hours went&amp;#8217;.&lt;/p&gt;
&lt;p&gt;What prevents this from being a total scam?  You do.   Though I don&amp;#8217;t have some A-List reputation, I still make my living based on my reputation as a developer and a contractor.  If I somehow totally screwed people who supported such an effort, all it would take would be enough negative feedback from the community to prevent me from getting away with dishonesty.&lt;/p&gt;
&lt;p&gt;What would indicate a great success?  If after 6 months, this all worked out, and I was interested in doing it for another 6 months, if people funded it, we&amp;#8217;d know they actually liked what I did the first time around.&lt;/p&gt;
&lt;p&gt;Finally, this doesn&amp;#8217;t have to be me.  It could be any old hacker you choose, someone you trust that&amp;#8217;s working on things you&amp;#8217;re interested in.  They&amp;#8217;d tell you how much it&amp;#8217;d cost to have them quit their day job for 6 months, and hopefully people could pool resources.  Though the open source community is kept alive by small day to day contributions, we all know the power of having someone dedicated to a project with copious free time.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m talking in theory, because obviously there are some complications.  If I personally were to do this, I&amp;#8217;d need to cycle out of some projects, and figure out to what extent it&amp;#8217;d piss of the people I work with.  Still, I am sort of curious, is this an idea that belongs in the trashbin, or should I open up a pledgie account for donations? :)&lt;/p&gt;
&lt;p&gt;Maybe this is something that could be done on a trial basis,  such as &amp;#8216;40 hrs over 1 month&amp;#8217;.   This is something I could do without putting a close to all my work.  Given that, based on my needs (not my billable rate), that&amp;#8217;d be um&amp;#8230; 10 dollars from 100 people?&lt;/p&gt;
&lt;p&gt;On the one hand, this seems almost like a joke to me, a sort of &amp;#8216;wouldn&amp;#8217;t it be nice if&amp;#8230;&amp;#8217;.  Still, if some respected Rubyist wanted to steal this idea, undercut me on the price, and ask people to start donating, would I be among them?   You bet ya.&lt;/p&gt;
&lt;p&gt;Let me know what you think.  I&amp;#8217;ve tried so hard over the last few years to find ethical and practical ways to work in open source development, and they pretty much work.   But because of that, I&amp;#8217;ve mostly ignored the idealistic ones, and this is just a shot in the dark at one of those.&lt;/p&gt;
    </content>
</entry>
<entry>
    <title>Structured. Warnings. Now.</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/2008/02/structured_warnings_now.html" />
    <id>tag:www.oreillynet.com,2008:/ruby/blog//3.23045</id>
    
    <published>2008-02-21T02:54:49Z</published>
    <updated>2008-02-21T03:05:12Z</updated>
    
    <summary>The Problem I find Ruby&apos;s current warning system, if you can call it that, lacking. Warnings are controlled by the -W flag on the command line, and are generated via the Kernel#warn method within code. There are a host of...</summary>
    <author>
        <name>Daniel Berger</name>
            </author>
            <category term="Opinion" />
        <content type="html">
&lt;h3&gt;The Problem&lt;/h3&gt;
&lt;p&gt;I find Ruby&amp;#8217;s current warning system, if you can call it that, lacking. Warnings are controlled by the -W flag on the command line, and are generated via the Kernel#warn method within code. There are a host of problems with this approach to warnings.&lt;/p&gt;
&lt;p&gt;
First, warnings aren&amp;#8217;t currently testable. With Test::Unit, for example, I can ensure that specific errors are raised in certain conditions via the &lt;i&gt;assert_raise&lt;/i&gt; method. There is no analogue for warnings. It would be nice if there were so I could test them.&lt;/p&gt;
&lt;p&gt;
Second, there is no backtrace information provided with warnings. If I discover a warning I have to wade through the source and figure out where it was generated, because a Kernel#warn call does not provide a line number or method name that I can refer back to.&lt;sup&gt;1&lt;/sup&gt; For large code bases that can be problematic and generally annoys me.&lt;/p&gt;
&lt;p&gt;
Third, and most significantly, with warning flags it&amp;#8217;s all or nothing. I cannot enable or disable specific kinds of warnings. Perl, on the other hand, implements warning control through pragmas. So, for example, I can specify &amp;#8220;no warnings uninitialized&amp;#8221; in a Perl program and warnings about uninitialized variables go away.&lt;sup&gt;2&lt;/sup&gt; With Ruby it&amp;#8217;s off, on, or even-more-on (-W0, -W1 or -W2).&lt;/p&gt;
&lt;p&gt;&lt;h3&gt;The Solution&lt;/h3&gt;
&lt;p&gt;
One of the things I&amp;#8217;ve pushed for in the past in Ruby is structured warnings.&lt;sup&gt;3&lt;/sup&gt; By &amp;#8217;structured warnings&amp;#8217; I mean a system analogous to the Error class, except that a warning would only emit text to STDERR, not cause the interpreter to exit. In our hypothetical Warning class you still have backtrace information available. And, like Exceptions, there would be a standard hierarchy, with Warning at the top, StandardWarning, UninitializedWarning, RedefinedMethodWarning, DeprecatedMethodWarning, etc. Whatever we can think of.&lt;/p&gt;
&lt;p&gt;
Such a system would allow you to raise specific warnings within your code:&lt;/p&gt;
&lt;pre&gt;
   class Foo
      def old_method
         warn DeprecatedMethodWarning, 'This method is deprecated. Use new_method instead'
         # Do stuff
      end
   end
&lt;/pre&gt;
&lt;p&gt;The ability to explicitly raise specific types of warnings then makes them testable:&lt;/p&gt;
&lt;pre&gt;
   require 'test/unit'
   class TC_Foo_Tests &lt; Test::Unit::TestCase
      def setup
         @foo = Foo.new
      end

      # Assume we've added an assert_warn method to Test::Unit
      def test_old_method
         assert_warn(DeprecatedMethodWarning){ @foo.old_method }
      end
   end
&lt;/pre&gt;
&lt;p&gt;And, for sake of backwards compatibility and convenience, a call to Kernel#warn without an explicit warning type would simply raise a StandardWarning in the same way that raise without an explicit error type raises a StandardWarning.&lt;sup&gt;4&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;
Unlike Exceptions you could permanately or temporarily disable warnings to suit your particular preferences in the system I have in mind. For example, in the win32-file library I'm well aware that I've gone and redefined some core File methods. When I run any code that uses win32-file with the -w flag, I get "method redefined" warnings. I don't want to see those because I neither need nor want to be reminded about them.&lt;/p&gt;
&lt;p&gt;
So, using our hypothetical RedefinedMethodWarning class, I could disable them like so:&lt;/p&gt;
&lt;pre&gt;
RedefinedMethodWarning.disable # No more warnings about method redefinitions!
&lt;/pre&gt;
&lt;p&gt;Or, with block syntax, we could disable a particular warning temporarily:&lt;/p&gt;
&lt;pre&gt;
# Don't bug me about deprecated method warnings within this block, I know
# what I'm doing.
#
DeprecatedMethodWarning.disable{
   [1,2,3,4,5].indexes(1,3) # Array#indexes is a deprecated method
}

# But here I would get a warning since it's outside the block:
[1,2,3,4,5].indexes(1,3)
&lt;/pre&gt;
&lt;p&gt;Unlike the current warning system, this would allow users to still receive other types of warnings, instead of the on/off switch we have now.&lt;sup&gt;5&lt;/sup&gt;And, in case you were wondering why I don&amp;#8217;t just create a &amp;#8216;warnings&amp;#8217; library that defines a bunch of warning classes and redefines Kernel#warn, the answer is that I still can&amp;#8217;t hook into the existing warnings being raised in core Ruby via &lt;i&gt;rb_warn()&lt;/i&gt;, like uninitialized variables or redefined methods.&lt;/p&gt;
&lt;p&gt;
With our warning system in place we could use it for other nefarious purposes down the road, like implementing an advisory typing system. But, I&amp;#8217;ll save that for the next post. :)&lt;/p&gt;
&lt;p&gt;
See you next Wednesday!&lt;/p&gt;
&lt;p&gt;
&lt;sup&gt;1&lt;/sup&gt;Curiously, a line number is provided by the &lt;i&gt;rb_warn()&lt;/i&gt; function, but Kernel#warn itself does not.&lt;/p&gt;
&lt;p&gt;
&lt;sup&gt;2&lt;/sup&gt;However, I do not remember if you can disable them temporarily in Perl, or if there&amp;#8217;s a way to explicitly test them. Someone feel free to fill me in.&lt;/p&gt;
&lt;p&gt;
&lt;sup&gt;3&lt;/sup&gt;http://tinyurl.com/yfko6z&lt;/p&gt;
&lt;p&gt;
&lt;sup&gt;4&lt;/sup&gt;At this point I&amp;#8217;m sure one of you is wondering about rescue/retry semantics. My opinion on the matter is that warnings should not be rescuable. They are meant to be informational. They are not meant to control program flow. This also lets us avoid having to worry about retry semantics. Not that anyone would retry based on a warning in practice.&lt;/p&gt;
&lt;p&gt;
&lt;sup&gt;5&lt;/sup&gt;Yes, there would have to be a Warning.enable method as well, for those times you want to trump some third party library that has them disabled, in a &amp;#8220;last call wins&amp;#8221; arrangement.
&lt;/p&gt;
    </content>
</entry>
<entry>
    <title>Do not question assert { 2.0 }</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/2008/02/do_not_question_assert_20.html" />
    <id>tag:www.oreillynet.com,2008:/ruby/blog//3.23007</id>
    
    <published>2008-02-14T21:52:19Z</published>
    <updated>2008-02-14T22:57:34Z</updated>
    
    <summary>Interesting: I asked a question on Phlip&apos;s last post about whether assert { 2.0 } was really necessary, given that it&apos;s mostly just assert_block from Test::Unit. I noticed this question disappeared from the post, because I guess it was offensive...</summary>
    <author>
        <name>Gregory Brown</name>
            </author>
            <category term="Opinion" />
        <content type="html">
&lt;p&gt;Interesting:&lt;/p&gt;
&lt;p&gt; I asked a question on &lt;a href="http://www.oreillynet.com/ruby/blog/2008/02/assert2.html"&gt;Phlip&amp;#8217;s last post&lt;/a&gt; about whether assert { 2.0 } was really necessary, given that it&amp;#8217;s mostly just &lt;tt&gt;assert_block&lt;/tt&gt; from Test::Unit.  I noticed this question disappeared from the post, because I guess it was offensive somehow.&lt;/p&gt;
&lt;p&gt;Though it made it through the second time around and Phlip answered reasonably, he sent me a private email explaining that he had censored me because I apparently don&amp;#8217;t &amp;#8220;get&amp;#8221; assert { 2.0 }.&lt;/p&gt;
&lt;p&gt;Not to pick a fight, but I personally believe that blog posts that have comments turned on should only be moderated for spam and/or abuse, which my comment was neither of.  If you&amp;#8217;ve got some questions about assert { 2.0 } that you&amp;#8217;d like to see stay online without silent censorship, feel free to post them here.&lt;/p&gt;
&lt;p&gt;This hopefully serves as a simple reminder that we should keep up with the &amp;#8216;open&amp;#8217; in open source, and not silence technical questions randomly.&lt;/p&gt;
    </content>
</entry>
<entry>
    <title>assert{ 2.0 }</title>
    <link rel="alternate" type="text/html" href="http://www.oreillynet.com/ruby/blog/2008/02/assert2.html" />
    <id>tag:www.oreillynet.com,2008:/ruby/blog//3.22971</id>
    
    <published>2008-02-12T03:23:53Z</published>
    <updated>2008-03-05T15:07:02Z</updated>
    
    <summary>This is a problem in all of unit testing - the cobbler&apos;s 
  own children always get the worst shoes! Our platform knows
  everything we know about assert x == 42, but it
  can&apos;t tell us everything for one reason: Languages optimize
  for the needs of production code, not test code. So our 
  customers will always get better tools than we get!

  Ruby supplies just enough reflection for an assertion to
  reconstruct a block of code.</summary>
    <author>
        <name>Phlip</name>
        <uri>http://www.oreilly.com/catalog/9780596510657/</uri>    </author>
            <category term="Technical" />
        <content type="html">
&lt;p&gt;I like developer tests, but I don&amp;#8217;t like
  the primitive assertions - &lt;code&gt;assert_equal&lt;/code&gt;,
  &lt;code&gt;assert_match&lt;/code&gt;, &lt;code&gt;assert_not_nil&lt;/code&gt;, etc. They
  only exist for one reason - to print out their input values when
  they fail. And they don&amp;#8217;t even reflect their variable names.&lt;/p&gt;

  &lt;p&gt;So I wrote an assertion to replace all of them. Put whatever you want
  into it; it prints out your expression, and all its values.
  Essentially like this:&lt;/p&gt;

  &lt;table&gt;
    &lt;tr&gt;
      &lt;td valign="top" align='center' style=
      'border:solid; padding:.1in 5.75pt .1in 5.75pt'&gt;
      __source__&lt;/td&gt;

      &lt;td valign="top" align='center' style=
      'border:solid; border-left:none;padding:.1in 5.75pt .1in 5.75pt'&gt;
      __failure_diagnostic__&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td valign="top" style=
      'border:solid; border-top:none;padding:.1in 5.75pt .1in 5.75pt'&gt;
      &lt;pre&gt;x = 43
assert{ x == 42 }&lt;/pre&gt;
      &lt;/td&gt;

      &lt;td valign="top" style=
      'border-top:none;border-left: none;border-bottom:solid; border-right:solid; padding:.1in 5.75pt .1in 5.75p