<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-25066620</id><updated>2011-11-27T18:08:31.415-06:00</updated><category term='sapir-whorf'/><category term='goebbels'/><category term='books'/><category term='collaboration'/><category term='development'/><category term='ranges'/><category term='coworking'/><category term='perforce'/><category term='rubyonrails'/><category term='array'/><category term='splat'/><category term='code generators'/><category term='activewarehouse'/><category term='darcs'/><category term='gnomevfs'/><category term='business analysis'/><category term='git'/><category term='configuration'/><category term='uk'/><category term='rails'/><category term='turbogears'/><category term='web2'/><category term='outsource'/><category term='nosql'/><category term='greatbritain'/><category term='serendipity'/><category term='jee'/><category term='bdd'/><category term='closures'/><category term='c++'/><category term='anti-patterns'/><category term='fraud'/><category term='code generation'/><category term='sin'/><category term='agile process'/><category term='illinilist'/><category term='reading'/><category term='metafusion'/><category term='agile practices'/><category term='CSS'/><category term='java'/><category term='Javascript'/><category term='kaizen'/><category term='restful'/><category term='cdbaby'/><category term='django'/><category term='concurrency'/><category term='cowboy monkey'/><category term='gedit'/><category term='monkey'/><category term='opinion'/><category term='mac'/><category term='power'/><category term='dsl'/><category term='benchmarking'/><category term='rbehave'/><category term='agile software'/><category term='specifications'/><category term='project'/><category term='chicken'/><category term='j2ee'/><category term='cjug'/><category term='datamapper'/><category term='stupid'/><category term='json'/><category term='svn'/><category term='subversion'/><category term='google'/><category term='gay marriage'/><category term='activewarehouse-etl'/><category term='pig'/><category term='ruby'/><category term='activerecord'/><category term='blocks'/><category term='ruby expressions'/><category term='=='/><category term='cryptography'/><category term='ETL'/><category term='messaging'/><category term='quote'/><category term='github'/><category term='merb'/><category term='SOA'/><category term='source control management'/><category term='mda'/><category term='think'/><category term='opensource'/><category term='neo4j'/><category term='illinois'/><category term='source control'/><category term='tdd'/><category term='ruby idioms'/><category term='productivity'/><category term='graph databases'/><category term='currying'/><category term='math'/><category term='dry'/><category term='liberty'/><category term='election'/><category term='Google Wave'/><category term='jug'/><category term='Cloud Computing'/><category term='smalltalk'/><category term='urbana'/><category term='scm'/><category term='basecamp'/><category term='dhh'/><category term='web services'/><category term='champaign urbana'/><category term='seo'/><category term='propaganda'/><category term='friendship'/><category term='ruby on rails'/><category term='energy'/><category term='scrum'/><category term='presenter'/><category term='paypal'/><category term='clearcase'/><category term='domain specific language'/><category term='twitter'/><category term='equal?'/><category term='rubyforge'/><category term='mathematics'/><category term='higher order messaging'/><category term='coffee'/><category term='project management'/><category term='conventions'/><category term='datawarehouse'/><category term='beautiful code'/><category term='reliable-msg'/><category term='engines'/><category term='warehouse'/><category term='erlang'/><category term='Feeds'/><category term='autotest'/><category term='commercial'/><category term='tony blair'/><category term='predictions'/><category term='BarCamp'/><category term='open source'/><category term='outsourcing'/><category term='HTTP'/><category term='psychology'/><category term='RSS'/><category term='web 2.0'/><category term='ROA'/><category term='champaign'/><category term='performance'/><category term='search engine optimization'/><category term='pradipta'/><category term='future'/><category term='xml'/><category term='fastcgi'/><category term='scala'/><category term='entrepreneur'/><category term='scalability'/><category term='==='/><category term='aesthetics'/><category term='arrays'/><category term='security'/><category term='blackle'/><category term='rails generators'/><category term='models'/><category term='language'/><category term='idioms'/><category term='equality'/><category term='Jabber'/><category term='user'/><category term='rspec'/><category term='editor'/><category term='looping'/><category term='libertarian'/><category term='coding'/><category term='agile methods'/><category term='testing'/><category term='release'/><category term='scam'/><category term='jms'/><category term='plugins'/><category term='prime minister'/><category term='test driven'/><category term='design patterns'/><category term='search engines'/><category term='beck'/><category term='apple'/><category term='illini'/><category term='twitter twitter4r'/><category term='enumerations'/><category term='hitler'/><category term='gnome'/><category term='ruby ranges'/><category term='england'/><category term='agile'/><category term='python'/><category term='consulting'/><category term='chicago'/><category term='great britain'/><category term='joseph goebbels'/><category term='range'/><category term='windows'/><category term='layout'/><category term='Alan Turing'/><category term='Service Oriented Architecture'/><category term='buddha'/><category term='tonyblair'/><category term='beauty'/><category term='database'/><category term='linux'/><category term='twitter4r'/><category term='software requirements'/><category term='oss'/><category term='cvs'/><category term='blair'/><category term='britain'/><category term='linguistics'/><category term='codegenie'/><category term='REST'/><category term='php'/><category term='loops'/><category term='patterns'/><category term='information leaks'/><category term='politics'/><category term='kent beck'/><category term='chambana'/><category term='XMPP'/><category term='expression'/><category term='YUI'/><category term='jvm'/><category term='book'/><category term='config'/><category term='jee5'/><category term='enumerable'/><category term='sapir whorf'/><category term='web2.0'/><category term='inject'/><category term='oo'/><category term='cowboy'/><category term='orm'/><category term='natural language'/><category term='functional programming'/><category term='ruby forwardable'/><category term='vote'/><category term='safir-whorf'/><category term='eql?'/><category term='model'/><category term='capistrano'/><category term='data'/><category term='conductor'/><title type='text'>Ruby, Javascript, Erlang blog: Snakes, Gems &amp; Coffee</title><subtitle type='html'>&lt;p&gt;Topics already covered in this blog: &lt;a href="http://snakesgemscoffee.blogspot.com/search/label/idioms"&gt;Ruby Idioms&lt;/a&gt;, &lt;a href="http://snakesgemscoffee.blogspot.com/search/label/rails"&gt;Ruby on Rails&lt;/a&gt;, &lt;a href="http://snakesgemscoffee.blogspot.com/search/label/restful"&gt;RESTful design&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Going forward you will see a lot of posts about: &lt;a href="http://snakesgemscoffee.blogspot.com/search/label/merb"&gt;Merb&lt;/a&gt;, Ruby 1.9, Server benchmarks, performance, tuning.&lt;/p&gt;</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default?start-index=101&amp;max-results=100'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>123</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-25066620.post-7391614450100476192</id><published>2010-04-03T11:37:00.001-05:00</published><updated>2010-08-16T12:21:35.572-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='neo4j'/><category scheme='http://www.blogger.com/atom/ns#' term='nosql'/><category scheme='http://www.blogger.com/atom/ns#' term='graph databases'/><title type='text'>DRAFT: Why proponents of marriage equality should love graph databases, Part 2: A Reply to 'The Database Engineering Perspective'</title><content type='html'>So &lt;a href="http://geek.susanpotter.net/2010/03/why-proponents-of-marriage-equality.html" target="_blank"&gt;in part 1&lt;/a&gt; I provided an &lt;a href="http://geek.susanpotter.net/2010/03/why-proponents-of-marriage-equality.html" target="_blank"&gt;overview of problems relational databases (&lt;acronym title="Relational Database Management Systems"&gt;RDBMS&lt;/acronym&gt;) have with modelling relationships&lt;/a&gt; as well as &lt;a href="http://geek.susanpotter.net/2010/03/why-proponents-of-marriage-equality.html" target="_blank"&gt;entities that have datasets that are only partially structured in structure and definition&lt;/a&gt; based on my experiences.&lt;br /&gt;
&lt;br /&gt;
This post will:&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;introduce new concepts utilized in graph databases and specific terminology for Neo databases&lt;/li&gt;
&lt;li&gt;review the most flexible schemas that accommodate both same-sex and opposite-sex marriages alike that were presented in the blog post I am responding to, &lt;a href="http://qntm.org/gay" target="_blank"&gt;&lt;em&gt;'Gay Marriage: The Database Engineering Perspective'&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;describe how graph databases could overcome the relational database shortcomings&lt;/li&gt;
&lt;li&gt;finally I will present a snippet of code (not meant for production use, but merely to demonstrate how we can use Neo4J) that makes it more natural to represent richer relationships between entities (or nodes as Neo4J calls them) that have substance to them (i.e. attributes in this case)&lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
&lt;h4&gt;New Concepts in Graph Databases&lt;/h4&gt;Before I can explain how graph databases on a conceptual level can overcome most if not all (for most of the time anyway) of the relational databases (&lt;acronym title="relational database management systems"&gt;RDBMS&lt;/acronym&gt;) shortcomings mentioned in the previous post, we need to be introduced to new concepts and terminology.&lt;br /&gt;
There are two basic classes of objects in a graph database, they are:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Nodes:&lt;/strong&gt; a &lt;em&gt;node&lt;/em&gt; is basically an entity as RDBMS people (like probably you and I) are familiar with.  A &lt;em&gt;node&lt;/em&gt; could represent a person, customer, blog post, photograph, video or tweet.  It isn't always true, nor is it a good idea to think of graph database concepts only in terms of RDBMS concepts, but we could consider most tables that do not represent an actual &lt;em&gt;relationship&lt;/em&gt; or association between other tables as an entity.  It is a simplification, so use only in this initial learning phase cautiously.  A &lt;em&gt;node&lt;/em&gt; can have zero or more properties.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Relatioships:&lt;/strong&gt; a &lt;em&gt;relationship&lt;/em&gt; is the concept of an association between two &lt;em&gt;node&lt;/em&gt;s that needs to be represented in your data store somehow.  A &lt;em&gt;relationship&lt;/em&gt; can also have zero or more properties, just like a &lt;em&gt;node&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
The third concept in graph databases is that of a &lt;em&gt;property&lt;/em&gt;.  It unifies &lt;em&gt;node&lt;/em&gt;s and &lt;em&gt;relationship&lt;/em&gt;s as both first class citizens of the data store, which is quite unlike relational databases.  &lt;br /&gt;
&lt;br /&gt;
In a relational database we try to either reduce an association between two entities/tables to a small handful of cases, which might use references (if supported by that particular vendor and version of RDBMS), create "join tables" or we might force a natural relationship to become a table itself so that we are able to capture pertinent attributes for it.  There is no other way of handling relationships.  In many cases it might not be an issue to reduce the problem in these ways, however, the more and more interconnected our &lt;em&gt;entities&lt;/em&gt; become with new types of relationships it seems (to me) that this method of modelling data quickly becomes problematic.&lt;br /&gt;
&lt;br /&gt;
The following are other useful terms that the Neo graph database family uses, but conceptually most of these will also have a synonym in other graph databases:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Traversers:&lt;/strong&gt; in graph databases "querying" for specific data is not done via declarative query statements and clauses like SQL.  Rather we define &lt;em&gt;traversers&lt;/em&gt; that are composed of the following elements:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;the starting node&lt;/li&gt;
&lt;li&gt;the relationship types needing to be traversed&lt;/li&gt;
&lt;li&gt;the stop criteria&lt;/li&gt;
&lt;li&gt;the selection criteria&lt;/li&gt;
&lt;li&gt;traversing order (e.g. breadth first, etc.)&lt;/li&gt;
&lt;/ul&gt;We need to know the starting node so the graph database knows where to start the traversal. The &lt;em&gt;traverser&lt;/em&gt; will then only move along relationships of the types given by the second element of a &lt;em&gt;traverser&lt;/em&gt;, evaluate the selection criteria to know which nodes are relevant tot he "query", then evaluate whether the stop criteria applies to know when traversing should stop.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Indexers:&lt;/strong&gt; In Neo4J there are several indexing utilities backed by a Lucene backend that make it easy to index actual nodes, full text and based on timelines.  This allows us to look up relevant starting nodes for traversing.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;h4&gt;How can graph databases overcome relational database shortcomings?&lt;/h4&gt;Now we will have a quick peak at how the new concepts and ideas from graph databases can resolve the complaints I had about relational databases:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;In response to &lt;em&gt;A lot of real world data isn't highly structured (only partially)&lt;/em&gt;: with graph databases we only need to set the properties a specific node actually has.  We do not need to fill in lots of &lt;code&gt;nulls&lt;/code&gt; in attributes that aren't relevant like we do in RDBMSes.  Our graph database "model" interface might be able to add any constraints that are necessary.  While, in situations where highly structured data sets really do occur, I prefer having two sets of constraints - those on the relational database level and those in the "model" (application tier) - however, it can quickly become hard to manage when two sets of constraints that should be identical are defined in vastly different ways (languages).&lt;/li&gt;
&lt;li&gt;In response to &lt;em&gt;Object to relational mapping (ORM) constraints and disjoints&lt;/em&gt;: using nodes and relationships conceptually on the graph database layer you have little if any translation between 'objects' that represent nodes or relationships.&lt;/li&gt;
&lt;li&gt;In response to &lt;em&gt;Weak and inefficient "traversal" support&lt;/em&gt;: this isn't a problem with graph databases.  Traversing data is much more relevant when dealing with data that is naturally in a network or graph formation already.  Relational databases cannot handle networks or graph-like data sets without a lot of workarounds.&lt;/li&gt;
&lt;li&gt;In response to &lt;em&gt;Maintaining and evolving relational schemas&lt;/em&gt;: when the data sets you are dealing with are not highly structured and densely populated evolving strict data schemas do not need to be maintained and with Neo4J's flexible attribute setting/getting, data structures can be flexibly evolved when the data itself changes, however, this raises another issue of application code being able to read the old attributes in the graph database, but I will talk about that in Part 3.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;h4&gt;Gay marriage: the graph database solution&lt;/h4&gt;&lt;script src="http://gist.github.com/354659.js"&gt;&lt;/script&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-7391614450100476192?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/7391614450100476192/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=7391614450100476192' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7391614450100476192'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7391614450100476192'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2010/04/draft-why-proponents-of-marriage.html' title='DRAFT: Why proponents of marriage equality should love graph databases, Part 2: A Reply to &apos;The Database Engineering Perspective&apos;'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-3872035431659546966</id><published>2010-03-13T10:28:00.003-06:00</published><updated>2010-04-03T20:58:56.047-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='neo4j'/><category scheme='http://www.blogger.com/atom/ns#' term='nosql'/><category scheme='http://www.blogger.com/atom/ns#' term='gay marriage'/><title type='text'>Why proponents of marriage equality should love graph databases, Part 1: A Reply to 'The Database Engineering Perspective'</title><content type='html'>This is a response to the excellent &lt;a href="http://qntm.org/gay" target="_blank"&gt;Gay marriage: the database engineering perspective&lt;/a&gt; blog post from November 2008 by Sam Hughs.  The only discussion that didn't happen in his blog post was offering a NOSQL alternative to the gay marriage database problem, but I will not hold it against Sam Hughs because it was a comprehensive look at relational database schema designs for modelling marriage!&lt;br /&gt;
&lt;br /&gt;
This response is a little dabble (i.e. not as thorough as the original post) into how graph databases could model monogamous same sex marriages without much problem and also allow for polyamorous marriages quite naturally.  My goal here is to demonstrate how graph databases provide a great amount of flexibility without very much work at all, especially when the data application is just as concerned with connections between entities (aka records in RDBMS or nodes in graph databases) than just the entities themselves.&lt;br /&gt;
&lt;br /&gt;
First off I want to look at the inherent problems with modelling real world data in relational databases and how the approach of graph databases can overcome many, if not all, of these problems.  Then I will launch into a specific snippet of code to demonstrate modelling marriages that can be between any two consenting adults rather than just one man and one woman.  Then I hope to outline how this could be extended to model poly amorous (including polygamous) relationships without much more work.  Near the end I will also include a section at the end that discusses shortcomings of the graph database approach as well for completeness.  My discussion will be focused on using &lt;a href="http://neo4j.org/" target="_blank"&gt;Neo4J&lt;/a&gt; as it is the only comprehensive graph database I am aware of that is open source.&lt;br /&gt;
&lt;br /&gt;
&lt;strong&gt;Update (2010-03-13):&lt;/strong&gt; Boris (in the comments) mentioned that there is another open source graph database called &lt;a href="http://www.kobrix.com/hgdb.jsp" target="_blank"&gt;HyperGraphDB&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;strong&gt;Update (2010-04-03):&lt;/strong&gt; Johannes (via email) told me about &lt;a href="http://infogrid.org/" target="_blank"&gt;InfoGrid&lt;/a&gt;, which looks like a very interesting alternative to Neo4J with a slightly different way of doing things.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;What is wrong with relational databases?&lt;/h3&gt;For the last 14 years I have been using, designing, maintaining and administering relational databases in some capacity as a software programmer, developer, engineer and now as an applications architect.    During that time I have found the following problems with using relational databases:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;A lot of real world data isn't highly structured (only partially):&lt;/strong&gt; this is probably the biggest problem with using relational databases (for everything) the way they are supposed to be.  Sure you can add a blob, clob or text field to a table and add an arbitrary structure of data that depends on the record, which can be parsed by the application, but that would be a violation of all things relational and you would be paying a heavy price for doing this in a relational database depending on how big these "blobs" generally are.  Not to mention you miss out on query-ability, which is something relational databases do well on highly structured datasets with the relevant indexes defined and a decent attempt at normalization.  &lt;em&gt;Note: I think relational databases are a fine thing when utilized to model data that truly is highly structured in the wild where the entities, not the relationships matter the most.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Object to relational mapping (ORM) constraints and disjoints:&lt;/strong&gt; For almost a decade I have been using ORM libraries such as TOPLink (Java), Persistence (C++), Hibernate (Java), ActiveRecord (Ruby), DataMapper (Ruby), SQLObject (Python) and others.  They each had their own specific problems at times, but they also possessed a set of common problems that resulted in creating a far from seamless integration between the object oriented layer (that a vast number of business systems and web applications are currently written with today) and the distinct properties of a relational database.  These common object to relational constraints and disjointedness is not an &lt;abbr title="Object-Relational Mapper"&gt;ORM&lt;/abbr&gt; tool issue, rather it is a problem of trying to shove a round pin into a square hole (paraphrasing a comment made in page 3 of &lt;a href="http://dist.neo4j.org/neo-technology-introduction.pdf" target="_blank"&gt;'The Neo Database: A Technology Introduction'&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weak and inefficient "traversal" support:&lt;/strong&gt; When I first started writing rich domain models in the nineties (that is last century for all you whipper-snappers) the focus of design was on the actual &lt;em&gt;entities&lt;/em&gt; not relationships between the entities.  Sure occasionally you had to model a relationship as an "entity", but for the most part you could do your best to reduce your domain relationships as much as possible to a combination of &lt;em&gt;contain a(n)&lt;/em&gt; (aka &lt;code&gt;belongs_to&lt;/code&gt; in ActiveRecord), &lt;em&gt;contained by&lt;/em&gt; (aka &lt;code&gt;has_one&lt;/code&gt; in ActiveRecord), &lt;em&gt;one-to-many&lt;/em&gt; (aka &lt;code&gt;has_many&lt;/code&gt; in ActiveRecord) and/or &lt;em&gt;many-to-many&lt;/em&gt; (aka &lt;code&gt;has_and_belongs_to_many&lt;/code&gt; in ActiveRecord) as much as possible because you can't attach attributes to relationships in a relational database unless you attempt to make it an entity.  If you are interested in deep and/or rich object graphs, however, you have to pay the penalty in any &lt;abbr title="Relational Database Management Systems"&gt;RDBMS&lt;/abbr&gt; with multiple joins, which are an expensive operation.  Even with all the right indices defined and queries optimized you will find more than three levels of indirection (aka &lt;code&gt;JOIN&lt;/code&gt;s) will take a while on even a medium sized dataset.  So as application developers, we are forced to add lazy loading and/or customized eager loading logic into our rich object domain layer for a number cases where performance matters, which in my view pollutes business logic and makes the code less maintainable going forward.  This is far from ideal.  It is also not ideal that data set traversal patterns change with new the introduction of new features, so often you will need to fork lazy loading logic in the business logic layer to satisfy you new requirements.  Adding a lot more complexity to manage in the application tier.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintaining and evolving relational schemas:&lt;/strong&gt; modifying relational database schemas has always been a little tricky at best.  Today I take advantage of using ActiveRecord's "migrations" that loosely orders (by creation timestamp) a set of relational schema modifications to run (and we wrote a simple extension to wrap them in a transaction to save our sanity - why wasn't that the default to begin with? anyway...).  Even though there is a method to the madness now, it is still a little crazy, especially when it comes time to run these migrations on the production server.  I always miss a heart beat or two when I run the &lt;code&gt;rake db:migrate RAILS_ENV=production&lt;/code&gt; command (yes, even after taking a snapshot).&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
It has been fun thinking out loud, but I have stuff to do today.  Hey, I sometimes have a life, honest!:)  So I have made an outline of what is to come in the next parts of this topic.  I have already written some (bad) Java code to model same-sex and opposite-sex marriages consistently using a graph database (in this case Neo4J), but I plan on offering a Python snippet too, since I really can't stand the look of Java any more and the API doesn't make the case of Graph Databases for those coming from more terser feeling languages like Python, Ruby, Haskell, Erlang, Javascript (well if you use sane APIs like jQuery at least).&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;What is coming in Part 2?&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;How can graph databases overcome relational database shortcomings?&lt;/li&gt;
&lt;li&gt;Gay marriage: the graph database solution&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;h3&gt;What is coming in Part 3?&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Poly amorous marriage using a graph database&lt;/li&gt;
&lt;li&gt;Graph databases: problems to watch out for&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-3872035431659546966?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/3872035431659546966/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=3872035431659546966' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3872035431659546966'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3872035431659546966'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2010/03/why-proponents-of-marriage-equality.html' title='Why proponents of marriage equality should love graph databases, Part 1: A Reply to &apos;The Database Engineering Perspective&apos;'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-5314403100358772015</id><published>2010-01-30T21:27:00.015-06:00</published><updated>2010-01-30T22:27:16.565-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='agile software'/><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='agile methods'/><category scheme='http://www.blogger.com/atom/ns#' term='agile process'/><category scheme='http://www.blogger.com/atom/ns#' term='agile practices'/><title type='text'>How agile practices improve code review</title><content type='html'>&lt;p&gt;On a mailing list recently someone posed a question about how to incorporate code reviews into an "agile methodology".  While I had questions about the way the question was posed it got me thinking about how agile practices make code review happen more often, more organically and more transparently.  Having different members of the team review code at different times during the development process on agile projects is both explicit, but also implicit.&lt;/p&gt;
&lt;h4&gt;Values&lt;/h4&gt;
&lt;p&gt;Let us back up a little here.  First off &lt;em&gt;agile&lt;/em&gt; is at its core a set of four ideals.  Prefer:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;individuals and interactions&lt;/strong&gt; &lt;em&gt;over&lt;/em&gt; processes and tools&lt;/li&gt;&lt;li&gt;&lt;strong&gt;working software&lt;/strong&gt; &lt;em&gt;over&lt;/em&gt; complete documentation&lt;/li&gt;&lt;li&gt;&lt;strong&gt;customer involvement&lt;/strong&gt; &lt;em&gt;over&lt;/em&gt; contract negotiation&lt;/li&gt;&lt;li&gt;&lt;strong&gt;adapting to changing needs&lt;/strong&gt; &lt;em&gt;over&lt;/em&gt; completing a stale plan&lt;/li&gt;&lt;/ul&gt;
&lt;h4&gt;Principles/Tenets&lt;/h4&gt;
&lt;p&gt;From these ideals there are about 10 or so principles or tenets that are most commonly discussed among agile practitioners from most of the main camps.  This is where it starts getting controversial as some agile camps acknowledge some of these principles and others do not necessarily, so here are just a few that I want to talk about in relation to increasing code review on agile projects:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Collective team ownership of product:&lt;/strong&gt; meaning everyone on the team owns the product and nobody on the team is exclusive to just one or a couple of components of the product.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Embracing change:&lt;/strong&gt; instead of continuing down a set path determined long ago that no longer applies, the team needs to adapt to customer's changing requirements at set (and regular) intervals (these might be called iteration planning meetings/negotiations, for example).  This has various implications throughout the iteration/sprint.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Verify expectations continuously:&lt;/strong&gt; meaning whenever possible, without impeding development, verify that customer expectations will be satisfied and no new additions have negative impacts.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Customers set priorities:&lt;/strong&gt; instead of technical staff or outsourcing companies specifying technical priorities, customers specify business/feature priorities.&lt;/li&gt;&lt;/ul&gt;
&lt;h4&gt;Practices&lt;/h4&gt;
&lt;p&gt;This is the point where we start seeing the translation into agile &lt;em&gt;practices&lt;/em&gt;, many of while you will start to recognize:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Pair programming&lt;/strong&gt; which arose from both &lt;em&gt;collective team ownership&lt;/em&gt; and &lt;em&gt;embracing change&lt;/em&gt; tenets.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Test-first&lt;/strong&gt; practice came about mostly to satisfy &lt;em&gt;embracing change&lt;/em&gt;, but also &lt;em&gt;verify expectations continuously&lt;/em&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Assigning new stories independently of previous ones&lt;/strong&gt;. This means that no developer or pair has a niche component or product area and is used to satisfy the first tenet listed above.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Continuous integration&lt;/strong&gt; that helps satisfy the &lt;em&gt;verify expectations continously&lt;/em&gt; principle.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;No fear refactoring&lt;/strong&gt;, which is only advisable if your test/spec suite has good enough coverage,.  This means you will likely revise poorly constructed code on agile projects vs non-agile projects more often and have the chance to do it more often too.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Obviously with the pair programming practice that some agile methods promote, you have another set of eyes reviewing code as it is written.  This catches errors closer to the time it was written, which is a big time (and money) saver.&lt;/p&gt;
&lt;p&gt;Applying a test-first approach means that you start out defining custom expectations of the code first before writing the actual code.  This is reviewing, perhaps not code in the traditional way, but the requirements, which in my view is even more productive usually.&lt;/p&gt;
&lt;p&gt;When a project manager assigns new stories to a developer or a pair without confining him/her/them to specific areas of the codebase, this increasing the transparency of the codebase and exposes hidden dragons to be exposed before too long and hopefully before being launched into production (but you never know).  Again this increasing the number of eye balls on parts of the codebase.&lt;/p&gt;
&lt;p&gt;With continuous integration you can incorporate metric tools to capture some metrics of the code so that parts of the code that are obviously in need to technical refactoring get highlighted earlier rather than later.  Increasing transparency of the codebase, which is always a goal of code reviews.&lt;/p&gt;
&lt;p&gt;Hopefully you can see where I am going with this and not think about code reviews in the traditional top-down way where every month or quarter a piece of code is reviewed from each developer in a formal way.&lt;/p&gt;
&lt;p&gt;There are a number of different agile (and common) software practices that could be used, I can think of 4 more off the top of my head that improves codebase visibility across more of the team.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-5314403100358772015?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/5314403100358772015/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=5314403100358772015' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5314403100358772015'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5314403100358772015'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2010/01/how-agile-practices-improve-code-review.html' title='How agile practices improve code review'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-3163473251174310759</id><published>2010-01-17T18:37:00.006-06:00</published><updated>2010-01-17T19:05:04.199-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby on rails'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyonrails'/><title type='text'>So Long, Farewell, Auf Wiedersehen, Goodbye...to Ruby &amp; Rails (sort of)</title><content type='html'>&lt;p&gt;It's been obvious to many of my close geek comrades that for the last 18-22 months I have been getting more and more frustrated with the Ruby community and the "quality" of products in this space.  The quality of people in the Ruby field has been tending towards the average PHP developer (PHP is a gateway drug for programming - i.e. not something to aspire to) and the recent Ruby mindset (as a whole though some dark corners still look interesting) has not been about doing things differently, but rather blending in with mainstream ideas.  For some that is exciting as it means not fighting management just to get to use Ruby in the enterprise.  &lt;strong&gt;I understand, I really do.&lt;/strong&gt;  Fighting management on technical platform is never fun.&lt;p&gt;

&lt;p&gt;I think it is safe to say the direction of the major libraries and frameworks that are prominent in the Ruby landscape will suffer from very similar conceptual limitations as the Java, J2EE and JEE frameworks that DHH once ridiculed (for good reason).  Of course, this is all to be expected in the usage lifecycle of a programming language.  In fact, I believe I predicted that I would get bored of Ruby in a blog post in 2006 within the next few years though it doesn't make this any easier.&lt;p&gt;

&lt;h4&gt;Thanks mates!&lt;/h4&gt;
&lt;/p&gt;I wish all friends in the Ruby and Rails worlds luck (I think you'll need it) and I thank (ex-)coworkers and project collaborators that have taught me something new for all the intellectual stimulation you have provided me.  I am sad to be saying goodbye to those of you.&lt;/p&gt;

&lt;h4&gt;But...&lt;/h4&gt;
&lt;p&gt;I look forward to grazing new pastures.  I will be working on my next generation financial products using the right tool for each primary job.  Currently most of the &lt;strong&gt;Finsignia Platform&lt;/strong&gt; is quite back-end and different pieces could take advantage of Haskell's concurrency and/or Erlang distribution.  Yet there will inevitably need to be (at some point) rich web applications for UI, so (J)Ruby and Rails I might come knocking on your door again, but for now, &lt;em&gt;adiós&lt;/em&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-3163473251174310759?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/3163473251174310759/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=3163473251174310759' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3163473251174310759'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3163473251174310759'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2010/01/so-long-farewell-auf-wiedersehen.html' title='So Long, Farewell, Auf Wiedersehen, Goodbye...to Ruby &amp; Rails (sort of)'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-649821935183669240</id><published>2009-09-29T21:59:00.007-05:00</published><updated>2009-09-29T22:18:34.152-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='business analysis'/><category scheme='http://www.blogger.com/atom/ns#' term='Cloud Computing'/><title type='text'>Cloud Computing, Part 2: Law, Privacy &amp; Maturity</title><content type='html'>At the CEO roundtable event I mentioned in my previous blog post about in &lt;a href="http://geek.susanpotter.net/2009/09/cloud-computing-part-1-delivery-models.html" target="_new"&gt;Cloud Computing, Part 1&lt;/a&gt; the other major business consideration that came up was how to protect your business' (and/or customers') assets properly.  Ultimately anyone that wants to build a solid foundation for their business needs to tackle this well to be successful long-term.

One person asked the speaker to elaborate about a recent case where a UK bank used servers in datacenters inside India where a datacenter employee stole customer information and used it to effectively steal money from these people's accounts for his own benefit.  This could happen in any country, however, in this case this datacenter employee actually didn't break any laws inside of India (or broke one very minor law that had a very limited penalty).  Ultimately this lack of relevant law and regulation in a foreign country is the major risk for a business using offshore datacenter resources.

Unfortunately I didn't hear the name of the bank involved to be able to find a news article about this incident, however, the speaker had heard about this incident (it wasn't just a figment of the questioners imagination) and his response (I am paraphrasing here) was:
&lt;blockquote&gt;Countries with the most mature infrastructure, laws, regulations and standards regarding data and information law (as well as privacy) will probably be the places that larger firms will invest in for sensitive and mission critical systems of their organization despite the likely higher cost of building.&lt;/blockquote&gt;

As you determine whether you are going to be a cloud consumer or provider in the future, make sure to keep an eye on these risk factors as some executives seem to only focus on the raw bottom line without conducting a thorough risk analysis to see if the cost savings are really worth the higher risks.  Choose the best cloud computing partners for all your cost trimming AND risk mitigation needs.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-649821935183669240?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/649821935183669240/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=649821935183669240' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/649821935183669240'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/649821935183669240'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2009/09/cloud-computing-part-2-law-privacy.html' title='Cloud Computing, Part 2: Law, Privacy &amp; Maturity'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-3272923407562795826</id><published>2009-09-11T21:10:00.011-05:00</published><updated>2009-09-11T22:39:26.005-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Cloud Computing'/><title type='text'>Cloud Computing, Part 1: Delivery Models</title><content type='html'>&lt;p&gt;Today I went to a regional CEO Roundtable lunch talk given by &lt;a href="http://cs.illinois.edu/people/faculty/roy-campbell" target="_new"&gt;Professor Roy Campbell at &lt;acronym title="University of Illinois at Urbana-Champaign"&gt;UIUC&lt;/acronym&gt;&lt;/a&gt; on Cloud Computing and why executives of any kind of business should care.&lt;/p&gt;
&lt;p&gt;While I am not new to the concepts of cloud computing and multi-tenancy SaaS, since I have consumed various cloud services and designed multi-tenant architectures for two previous clients.  However, there was one particular "overview" slide that Professor Campbell showed that got me thinking a little.  It was titled Delivery Models.&lt;/p&gt;
&lt;p&gt;It was a text slide with bullets, but I think a diagram (or two) could have communicated to the higher-level audience more than most of the first half of his presentation could have (see below and let me know if it is more effective).&lt;/p&gt;
&lt;p&gt;I especially liked Professor Campbell's categorization of the Delivery Models.  Until this lunch time I had really only considered there to be two cloud delivery models: &lt;acronym title="Software as a Service"&gt;SaaS&lt;/acronym&gt; and &lt;acronym title="Platform as a Service"&gt;PaaS&lt;/acronym&gt;.  I wasn't making a distinction between Amazon services like S3 and EC2 and that of Google's AppEngine.  On reflection, I am glad I took two hours out of my busy Friday if only to consider the distinction made in this talk.&lt;/p&gt;
&lt;p&gt;In a nutshell the slide has three major bullets:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Software as a Service&lt;/li&gt;
  &lt;li&gt;Platform as a Service&lt;/li&gt;
  &lt;li&gt;Infrastructure as a Service&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nice!  Google AppEngine, essentially being an application container (that supports various frameworks now), is a platform, whereas S3 and EC2 provide infrastructure on demand.&lt;/p&gt;
&lt;p&gt;Now unfortunately this is where Campbell left it.  For those of us not unfamiliar with cloud computing, we can see the importance of this distinction so that we can develop a more solid business plan (for those of us that might be interested in using the cloud to generate revenue from).  It may also help technically savvy people that aren't familiar with the cloud.&lt;/p&gt;
&lt;p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_YUkA0L0Czls/SqsNB413zaI/AAAAAAAAAIw/_iJREHeogXs/s1600-h/CloudComputing-DeliveryModel.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 283px; height: 400px;" src="http://4.bp.blogspot.com/_YUkA0L0Czls/SqsNB413zaI/AAAAAAAAAIw/_iJREHeogXs/s400/CloudComputing-DeliveryModel.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5380408505913560482" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Hope this helps people make a choice between container services like AppEngine or Heroku (platform) and those such as S3 and EC2 (infrastructure) from a consumer side and perhaps those devising business models to focus on their core competencies.&lt;/p&gt;
&lt;p&gt;Next time I want to talk about privacy, legal and compliance issues surrounding Cloud Computing as well as how business should weigh the risk of outsourcing their data handling (however that happens) to countries will less mature laws, regulation and oversight.  Perhaps &lt;em&gt;cloud governance&lt;/em&gt; would be the most appropriate term?&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-3272923407562795826?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/3272923407562795826/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=3272923407562795826' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3272923407562795826'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3272923407562795826'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2009/09/cloud-computing-part-1-delivery-models.html' title='Cloud Computing, Part 1: Delivery Models'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_YUkA0L0Czls/SqsNB413zaI/AAAAAAAAAIw/_iJREHeogXs/s72-c/CloudComputing-DeliveryModel.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-915986941225105528</id><published>2009-09-10T21:42:00.007-05:00</published><updated>2009-09-10T21:52:46.752-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Alan Turing'/><title type='text'>Alan Turing Can Now Rest In Peace</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.number10.gov.uk/wp-content/uploads/number10door474-300x254.jpg"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 300px; height: 254px;" src="http://www.number10.gov.uk/wp-content/uploads/number10door474-300x254.jpg" border="0" alt="" /&gt;&lt;/a&gt;
&lt;p&gt;
While I am by no means the biggest fan of the New Labour government in my homeland (mostly due to Blair's sociopathic reign), I have to give Gordon Brown props (or at least his aids) for giving credit to Alan Turing and saying Britain, as a country, fucked up big time (sorry, I really couldn't think of another phrase that matched the intensity - I'll replace it if you suggest a good alternative).
&lt;/p&gt;
&lt;p&gt;
You can read the Downing Street blog post here:&lt;br/&gt;
&lt;a href="http://www.number10.gov.uk/Page20571" target="_new"&gt;Treatment of Alan Turing was “appalling” - PM&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
Thanks from this particular graduate from University of the Manchester's Mathematics department who now works as a software engineer who also happens to be gay!&lt;/p&gt;
&lt;p&gt;"Ta luv", as the Manc locals would say.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-915986941225105528?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/915986941225105528/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=915986941225105528' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/915986941225105528'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/915986941225105528'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2009/09/alan-turing-can-now-rest-in-peace.html' title='Alan Turing Can Now Rest In Peace'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-5096397482927613381</id><published>2009-09-09T19:07:00.012-05:00</published><updated>2009-09-10T08:09:41.452-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='data'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby on rails'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><title type='text'>Faker, Ruby on Rails Snippet &amp; Generator</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_YUkA0L0Czls/SqhxBu4ZNzI/AAAAAAAAAIo/33EMivvSZsw/s1600-h/twitter-screenshot.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 218px;" src="http://2.bp.blogspot.com/_YUkA0L0Czls/SqhxBu4ZNzI/AAAAAAAAAIo/33EMivvSZsw/s400/twitter-screenshot.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5379674029472429874" /&gt;&lt;/a&gt;

Hey blog readers (if there are any left),

I know it has been a while, and I know I have left you high and dry, so today I decided I will share some code and software development thoughts with you.

I am moving to a more snippet oriented blog posting style from now on.  Previously I preferred &lt;a href="http://geek.susanpotter.net/2007/06/java-dogma-and-downfall.html" target="_new"&gt;long ramblings about the political ideology of programming languages&lt;/a&gt; and even &lt;a href="http://geek.susanpotter.net/2007/10/democratic-debate-ann-coulter-and.html" target="_new"&gt;how Ann Coulter was _more_ comprehensible when I used my Metafusion Crypto library&lt;/a&gt; on her words (actually that was a marketing lie - she sounded just as incomprehensible using &lt;a href="http://github.com/mbbx6spp/metafusion/tree/master/crypto"&gt;Metafusion Crypto&lt;/a&gt; as she did when not using it).  Of course, this was all in 2006-2007 when blogging was all about spewing small tidbits that taught the reader nothing, so I had to be different and attempt to find a reason for things.  Now things are swinging so I am trying to do the opposite from the norm again.

Ok, on with it.

Over the last week I have been using the Rails 2.3.4 "seed" mechanism, which in essence attempts to provide 80% of seed-fu's features inside Rails core.  In addition on my personal projects I have been using machinist as my factory framework of choice (although I am forced to use factory_girl for my clients), through which I was introduced to faker.  A beautiful gem indeed.

So to cut a long story short, I have found myself doing the following a hell of a lot in my brand new Rails 2.3.4 projects this week:
&lt;code class="ruby"&gt;
# in db/seeds.rb
def load_seed_files!
  seed_files = Dir["#{RAILS_ROOT}/db/seeds/all/*.rb"] + Dir["#{RAILS_ROOT}/db/seeds/#{RAILS_ENV}/*.rb"]
  seed_files.each do |file|
    puts "Loading seed file: #{file}"
    load(file)
  end
end

load_seed_files!
# When I add files to the db/seeds/all directory it seed ALL environments
# When I add files to the db/seeds/{development,staging,test,production} directories it seeds 
# for only that environment.
&lt;/code&gt;
Note: I wouldn't recommend using seeds in just staging, test or production generally.  If you need seed data in these environments you _probably_ need them in all environments OR (in the case of the test environment) you need to use a factory framework like machinist or factory_girl.

Now where is faker?  Well it's what I use inside these .rb files where faker comes into play:
&lt;code&gt;
# Faking a company name
&gt;&gt; Faker::Company.name
=&gt; "King-Ruecker"
&gt;&gt; Faker::Company.name
=&gt; "Cartwright, Prosacco and Smith"
&gt;&gt; Faker::Company.name
=&gt; "Pagac, Adams and Barrows"
&gt;&gt; Faker::Company.name
=&gt; "Satterfield and Sons"
&gt;&gt; Faker::Company.name
=&gt; "Blick-Deckow"

# Faking a person's name
&gt;&gt; Faker::Name.first_name
=&gt; "Ruthe"
&gt;&gt; Faker::Name.first_name
=&gt; "Jameson"
&gt;&gt; Faker::Name.first_name
=&gt; "Avery"
&gt;&gt; Faker::Name.first_name
=&gt; "Jordan"
&gt;&gt; Faker::Name.first_name
=&gt; "Ruthe"
&gt;&gt; Faker::Name.first_name
=&gt; "Winona"
&gt;&gt; Faker::Name.last_name
=&gt; "Brown"
&gt;&gt; Faker::Name.last_name
=&gt; "Hayes"
&gt;&gt; Faker::Name.last_name
=&gt; "Jerde"
&gt;&gt; Faker::Name.last_name
=&gt; "Lindgren"
&gt;&gt; Faker::Name.last_name
=&gt; "Considine"
&gt;&gt; Faker::Name.last_name
=&gt; "Rodriguez"
&gt;&gt; Faker::Name.last_name
=&gt; "Turner"
&gt;&gt; Faker::Name.name
=&gt; "Mekhi Lebsack"
&gt;&gt; Faker::Name.name
=&gt; "Esteban Brown"
&gt;&gt; Faker::Name.name
=&gt; "Velma Crona"
&gt;&gt; Faker::Name.name
=&gt; "Rollin Zemlak"
&gt;&gt; Faker::Name.name
=&gt; "Casper Sanford"

# My favorite is coming up with BS (yes, BS is what you think it is):
&gt;&gt; Faker::Company.bs
=&gt; "redefine ubiquitous e-commerce"
&gt;&gt; Faker::Company.bs
=&gt; "synergize viral e-business"
&gt;&gt; Faker::Company.bs
=&gt; "innovate real-time experiences"
&gt;&gt; Faker::Company.bs
=&gt; "aggregate scalable systems"
&gt;&gt; Faker::Company.bs
=&gt; "e-enable back-end functionalities"
&gt;&gt; Faker::Company.bs
=&gt; "implement 24/365 models"
&lt;/code&gt;
So the point of faker, if you haven't already figured out is that it generates data the way you would probably have in production so you are generating meaningful data in your development environment(s) where you want to show your client what the interface will look like with data inside.

&lt;b&gt;Now a word of caution:&lt;/b&gt; I would not advise you to use this in an end-user facing test environment.  I sometimes call that staging or qa, but ultimately it is an environment where real end-users or clients test out the features in a stable environment (not development or continuous integration environment) so they can sign off on your stories.  In these environments you should let the user create the data themselves using the admin forms or whatever UI you provide them because you want to see how they will break the system (and we all know they will somehow).  It gives you a much better indication of how things will end up working in production, which is very important.

Thanks for reading and let me know if this is yet again too rambly?  How does it happen?  I have the best intentions to just blurt out tidbits with no nuance, yet I always end up rambling on about things and give it a thought process (well sort of).  Anyone want to be my editor to strip out all thought and insight so I can be just blog brainless "tip" blog posts like everyone else in the Rails community that wanted to be a micro celebrity (trust me I couldn't handle that kind of paparazzi attention and I don't even have a Macbook Pro to sling over my shoulder in a fashionable messenger bag)?  I can't pay you anything, but you could share in my measely $0.63 that Google Adsense pays me each month (actually they don't actually pay until a threshold is met).  You could buy one third of an MP3 from iTunes each month.  Any takers?  What an awesome deal!  No really!

PS Coming soon a simple generator and Rails application template (or applate as I like to call them) that will set you up with this so you don't have to copy and paste all the time.  It'll be on GitHub.com/mbbx6spp.  Ta ta.

&lt;b&gt;Update:&lt;/b&gt; As Damien in the comments pointed out indirectly, I really didn't do Faker justice.  Please read the &lt;a href="http://faker.rubyforge.org/rdoc/" target="_new"&gt;Faker RDocs&lt;/a&gt; for more information.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-5096397482927613381?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/5096397482927613381/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=5096397482927613381' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5096397482927613381'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5096397482927613381'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2009/09/faker-ruby-on-rails-snippet-generator.html' title='Faker, Ruby on Rails Snippet &amp; Generator'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_YUkA0L0Czls/SqhxBu4ZNzI/AAAAAAAAAIo/33EMivvSZsw/s72-c/twitter-screenshot.png' height='72' width='72'/><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-5982449507200889600</id><published>2009-07-03T10:05:00.006-05:00</published><updated>2009-07-03T10:44:46.751-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='design patterns'/><title type='text'>Design Patterns Book Club</title><content type='html'>I just discovered that &lt;a href="http://www.ustream.tv/channel/hashrocket" target="_new"&gt;Hashrocket has a video book club&lt;/a&gt; where people can join in the conversation with a live videocast on USTREAM or watch older episodes on their own schedule.  They are currently going through &lt;a href="http://www.amazon.com/Design-Patterns-Ruby-Addison-Wesley-Professional/dp/0321490452/ref=sr_1_1?ie=UTF8&amp;s=books&amp;qid=1246633743&amp;sr=8-1&amp;tag=supo-20"&gt;Russ Olson's Design Patterns in Ruby&lt;/a&gt;.

This is a great idea, kudos to Hashrocket.

I watched the latest episode on the &lt;a href="http://www.ustream.tv/recorded/1700942" target="_new"&gt;Strategy pattern&lt;/a&gt; and had a couple of thoughts I would love Desi and the rest of Hashrocket to consider for subsequent episodes:
&lt;ul&gt;&lt;li&gt;I liked that this specific episode wasn't very long (I know you were a little worried)&lt;/li&gt;&lt;li&gt;I think discussing anti-patterns that could be solved with this pattern you are discussing the episode is very valid (as the person sitting next to Desi said).  Perhaps that could be explored more.  Note: I have only seen that one episode so perhaps you do this in other episodes much more, but if you don't please consider it, because I feel that anti-patterns are very important to identify.  &lt;strong&gt;&lt;em&gt;Think of the design patterns as the treatment or medicine and the anti-patterns as the symptoms.  Physicians need to be acutely aware of the symptoms and symptom combinations to be effective at prescribing the best course of treatment or medication.  Therefore we should be teaching software engineers in the same way.&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;Also please consider putting the name of the design pattern under discussion in the title of the video for archival purposes.&lt;/li&gt;&lt;/ul&gt;
Thanks goes to Hashrocket for the great idea and work of putting it together!  It's about time the Ruby community starts to mature w.r.t. language-neutral concepts and these efforts will hopefully help those that came into software development from a less formal software engineering education path (I used to be one of them and had to learn from scratch myself) with a little more assistance. It was pretty lonely bookwork and experimental projects for me to learn this stuff when I first started out.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-5982449507200889600?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/5982449507200889600/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=5982449507200889600' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5982449507200889600'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5982449507200889600'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2009/07/design-patterns-book-club.html' title='Design Patterns Book Club'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-5308617635880711251</id><published>2009-05-29T13:00:00.013-05:00</published><updated>2009-06-02T10:16:47.337-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Jabber'/><category scheme='http://www.blogger.com/atom/ns#' term='SOA'/><category scheme='http://www.blogger.com/atom/ns#' term='HTTP'/><category scheme='http://www.blogger.com/atom/ns#' term='Google Wave'/><category scheme='http://www.blogger.com/atom/ns#' term='Service Oriented Architecture'/><category scheme='http://www.blogger.com/atom/ns#' term='web services'/><category scheme='http://www.blogger.com/atom/ns#' term='google'/><category scheme='http://www.blogger.com/atom/ns#' term='XMPP'/><title type='text'>Google Wave is not the answer, XMPP is!</title><content type='html'>Since yesterday afternoon (my time) all over the Twittersphere (and blogosphere) I kept hearing a ton of great reviews of the Google Wave API/protocol and how it will revolutionize communication and collaboration on the web.

This morning I watched the Google Wave demonstration from yesterday and I have to say I am completely underwhelmed considering everyone else's reactions were ridiculously positive and had nothing measured or thoughtful to say about it.

You can find my initial reaction of it, &lt;a href="http://tumblelog.susanpotter.net/post/114911182/google-wave-is-just-a-simple-xmpp-extension-shrink"&gt;&lt;em&gt;"Google Wave is just a simple &lt;acronym title="Extensible Messaging and Presence Protocol"&gt;XMPP&lt;/acronym&gt; extnesion"&lt;/em&gt;, on my tumblelog&lt;/a&gt;.

As someone that has had to write &lt;acronym title="Extensible Messaging and Presence Protocol"&gt;XMPP&lt;/acronym&gt; extensions for clients and my own internal apps, servers, API interfaces (mostly distributed monitoring of services, but a few more for media processing) I am saddened that anyone would think the current Google Wave protocol (which is just an &lt;acronym title="Extensible Messaging and Presence Protocol"&gt;XMPP&lt;/acronym&gt; extension) is extraordinarily ground breaking.  There have been a number of existing extensions that tried to do VERY VERY similar things already and specific features of Google Wave were already exactly defined.

What made the demonstration of Google Wave yesterday &lt;em&gt;appear&lt;/em&gt; so impressive were the applications: the wave client and the web embedding.  The responsiveness of the web application really made it stand out, NOT the underlying Google Wave protocol/API.

For those who have never really gotten their hands dirty with &lt;acronym title="Extensible Messaging and Presence Protocol"&gt;XMPP&lt;/acronym&gt; in any real technical way, I expected they would not really know what it already provided in &lt;acronym title="Extensible Messaging and Presence Protocol"&gt;XMPP&lt;/acronym&gt;.  Many &lt;em&gt;"geeks"&lt;/em&gt; hyping up Google Wave seemed to think &lt;acronym title="Extensible Messaging and Presence Protocol"&gt;XMPP&lt;/acronym&gt; only supported instant messaging.  That just happened to be the most popular and apparent &lt;acronym title="Extensible Messaging and Presence Protocol"&gt;XMPP&lt;/acronym&gt; extension, which many people call Jabber.  &lt;acronym title="Extensible Messaging and Presence Protocol"&gt;XMPP&lt;/acronym&gt; itself is a lot more far reaching than just an instant messaging protocol and the fact that Google Wave was based on top of &lt;acronym title="Extensible Messaging and Presence Protocol"&gt;XMPP&lt;/acronym&gt; should tell you how flexible it is.  Yes that is right, Google may not have really made a big deal about it (why would they if they can get extra credit when it isn't due), but Google Wave &lt;strong&gt;is&lt;/strong&gt; just an &lt;acronym title="Extensible Messaging and Presence Protocol"&gt;XMPP&lt;/acronym&gt; extension and compared to other extensions I have had to interface with, not a particularly complicated one.

Sure the &lt;strong&gt;&lt;em&gt;applications&lt;/em&gt;&lt;/strong&gt; that Google demonstrated as part of the Google Wave presentation were nifty for personal communication, but are they really ground breaking, multipurpose, or ubiquitous?  No, it is only as ground breaking, multipurpose and ubiquitous as it's applications make it.

Let us all now return to earth for a moment and praise Google for the things they do really well like web applications, but let us not pretend that Google is the mover and shaker in terms of the visionary of future underlying protocols.  They still advocate developing &lt;acronym title="HyperText Transfer Protocol"&gt;HTTP&lt;/acronym&gt; applications for the future.

HyperText Transfer Protocol (&lt;acronym title="HyperText Transfer Protocol"&gt;HTTP&lt;/acronym&gt;) is virtually on every digital device made by man today, but does it really lend itself to providing the distributed service oriented architecture we should be striving for and the protocol of the &lt;em&gt;future&lt;/em&gt;?  REST is not about the &lt;acronym title="HyperText Transfer Protocol"&gt;HTTP&lt;/acronym&gt; protocol.  RESTful APIs can be creating in a number of ways and through a lot of different protocols.

My take is (from a technical level) &lt;acronym title="Extensible Messaging and Presence Protocol"&gt;XMPP&lt;/acronym&gt; ought to be the protocol of the future, NOT &lt;acronym title="Extensible Messaging and Presence Protocol"&gt;XMPP&lt;/acronym&gt; on just the back-end and then adding tons of bloatware in between the client and the service just so that we can create an API or UI on top of an antiquated protocol (&lt;acronym title="HyperText Transfer Protocol"&gt;HTTP&lt;/acronym&gt;) that has seen better days.

Anyone want to create a truly modular &lt;acronym title="Extensible Messaging and Presence Protocol"&gt;XMPP&lt;/acronym&gt; "browser" for the new &lt;em&gt;"web"&lt;/em&gt; with me?  Email &lt;code&gt;me&lt;/code&gt; at &lt;code&gt;susanpotter&lt;/code&gt; dot &lt;code&gt;net&lt;/code&gt; if you want to join the future.  

UPDATE: Please subscribe to the &lt;a href="http://groups.google.com/group/jointhefuture" target="_new"&gt;"Join the Future" Google Group&lt;/a&gt; if you want to get involved in the XMPP browser project.

&lt;h4&gt;Related Links&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://xmpp.org/" target="_new"&gt;XMPP Standards Foundation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://xmpp.org/protocols/" target="_new"&gt;XMPP Protocols&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://xmpp.org/extensions/" target="_new"&gt;XMPP Open Extensions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-5308617635880711251?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/5308617635880711251/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=5308617635880711251' title='12 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5308617635880711251'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5308617635880711251'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2009/05/google-wave-is-not-answer-xmpp-is.html' title='Google Wave is not the answer, XMPP is!'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-9189369663339915909</id><published>2009-05-20T16:13:00.004-05:00</published><updated>2009-05-20T16:53:58.937-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='functional programming'/><category scheme='http://www.blogger.com/atom/ns#' term='currying'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>Erlang doesn't support currying? News to me!</title><content type='html'>I keep seeing developers who only glance at Erlang and think that &lt;strong&gt;currying&lt;/strong&gt; isn't supported.  &lt;strong&gt;Nothing could be further from the truth...&lt;/strong&gt;&lt;em&gt;unless you are defining &lt;strong&gt;currying&lt;/strong&gt; support only in terms of specific syntactic sugar in a language for currying.&lt;/em&gt;

So let us step back and define what currying support really means:
&lt;blockquote&gt;
Currying is the process of transforming a function that takes multiple arguments into a function that takes just a single argument and returns another function if any arguments are still needed.
&lt;cite&gt;&lt;a href="http://www.haskell.org/haskellwiki/Currying" target="_new"&gt;http://www.haskell.org/haskellwiki/Currying&lt;/a&gt;&lt;/cite&gt;
&lt;/blockquote&gt;

Let us look at a simple (but hopefully not too contrived, albeit it _slightly_ contrived) example:
&lt;pre&gt;&lt;code&gt;
%% currying_example.erl
-module(currying_example).

-compile([export_all]).

multiply(X, Y) -&gt; X*Y.
doubler() -&gt; fun(X) -&gt; multiply(2, X) end.
&lt;/code&gt;&lt;/pre&gt;
In your erl shell try the following:
&lt;pre&gt;&lt;code&gt;
1&gt; c(currying_example).
{ok,currying_example}
2&gt; D = currying_example:doubler().
#Fun&lt;currying_example.0.96239340&gt;
3&gt; D(6).
12
4&gt; q().
ok
&lt;/code&gt;&lt;/pre&gt;

As you can see in the module code above, Erlang supports what is called &lt;em&gt;anonymous functions&lt;/em&gt; (e.g. fun (X) -&gt; somePredefineFunction(OtherVarOrConst, X) end), which then allows us to manufacture single argument functions in a way that is clear and expressive in the code IMO.

For those that haven't been following the functional programming debates over the last week or two around the blogosphere, please see the following blog posts (some are pretty funny and will make you smile at least twice with their intended absurdity):
&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://enfranchisedmind.com/blog/posts/scala-not-functional/" rel="nofollow" target="_new"&gt;Scala is not a functional programming language&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://www.reddit.com/goto?id=8krbo" rel="nofollow" target="_new"&gt;Erlang is not a programming language&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://conal.net/blog/posts/the-c-language-is-purely-functional/" rel="nofollow" target="_new"&gt;The C language is purely functional&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://12monkeys.co.uk/2009/05/16/erlang-is-pragmatic.html" rel="nofollow" target="_new"&gt;Erlang is pragmatic&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

Previous posts on this blog that are related are:
&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://geek.susanpotter.net/2009/04/scala-vs-erlang-debate-part-1-managers.html"&gt;Scala vs Erlang Debate, Part 1: Manager's Overview&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://geek.susanpotter.net/2009/04/scala-vs-erlang-debate-part-2-geek-off.html"&gt;Scala vs Erlang Debate, Part 2: Geek Off&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

I will try to put together a few snippets on currying support in Python, Javascript and Ruby tonight after my Greek class (which I am late for now) if I have time as a quick exercise:)&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-9189369663339915909?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/9189369663339915909/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=9189369663339915909' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/9189369663339915909'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/9189369663339915909'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2009/05/erlang-doesnt-support-currying-news-to.html' title='Erlang doesn&apos;t support currying? News to me!'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-4349350825811956698</id><published>2009-05-18T12:16:00.005-05:00</published><updated>2009-05-18T12:39:49.008-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='REST'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R 0.3.1 and the future</title><content type='html'>Hi Twitter4R users,

I know I have been rather absent in terms of my Twitter4R contributions and ongoing development, but despite my absence I have &lt;em&gt;_finally_&lt;/em&gt; officially released 0.3.1 with a lot more new features and a few bug fixes more than I initially expected to release 0.3.1 with.

Some of the goodies included in the official 0.3.1 release are:
&lt;ul&gt;&lt;li&gt;Added &lt;em&gt;raw&lt;/em&gt; Twitter Search API support.  Meaning you can use any of the HTTP parameters the Twitter HTTP-based Search API will expect.  E.g. &lt;code&gt;twitter.search(:to =&gt; "twitter4r", :rpp =&gt; 10)&lt;/code&gt; or &lt;code&gt;twitter.search(:q =&gt; "twitter4r", :rpp =&gt; 15, :geocode =&gt; "40.112186,-88.207592,250km")&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Profile, theme API coverage on Twitter REST API.&lt;/li&gt;&lt;li&gt;Social Graphing API. E.g. &lt;code&gt;twitter.graph(:friends, user)&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Added "reply" support at all levels (low-level to model-level)&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Bugfix:&lt;/strong&gt; the infamous &lt;code&gt;URI.encode&lt;/code&gt; to &lt;code&gt;CGI.escape&lt;/code&gt; change.&lt;/li&gt;&lt;li&gt;Rate limit information (still needs to be able to support the HTTP header method, which I actually suggested to Twitter developers some time back, but they only recently added it when my bandwidth for OSS contribution was minimal) :(&lt;/li&gt;&lt;li&gt;&lt;a href="http://wiki.github.com/mbbx6spp/twitter4r/twitter4r-0-3-1-development-release"&gt;much more&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;

Now the future of the Twitter4R project.  I have made a LOT of mistakes with this Twitter4R OSS project venture, so I am going to try to fix a bunch of these things:
&lt;ul&gt;&lt;li&gt;Create a new set of Git repos on GitHub that separates the education portions (screencast, etc.) from the core source code of the project so that people do not need to download ridiculous amounts of data to their local drive just to fork Twitter4R on GitHub as it required at present.  &lt;strong&gt;Announcements about this will be coming soon!&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;Have a consistent issue tracking system for Twitter4R.  Previously it has appeared inconsistent to users of the Twitter4R library where to submit defect reports or new feature requests to.  So I have had to field these requests from the Google Groups mailing list to personal emails and also @twitter4r replies on Twitter.  &lt;strong&gt;I plan on using GitHub's new issue tracking system for this as it seems a logical extension.&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;I plan on documenting things a little more obviously on the official website from now one.  It appears many users did not want to go to the RDoc unless specifically pointed to it, even though a large majority of the library is fairly well documented in the RDocs.&lt;/li&gt;&lt;/ul&gt;

Please be patient while I fix these process/marketing/workflow issues of the project and in the mean time I will also be &lt;strong&gt;tackling OAuth support&lt;/strong&gt; as well.

Again thanks for your patience thus far and tweet me on twitter at either &lt;a href="http://twitter.com/SusanPotter" target="_new"&gt;@SusanPotter&lt;/a&gt; or &lt;a href="http://twitter.com/twitter4r" target="_new"&gt;@twitter4r&lt;/a&gt; if you have suggestions or questions about the direction.  If you have specific queries on using the actual API, please join the twitter4r-users Google Groups mailing list and search the archives for a solution first and then if you can't find a solution, please post a message giving the group members your environment details and the version of Twitter4R you are using.

Thanks!
Susan
Twitter4R Developer&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-4349350825811956698?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/4349350825811956698/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=4349350825811956698' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4349350825811956698'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4349350825811956698'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2009/05/twitter4r-031-and-future.html' title='Twitter4R 0.3.1 and the future'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-5726300406977243992</id><published>2009-04-20T15:47:00.028-05:00</published><updated>2010-02-11T23:08:24.198-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='functional programming'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='scalability'/><category scheme='http://www.blogger.com/atom/ns#' term='scala'/><category scheme='http://www.blogger.com/atom/ns#' term='jvm'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>The Scala vs Erlang Debate, Part 2: The Geek Off</title><content type='html'>In the &lt;a href="http://geek.susanpotter.net/2009/04/scala-vs-erlang-debate-part-1-managers.html"&gt;first part of The Scala vs Erlang Debate&lt;/a&gt; article series, I discussed various ways to go about adopting either Scala or Erlang and in what scenarios it made sense to go with one over the other from the manager's perspective.    In summary the major advantage of Scala is that it runs on the &lt;acronym title="Java Virtual Machine"&gt;JVM&lt;/acronym&gt; so it can access pre-existing Java libraries your enterprise has already developed.  This is also Scala's major disadvantage because it makes a rather large assumption (which in my opinion would be incorrect the majority of the time) that the Java libraries being used are thread-safe.  Erlang on the other hand doesn't allow you to integrate in the same OS process with Java libraries, because Erlang was built from the ground up with scalability and fault-tolerence in mind and ideally you shouldn't put Java into the run-time stack because it compromises Erlang's fault-tolerance features significantly.  Instead the preferred approach when writing the biggest bottlenecks of your system in Erlang and integrating with pre-existing Java libraries/system is to use &lt;acronym title="Service Oriented Architecture"&gt;SOA&lt;/acronym&gt; to create separate, but integrated systems that are easy to swap out and upgrade at a later point.  In my view, you should be refactoring your enterprise architecture by defining services anyway, since this will decouple systems and remove a great deal of headaches in IT maintenance and deployment.  Today I wanted to geek out and differentiate the two environments on a more technical level for the middle/technical managers.&lt;h4&gt;Scala: Technical Overview&lt;/h4&gt;Scala is an objected oriented, statically typed language with many functional programming capabilities built-in.  However, it must be noted (because few Scala documents do this) that Scala is hybrid actor programming language and therefore a number of assumptions cannot be made.  So by being a hybrid functional language it losses some of the benefits.  As well as it's Java run-time support on the &lt;acronym title="Java Virtual Machine"&gt;JVM&lt;/acronym&gt; it is also supported in Microsoft's Common Language Runtime (&lt;acronym title="Common Language Runtime"&gt;CLR&lt;/acronym&gt;) so .NET integration, in theory, is a breeze.  Again this also assumes that the .NET libraries you use from Scala are thread-safe.  On a related topic, .NET also has F# available, which is Microsoft's attempt at a &lt;em&gt;hybrid&lt;/em&gt; functional programming language also.  I have no comment on F# myself, since I have zero experience with it.  Please chime in on the comments section if you have experience with F# and are able to contrast it with Scala.  &lt;h4&gt;Erlang: Technical Overview&lt;/h4&gt;Erlang on the other hand is a dynamic, purer functional programming language that supports light-weight processes in its own run-time environment (separate from &lt;acronym title="Operating System"&gt;OS&lt;/acronym&gt; processes), which send messages to each other asynchronously.  One major side effect of being purer in its actor treatment is that there is virtually &lt;strong&gt;not state&lt;/strong&gt;.  There is no way to pass "state" around (a couple of minor exceptions), but there is a &lt;em&gt;lambda calculus&lt;/em&gt; optimization that can be taken advantage of that will ease away our troubles called &lt;em&gt;tail-recursion&lt;/em&gt;.  Tail-recursion only partially works in Scala.  For many developers that only have experience in procedural and Object-Oriented (&lt;acronym title="Object Oriented"&gt;OO&lt;/acronym&gt;) programming languages like Java, Python, Ruby, C++, C, Perl, C# this sounds completely &lt;em&gt;insane&lt;/em&gt;.  Two years ago I may have agreed with you, but today life is much simpler without state in my Erlang travels than when I had to write thread-safe Java code and I wish I had discovered this concept earlier.  I will not pretend this notion didn't take some getting used to but today I see things a lot clearer and I am thankful for that clarity.  &lt;h4&gt;Scala: Concurrency&lt;/h4&gt;Concurrency in Scala is modeled using a concept of &lt;em&gt;actors&lt;/em&gt;, except there are two types of &lt;em&gt;actors&lt;/em&gt;: thread-based and event-based actors.  Since Scala was implemented on top of the &lt;acronym title="Java Virtual Machine"&gt;JVM&lt;/acronym&gt;, which is stack-based, Scala had to compromise on Erlang's one actor approach.  This is because native threads, which the &lt;acronym title="Java Virtual Machine"&gt;JVM&lt;/acronym&gt; uses for concurrency support, are much heavier-weight than Erlang's lightweight processes thus reducing the number of thread-based actors that could be launched inside one &lt;acronym title="Java Virtual Machine"&gt;JVM&lt;/acronym&gt;.  So Scala created this concept of a very peared down event-based actor, which does not have all the functionality of thread-based actors but does not incur the penalty of needing to be backed by a native thread.  To be as complete as I can be in this section I should also note that Scala treats everything as an object, even though it has these two types of actors.  Actors in Scala are just objects.  In turn this may create duplication between objects and actors.  &lt;h4&gt;Erlang: Concurrency&lt;/h4&gt;The Erlang &lt;acronym title="Virtual Machine"&gt;VM&lt;/acronym&gt; was built from the bottom up with lightweight processes (a.k.a. actors), therefore it is stackless and much better at lightweight concurrency.  Erlang also has a built-in mechanism for load balancing these lightweight processes across all available CPU cores.  This means Erlang has a massive advantage over JVM based functional programming languages at getting the most out of multi-core hardware, which is at the crux of the problem domain that software needs to address going forward.  &lt;h4&gt;Scala vs. Erlang: Performance&lt;/h4&gt;In terms of linear performance (sequential) Scala has been shown to beat Erlang in certain cases.  However, concurrent performance consistently shows Erlang beating Scala, sometimes by wide margins on multi-core hardware.  Not surprising considering what we learned in the concurrency sections above.  When considering the problem we are trying to overcome - optimizing software on multi-core hardware - it seems clear which of these benchmarks are more important! :)  &lt;h4&gt;Conclusion&lt;/h4&gt;I think it is clear, which side of this debate I firmly stand, but I have tried to demonstrate both good and bad traits of each programming language from a technical perspective that might translate into a business case or reason for choosing one over the other in your organization.  If I have time I might do a part 3, which will talk a little about syntax and DSL support.  This might round out the argument since it could be argued that Scala wins in the third part by a hair, but all is not lost for Erlang if the underlying problem you need to solve is related to optimizing software for modern hardware and building robust systems at the same time.  My vote still goes to Erlang despite the lack of built-in features that make designing &lt;acronym title="Domain Specific Language"&gt;DSL&lt;/acronym&gt;s easier, but it is not difficult to build your own &lt;acronym title="Domain Specific Language"&gt;DSL&lt;/acronym&gt; engine in Erlang either.  &lt;h4&gt;Limitations of this series:&lt;/h4&gt;There are quite a few topics I left out of this post that might be of interest for some applications or services.  Namely: &lt;ul&gt;&lt;li&gt;&lt;strong&gt;Hot swapping:&lt;/strong&gt; Erlang has great hot swapping capabilities for production environments that minimizes or possibly eliminates downtime of systems.  This feature in Scala is not close in capability or reliability at this point.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Garbage collection:&lt;/strong&gt; without getting into too many details Erlang probably wins this section by a hair (not significant to write home about).  Long sweeps will not be a problem in Erlang and only a problem in JVM languages on rare occasions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Distributed nodes:&lt;/strong&gt; The clear winner here would be Erlang because built-in to the environment are all the multi-node features that make distributed systems a breeze.  Scala on the other hand has different frameworks and libraries to facilitate distributing nodes of logic on different hosts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[Added Nov-2009] Frameworks:&lt;/strong&gt; As far as I know, Scala doesn't have an OTP killer, not even a contender to compete with Erlang's Open Telephony Platform (OTP).  Sure it will take some time, even Haskell, which has been around much longer than Scala is slightly jealous of OTP (I saw the way Don Stewart looked at me when I asked him if Haskell had framework similar to OTP;) ).  Supervision trees along make it stand out as the best thing since sliced bread, at least for this particular software engineer.&lt;/li&gt;
&lt;/ul&gt;&lt;h4&gt;Resources:&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://geek.susanpotter.net/2009/04/scala-vs-erlang-debate-part-1-managers.html"&gt;The Scala vs Erlang Debate, Part 1: The Manager's Overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;h4&gt;Corrections (added February 2010):&lt;/h4&gt;&lt;ul&gt;  &lt;li&gt;Erlang is not a pure functional language.  However, it does not have a polluted (impure) treatment of actors like Scala:)  Some think this is a good-enough approach to be able to leverage the JVM, when running on the JVM is really important, perhaps 4 times out of 5 it might be "good enough".  I just wonder if it really matters for most projects that you run on the JVM!?&lt;/li&gt;
  &lt;li&gt;"Scala's lack of tail recursion is entirely due to a trade-off. The JVM doesn't do tail recursion itself. It is possible to add tail recursion on top of the JVM using various tricks, but they are expensive. SISC does its own stack based heap and Kawa uses trampolining." -- James Iry&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-5726300406977243992?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/5726300406977243992/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=5726300406977243992' title='26 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5726300406977243992'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5726300406977243992'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2009/04/scala-vs-erlang-debate-part-2-geek-off.html' title='The Scala vs Erlang Debate, Part 2: The Geek Off'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>26</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-9116860869510075065</id><published>2009-04-15T09:37:00.017-05:00</published><updated>2009-04-15T11:36:34.775-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='functional programming'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='scala'/><category scheme='http://www.blogger.com/atom/ns#' term='jvm'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>The Scala vs Erlang Debate, Part 1: The Managers Overview</title><content type='html'>&lt;em&gt;Note: The discussion is primarily between Scala and Erlang, but other languages could potentially be substituted in the arguments for and against.  Scala stands for any language that attempts to solve the multi-core issue software developers face with threads and Erlang is a language that has light weight processes which solves the biggest issue of going with a multi-process solution in software.&lt;/em&gt;

The debate started with the realization that Moore's Law died a while ago.  Tomorrow (as we see it now) is destined for multi-cores.

A problem of massive proportions now faces software engineers, since hardware engineers changed the rules on us.  Now I am not giving hardware engineers a hard time, I am simply pointing out that there has been a massive shift in the way hardware is built, which directly affects the performance and reliability of the software which runs on it.  It is not a transparent change that software engineers can ignore.

With that background laid out, as software engineers, we must appreciate the problem we now have to tackle, both server-side and client-side developers alike will need to tackle these issues promptly.  However, this article only considers server-side software as there is where my specialized expertise lies.  This part of the article is the high-level.  I'll get into the specifics in the second part.

So the question now is how do we leverage the advantage given to us by new hardware progress of multi-cores?

There are two fundamentally different problems to solve: software products/projects that already have large complex systems already built in languages that support threads half-decently such as in Java and JVM languages including Scala; but there are also those startups that don't have baggage and can start with a better long-term solution by using a true concurrent language like Erlang.

&lt;h3&gt;The Legacy Enterprise&lt;/h3&gt;
The problem with utilizing JVM threading (whether directly in Java or via other languages built on the JVM such as Scala) is that third party code you use or existing Java/JVM code written isn't necessarily thread-safe.  You might have all your business entities and models defined in Java, but can it really be threaded?

The pain of migrating or porting your existing system from a JVM language to another truly concurrent language like Erlang might be much greater than dealing with threading issues in just a few key bottleneck areas that will increase throughput and CPU utilization &lt;strong&gt;enough&lt;/strong&gt;.  In this case I doubt many pragmatic engineers or architects will advocate starting from scratch in a language you know little (if anything) about.  However, identifying those key pain points might take some time, but will be a necessary cost if you are serious about solving this problem in the near-term using a solution like Scala.

However, what these engineers and architects should stress to management is that this is likely to become a monkey patch.  There will be a tipping point where moving to a pure concurrent language like Erlang would be more beneficial that sticking to the monkey patch and living on the JVM forever, no matter what Sun tries to tell you.

There are hybrid solutions for the medium-term, that depending on the project/enterprise specifics may be a good solution until you are able to fully port the code to a langauge like Erlang (or whatever the trend may be at the time you get to it).  &lt;acronym title="Service Oriented Architecture"&gt;SOA&lt;/acronym&gt; principles can be used to design and define a meaningful architecture that will declutter the system and allow you to tackle just the bottlenecks of the system in an Erlang port/rewrite and still give you (the manager) a low risk &lt;acronym title="Proof of Concept"&gt;PoC&lt;/acronym&gt; project so you can evaluate Erlang in your enterprise.

Having worked on a number of large enterprise projects where &lt;em&gt;upgrading&lt;/em&gt; the "legacy" system was the goal, I know full well that management are likely to prefer the monkey patches (i.e. Scala where the pain of rewriting components in a PoC way doesn't exceed the pain of threading your existing code at the bottlenecks), especially when the middle/technical managers do not highlight the drawbacks of the "solution" adequately.  Three to nine months later the management (especially middle management) are hurting big time, but it is not usually the most appropriate time to do your &lt;em&gt;"I told you so dance"&lt;/em&gt;.  Trust me!:)

The the monkey patch isn't an ideal solution when you are simply trading one complexity of equal of greater value for the other and in 2-5 years the likelihood that there will be many more skilled engineers and architects that truly understand and have solid production experience in a truly concurrent language like Erlang is high in my estimation, so making a move to such a language/environment would not be as risky at that time.  However, for 2-5 years you have been getting little out of your hardware and software together unless you overbuy hardware.  In fact, in the case where legacy code already exists, there is never really an ideal solution.  However, starting out with meaningful &lt;acronym title="Proof of Concept"&gt;PoC&lt;/acronym&gt;s and structuring low risk exit points after conducting objective evaluations of these projects is key to the success of every "legacy solution".  If in doubt and your legacy system isn't showing signs of hurting yet, you might want to wait and beef up on hardware.  This is a transition period and if you are in a conservative IT enterprise this might be a better near-term approach.

&lt;h3&gt;Startups&lt;/h3&gt;
Now for the startups or those starting afresh, there is mixed news: very good news and not-so-bad news.  

Green field projects, in theory, are always the best.  You can get everything right from day one....well at least you can hope to do so (let us pretend for this article).  My advise for your team is to take the plunge to Erlang.  It is actually relatively low risk to do so for the following reasons:
&lt;ul&gt;
  &lt;li&gt;It is a mature language and environment already tested by large and complex real world systems at telcos where reliability and scalability were and are top priorities.&lt;/li&gt;
  &lt;li&gt;There are a few good books out there on that will help you convert to Erlang and Functional Programming (see resources section below), although documentation and education on Erlang, generally, is somewhat lacking.&lt;/li&gt;
  &lt;li&gt;A framework for services has been designed, developed and put into production called OTP, which stands for the Open Telephony Platform and actually nothing specific to Telephony applications in it, it was simply developed by a Telco.&lt;/li&gt;
&lt;/ul&gt;

Erlang's approach to concurrency is interesting and takes some getting used to as it is a pure functional programming language unlike Scala which is a hybrid.  

Now the bad news is that there are very few people that know Erlang (or other pure concurrent languages), you need to hire at least one specialist with Erlang experience to guide your team to avoid the potholes and perhaps even stop you before you get to the edge of a cliff.  They might not be cheap, but starting out it will mitigate project risk significantly.

Another negative on the Erlang side of the argument is that you will not be able to use your Java classes straight from within Erlang (in a meaningful way - I'll cover this in the next part).  The positive about this negative though is that you can encapsulate your Java code in an SOA way so that you can decouple and declutter subsystems within your enterprise, which is an important task to do regardless of which legacy solution you choose.

Finally, good luck and let me know how it goes!

&lt;h4&gt;Resources&lt;/h4&gt;
&lt;h5&gt;&lt;a href="http://www.amazon.com/gp/product/193435600X?ie=UTF8&amp;tag=illinilistn09-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=193435600X"&gt;Programming Erlang: Software for a Concurrent World&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;l=as2&amp;o=1&amp;a=193435600X" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt; by Joe Armstrong&lt;/h5&gt;
&lt;p&gt;This book, for the moment, is the definitive guide for Erlang.  If you have already decided to go the Erlang route then you will not mind (or perhaps not even notice) the "selling" of Erlang throughout the book.  Overall this is an essential book to anyone learning Erlang at this point.&lt;/p&gt;

&lt;h5&gt;&lt;a href="http://www.amazon.com/gp/product/0596514980?ie=UTF8&amp;tag=illinilistn09-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0596514980"&gt;Real World Haskell&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;l=as2&amp;o=1&amp;a=0596514980" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt;&lt;/h5&gt;
&lt;p&gt;While Haskell isn't either Scala or Erlang, if you do not come from a functional programming frame of mind you will need a good book to introduce you gently into the realm of functional programming.  This book does a pretty good job.  While the syntax for Haskell is different to both Erlang and Scala, the hardest part of moving to functional programming isn't the syntax.  Trust me on this one:)&lt;/p&gt;

Also consider me, &lt;a href="http://www.linkedin.com/in/susanpotter"&gt;Susan Potter&lt;/a&gt;, a resource if you need an Erlang software engineer with high-level enterprise architecture background in Java/J2EE/JEE and Ruby/Rails production environments.  I am developing a risk management product in Erlang right now and I am available as a consultant through my consulting firm &lt;a href="http://finsignia.com"&gt;Finsignia&lt;/a&gt;, which mostly works with financial services firms such as investment banks and hedge funds, which is where my business domain expertise lies, but also works on designing, building and deploying reliable and robust server-side solutions for any industry.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-9116860869510075065?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/9116860869510075065/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=9116860869510075065' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/9116860869510075065'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/9116860869510075065'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2009/04/scala-vs-erlang-debate-part-1-managers.html' title='The Scala vs Erlang Debate, Part 1: The Managers Overview'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-297445119338360086</id><published>2009-04-04T20:13:00.005-05:00</published><updated>2009-04-04T20:34:05.044-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='chambana'/><category scheme='http://www.blogger.com/atom/ns#' term='BarCamp'/><category scheme='http://www.blogger.com/atom/ns#' term='champaign urbana'/><title type='text'>CUBarCamp: Champaign-Urbana's Tech Meetup Update</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_YUkA0L0Czls/SdgGQQ9SN4I/AAAAAAAAAHw/wgEm9qlUa_A/s1600-h/CUBarCamp.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 186px;" src="http://2.bp.blogspot.com/_YUkA0L0Czls/SdgGQQ9SN4I/AAAAAAAAAHw/wgEm9qlUa_A/s400/CUBarCamp.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5321009836237928322" /&gt;&lt;/a&gt;

This is just an update blog post to let geeks in and around the Champaign-Urbana area know that the CU BarCamp is now scheduled for April 25th, 2009.  The meetup will start at 2pm CDT and will be located at the Collective Turf Coworking space:
&lt;address&gt;110 W. Main Street
Urbana, IL 61801&lt;/address&gt;

It is right between the Heartland Gallery and Priceless Books and opposite the Urbana Business Association office and Crane Alley.

&lt;h3&gt;Want to participate?&lt;/h3&gt;
If you want to volunteer, present or demo any topics at this BarCamp please update the &lt;A href="http://barcamp.org/CUBarCamp"&gt;CUBarCamp wiki page&lt;/a&gt; and join the &lt;a href="http://groups.google.com/group/cubarcamp"&gt;Google Group for the CU BarCamp event&lt;/a&gt;.

Hope to see you there!&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-297445119338360086?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/297445119338360086/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=297445119338360086' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/297445119338360086'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/297445119338360086'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2009/04/cubarcamp-champaign-urbanas-tech-meetup.html' title='CUBarCamp: Champaign-Urbana&apos;s Tech Meetup Update'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_YUkA0L0Czls/SdgGQQ9SN4I/AAAAAAAAAHw/wgEm9qlUa_A/s72-c/CUBarCamp.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-7094876133553155591</id><published>2009-03-27T18:59:00.005-05:00</published><updated>2009-03-27T19:14:48.886-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='YUI'/><category scheme='http://www.blogger.com/atom/ns#' term='CSS'/><title type='text'>YUI MenuBar: A Facebook-like Application Bar</title><content type='html'>&lt;p&gt;Below is a snippet of HTML code with embedded Javascript code that will create a top anchored menu bar very similar to what you see at the bottom of the Facebook page:&lt;/p&gt;

&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&amp;lt;!-- Add the following to your HTML/HEAD --&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;http://yui.yahooapis.com/2.7.0/build/fonts/fonts-min.css&amp;quot;&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;http://yui.yahooapis.com/2.7.0/build/menu/assets/skins/sam/menu.css&amp;quot;&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;http://yui.yahooapis.com/2.7.0/build/yahoo-dom-event/yahoo-dom-event.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;http://yui.yahooapis.com/2.7.0/build/container/container_core-min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;http://yui.yahooapis.com/2.7.0/build/menu/menu-min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;
YAHOO.util.Event.onDOMReady(function () {
  var oMenuBar = new YAHOO.widget.MenuBar(&amp;quot;menubar&amp;quot;);
  oMenuBar.render();
});
&amp;lt;/script&amp;gt;

&amp;lt;!-- in your HTML/BODY modify the following for your own &amp;quot;Facebook Application Bar&amp;quot; menu --&amp;gt;
&amp;lt;div id=&amp;quot;menubar&amp;quot; class=&amp;quot;yuimenubar&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;bd&amp;quot;&amp;gt;
    &amp;lt;ul class=&amp;quot;first-of-type&amp;quot;&amp;gt;
      &amp;lt;li class=&amp;quot;yuimenubaritem first-of-type&amp;quot;&amp;gt;
        &amp;lt;a class=&amp;quot;yuimenubaritemlabel&amp;quot; href=&amp;quot;#file&amp;quot;&amp;gt;Applications&amp;lt;/a&amp;gt;
        &amp;lt;div id=&amp;quot;file&amp;quot; class=&amp;quot;yuimenu&amp;quot;&amp;gt;
          &amp;lt;div class=&amp;quot;bd&amp;quot;&amp;gt;
            &amp;lt;ul&amp;gt;
              &amp;lt;li class=&amp;quot;yuimenuitem&amp;quot;&amp;gt;
                &amp;lt;a class=&amp;quot;yuimenuitemlabel&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;Notes&amp;lt;em class=&amp;quot;helptext&amp;quot;&amp;gt;Ctrl + N&amp;lt;/em&amp;gt;&amp;lt;/a&amp;gt;
              &amp;lt;/li&amp;gt;
              &amp;lt;li class=&amp;quot;yuimenuitem&amp;quot;&amp;gt;
                &amp;lt;a class=&amp;quot;yuimenuitemlabel&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;Links&amp;lt;em class=&amp;quot;helptext&amp;quot;&amp;gt;Ctrl + L&amp;lt;/em&amp;gt;&amp;lt;/a&amp;gt;
              &amp;lt;/li&amp;gt;
              &amp;lt;li class=&amp;quot;yuimenuitem&amp;quot;&amp;gt;
                &amp;lt;a class=&amp;quot;yuimenuitemlabel&amp;quot; href=&amp;quot;#open&amp;quot;&amp;gt;Photos&amp;lt;em class=&amp;quot;helptext&amp;quot;&amp;gt;Ctrl + P&amp;lt;/em&amp;gt;&amp;lt;/a&amp;gt;
                &amp;lt;div id=&amp;quot;open&amp;quot; class=&amp;quot;yuimenu&amp;quot;&amp;gt;
                  &amp;lt;div class=&amp;quot;bd&amp;quot;&amp;gt;
                      &amp;lt;ul class=&amp;quot;first-of-type&amp;quot;&amp;gt;
                        &amp;lt;li class=&amp;quot;yuimenuitem&amp;quot;&amp;gt;
                            &amp;lt;a class=&amp;quot;yuimenuitemlabel&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;Upload Album&amp;lt;/a&amp;gt;
                        &amp;lt;/li&amp;gt;
                        &amp;lt;li class=&amp;quot;yuimenuitem&amp;quot;&amp;gt;
                            &amp;lt;a class=&amp;quot;yuimenuitemlabel&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;Browse Albums&amp;lt;/a&amp;gt;
                        &amp;lt;/li&amp;gt;
                      &amp;lt;/ul&amp;gt;            
                  &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;            
              &amp;lt;/li&amp;gt;
            &amp;lt;/ul&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;quot;yuimenubaritem first-of-type&amp;quot;&amp;gt;
        &amp;lt;a class=&amp;quot;yuimenubaritemlabel&amp;quot; href=&amp;quot;#&amp;quot;edit&amp;gt;Something Else&amp;lt;/a&amp;gt;
        &amp;lt;div id=&amp;quot;pim&amp;quot; class=&amp;quot;yuimenu&amp;quot;&amp;gt;
          &amp;lt;div class=&amp;quot;bd&amp;quot;&amp;gt;
            &amp;lt;ul class=&amp;quot;first-of-type&amp;quot;&amp;gt;
              &amp;lt;li class=&amp;quot;yuimenuitem&amp;quot;&amp;gt;
                &amp;lt;a class=&amp;quot;yuimenuitemlabel&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt; Bla 1&amp;lt;/a&amp;gt;
              &amp;lt;/li&amp;gt;
              &amp;lt;li class=&amp;quot;yuimenuitem&amp;quot;&amp;gt;
                &amp;lt;a class=&amp;quot;yuimenuitemlabel&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt; Bla 2&amp;lt;/a&amp;gt;
              &amp;lt;/li&amp;gt;
            &amp;lt;/ul&amp;gt;            
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

For further details about customizing the YUI MenuBar look and feel you should visit the &lt;a href="http://developer.yahoo.com/yui/docs/YAHOO.widget.MenuBar.html"&gt;API documentation&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-7094876133553155591?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/7094876133553155591/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=7094876133553155591' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7094876133553155591'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7094876133553155591'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2009/03/yui-menubar-facebook-like-application.html' title='YUI MenuBar: A Facebook-like Application Bar'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-7685492460714769518</id><published>2009-02-24T00:04:00.003-06:00</published><updated>2009-02-24T00:12:43.241-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='urbana'/><category scheme='http://www.blogger.com/atom/ns#' term='champaign'/><category scheme='http://www.blogger.com/atom/ns#' term='chambana'/><category scheme='http://www.blogger.com/atom/ns#' term='BarCamp'/><category scheme='http://www.blogger.com/atom/ns#' term='champaign urbana'/><title type='text'>CUBarCamp: Champaign-Urbana's Tech Meetup 4/22 (tentatively)</title><content type='html'>Just a quick post to mention that with the assistance of &lt;a href="http://www.exampler.com/"&gt;Brian Marick&lt;/a&gt; and hopefully a few more people in the weeks to come, &lt;a href="http://barcamp.org"&gt;BarCamp&lt;/a&gt; is coming to the &lt;a href="http://barcamp.org/CUBarCamp"&gt;Champaign-Urbana area&lt;/a&gt; and is to be held at the Collective Turf Coworking space in downtown Urbana (110 W. Main Street, Urbana, IL - opposite Crane Alley and above Priceless Books).

To keep updated on the latest developments and participate in the event organizing join the &lt;a href="http://groups.google.com/group/CUBarCamp"&gt;CUBarCamp Google Group&lt;/a&gt;.

Hope to see you there!&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-7685492460714769518?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/7685492460714769518/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=7685492460714769518' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7685492460714769518'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7685492460714769518'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2009/02/cubarcamp-champaign-urbanas-tech-meetup.html' title='CUBarCamp: Champaign-Urbana&apos;s Tech Meetup 4/22 (tentatively)'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-7527602399441387632</id><published>2009-02-09T15:26:00.006-06:00</published><updated>2009-02-09T17:31:03.766-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='coworking'/><title type='text'>Coworking Space in Champaign Urbana (Illinois)</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://twitter.com/SusanPotter/status/1193116121"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 222px;" src="http://1.bp.blogspot.com/_YUkA0L0Czls/SZC4RyhnKcI/AAAAAAAAAHg/Js4nD0qOQ4E/s400/tweet-coworking.png" border="0" alt="Tweet from @SusanPotter about starting a Champaign-Urbana Coworking environment/venue" id="BLOGGER_PHOTO_ID_5300939377175112130" /&gt;&lt;/a&gt;

Since I moved to Champaign-Urbana, IL (aka CU) in 2005 I have been looking for work space that would provide me with a more productive environment and social yet professional outlet to work in since I work primarily on telecommute contracts and lack face time with clients.

Today I decided that I will setup a local &lt;strong&gt;coworking&lt;/strong&gt; space for solo entrepreneurs and freelancers in and around the Champaign-Urbana area to fulfill my needs as a consultant and fulfill my new venture urge of late.

Essentially &lt;strong&gt;coworking&lt;/strong&gt; is defined as the following:
&lt;blockquote&gt;
&lt;strong&gt;Coworking&lt;/strong&gt; is a movement to create cafe-like community/collaboration spaces for developers, writers and independents.
&lt;/blockquote&gt;

This is exactly how I imagined my dream work space since I work a lot in coffee shops all around town throughout the work week and even during the weekend sometimes and find it surprisingly (initially) productive.

I have seen these types of office spaces in larger metro areas and the terminology used tends to be different from place to place.  Some terms used to mean a community of independent professionals sharing office space, facilities and amenities that I have come across on my travels include:
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Coworking&lt;/strong&gt; or Co-working (obviously)&lt;/li&gt;
  &lt;li&gt;Collaborative work environments (not used in the context of a company job description:))&lt;/li&gt;
  &lt;li&gt;Work Space Cooperative (this might be organized at the top levels slightly differently though)&lt;/li&gt;
  &lt;li&gt;Shared Work Space&lt;/li&gt;
  &lt;li&gt;Serviced Office Space&lt;/li&gt;
&lt;/ul&gt;

There are probably some slight variants on the definitions of these as compared to that of &lt;strong&gt;coworking&lt;/strong&gt;, so some places will have more structure and a more formal setting, whereas &lt;strong&gt;coworking&lt;/strong&gt; itself strives to create a more creative and collaborative environment for independent professionals.

If you are in the &lt;acronym title="Champaign-Urbana"&gt;CU&lt;/acronym&gt; (East Central Illinois) area and would be interested in working in such a &lt;strong&gt;coworking&lt;/strong&gt; space, please drop me a line at any of the following locations:
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;twitter:&lt;/strong&gt; &lt;a href="http://twitter.com/SusanPotter"&gt;@SusanPotter&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;email:&lt;/strong&gt; me at susanpotter dot net&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;facebook:&lt;/strong&gt; &lt;a href="http://www.facebook.com/people/Susan-Potter/706880787"&gt;My Profile&lt;/a&gt;&amp;nbsp|&amp;nbsp&lt;a href="http://www.facebook.com/group.php?gid=54723272076"&gt;Champaign-Urbana Coworking Group&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

Are you a freelancer or entrepreneur in another area or region that is part of a coworking environment experience there?  What do you like about your coworking space and why?  What would you change about it if you could?&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-7527602399441387632?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/7527602399441387632/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=7527602399441387632' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7527602399441387632'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7527602399441387632'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2009/02/coworking-space-in-champaign-urbana.html' title='Coworking Space in Champaign Urbana (Illinois)'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_YUkA0L0Czls/SZC4RyhnKcI/AAAAAAAAAHg/Js4nD0qOQ4E/s72-c/tweet-coworking.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-704071610769103721</id><published>2009-01-14T23:54:00.006-06:00</published><updated>2009-01-15T00:19:18.447-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='gedit'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>GEdit Extensions Project</title><content type='html'>Just a quick post from a long blogging hiatus to let people know that I launched gedit-extensions where I will maintain a set of commonly used GEdit snippets and eventually plugins and tools that others might find useful.

You can view the snippet source at the &lt;a href="http://github.com/finsignia/gedit-extensions/tree/"&gt;GitHub gedit-extensions repository&lt;/a&gt;.  I plan on adding documentation to the &lt;a href="http://wiki.github.com/finsignia/gedit-extensions"&gt;gedit-extensions wiki&lt;/a&gt; in the next couple of days, but for now see the sneak peak in the Ruby example snippets at the end of the post.  

The name in the comments directly above the example snippet is the shortcut you can use.  Type the first few characters of the shortcut and then &lt;tt&gt;Ctl+Space&lt;/tt&gt; key combination to pull up all shortcuts the characters match so far.  Choose the relevant shortcut from the drop down list.  If the snippet contains variables the first variable will be highlighted for your to fill in, then press &lt;tt&gt;TAB&lt;/tt&gt; to navigate to the next variable to fill in.

As you will see most of the shortcuts so far are Ruby, Rails, RSpec or Cucumber specific since there are a few Python snippets I need to untangle from our in-house code references.

&lt;pre&gt;&lt;span class="comment"&gt;# shebang&lt;/span&gt;
&lt;span class="comment"&gt;#!/usr/bin/env ruby&lt;/span&gt;
&lt;span class="comment"&gt;# absrequire&lt;/span&gt;
&lt;span class="ident"&gt;require&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="constant"&gt;File&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;join&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="constant"&gt;File&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;dirname&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="constant"&gt;__FILE__&lt;/span&gt;&lt;span class="punct"&gt;),&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;config&lt;/span&gt;&lt;span class="punct"&gt;",&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;enviornment&lt;/span&gt;&lt;span class="punct"&gt;")&lt;/span&gt;

&lt;span class="comment"&gt;# habtm&lt;/span&gt;
&lt;span class="ident"&gt;has_and_belongs_to_many&lt;/span&gt; &lt;span class="symbol"&gt;:idiots&lt;/span&gt;
&lt;span class="comment"&gt;# unique&lt;/span&gt;
&lt;span class="symbol"&gt;:unique&lt;/span&gt; &lt;span class="punct"&gt;=&gt;&lt;/span&gt; &lt;span class="constant"&gt;true&lt;/span&gt;
&lt;span class="comment"&gt;# habtmthru&lt;/span&gt;
&lt;span class="ident"&gt;has_and_belongs_to_many&lt;/span&gt; &lt;span class="symbol"&gt;:idiots&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="symbol"&gt;:through&lt;/span&gt; &lt;span class="punct"&gt;=&gt;&lt;/span&gt; &lt;span class="symbol"&gt;:village&lt;/span&gt;

&lt;span class="comment"&gt;# belongsto&lt;/span&gt;
&lt;span class="ident"&gt;belongs_to&lt;/span&gt; &lt;span class="symbol"&gt;:village&lt;/span&gt;

&lt;span class="comment"&gt;# createtable&lt;/span&gt;
&lt;span class="ident"&gt;create_table&lt;/span&gt; &lt;span class="symbol"&gt;:sessions&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;t&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt;
  &lt;span class="ident"&gt;t&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;column&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="symbol"&gt;:name&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="symbol"&gt;:string&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="symbol"&gt;:limit&lt;/span&gt; &lt;span class="punct"&gt;=&gt;&lt;/span&gt; &lt;span class="number"&gt;34&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
  &lt;span class="ident"&gt;t&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;column&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="symbol"&gt;:description&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="symbol"&gt;:string&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="symbol"&gt;:limit&lt;/span&gt; &lt;span class="punct"&gt;=&gt;&lt;/span&gt; &lt;span class="number"&gt;255&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="comment"&gt;# addindex&lt;/span&gt;
&lt;span class="ident"&gt;add_index&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="symbol"&gt;:sessions&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="symbol"&gt;:name&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;# changecolumn&lt;/span&gt;
&lt;span class="ident"&gt;change_column&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="symbol"&gt;:sessions&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="symbol"&gt;:name&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="symbol"&gt;:limit&lt;/span&gt; &lt;span class="punct"&gt;=&gt;&lt;/span&gt; &lt;span class="number"&gt;70&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;

&lt;span class="comment"&gt;# removecolumn&lt;/span&gt;
&lt;span class="ident"&gt;remove_column&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="symbol"&gt;:sessions&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="symbol"&gt;:name&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;# irreversible&lt;/span&gt;
&lt;span class="keyword"&gt;raise&lt;/span&gt; &lt;span class="constant"&gt;ActiveRecord&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;IrreversibleMigration&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;the reason the migration is irreversible&lt;/span&gt;&lt;span class="punct"&gt;"&lt;/span&gt;

&lt;span class="comment"&gt;# resource&lt;/span&gt;
&lt;span class="keyword"&gt;class &lt;/span&gt;&lt;span class="class"&gt;PersonResource&lt;/span&gt; &lt;span class="punct"&gt;&lt;&lt;/span&gt; &lt;span class="constant"&gt;ActiveResource&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Base&lt;/span&gt;
  &lt;span class="constant"&gt;self&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;site&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;http://domain.com:8080/&lt;/span&gt;&lt;span class="punct"&gt;"&lt;/span&gt;
  &lt;span class="constant"&gt;self&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;element_name&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;contacts&lt;/span&gt;&lt;span class="punct"&gt;"&lt;/span&gt;
  &lt;span class="constant"&gt;self&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;prefix&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;v1_0&lt;/span&gt;&lt;span class="punct"&gt;"&lt;/span&gt;
  &lt;span class="constant"&gt;self&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;user&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;supo&lt;/span&gt;&lt;span class="punct"&gt;"&lt;/span&gt;
  &lt;span class="constant"&gt;self&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;password&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;geditrocks&lt;/span&gt;&lt;span class="punct"&gt;"&lt;/span&gt;
  &lt;span class="constant"&gt;self&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;timeout&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="number"&gt;60&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="comment"&gt;# describe&lt;/span&gt;
&lt;span class="ident"&gt;describe&lt;/span&gt; &lt;span class="constant"&gt;PersonResource&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
  &lt;span class="ident"&gt;before&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="symbol"&gt;:each&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
    &lt;span class="attribute"&gt;@resource&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;mock&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="constant"&gt;PersonResource&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
  &lt;span class="keyword"&gt;end&lt;/span&gt;

  &lt;span class="ident"&gt;it&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;should bla&lt;/span&gt;&lt;span class="punct"&gt;"&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
    
  &lt;span class="keyword"&gt;end&lt;/span&gt;

  &lt;span class="ident"&gt;after&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="symbol"&gt;:each&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
    &lt;span class="attribute"&gt;@resource&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="constant"&gt;nil&lt;/span&gt;
  &lt;span class="keyword"&gt;end&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="comment"&gt;# specmodelerror&lt;/span&gt;
&lt;span class="ident"&gt;it&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;should have an error on name&lt;/span&gt;&lt;span class="punct"&gt;"&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
  &lt;span class="attribute"&gt;@user&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;have&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="number"&gt;1&lt;/span&gt;&lt;span class="punct"&gt;).&lt;/span&gt;&lt;span class="ident"&gt;errors_on&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="symbol"&gt;:name&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="comment"&gt;# specmodelrecords&lt;/span&gt;
&lt;span class="ident"&gt;it&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;should have 50 records&lt;/span&gt;&lt;span class="punct"&gt;"&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
  &lt;span class="attribute"&gt;@states&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;have&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="number"&gt;50&lt;/span&gt;&lt;span class="punct"&gt;).&lt;/span&gt;&lt;span class="ident"&gt;records&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="comment"&gt;# spectemplaterendered&lt;/span&gt;
&lt;span class="ident"&gt;it&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;should render 'common/user' on get '/user'&lt;/span&gt;&lt;span class="punct"&gt;"&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
  &lt;span class="ident"&gt;get&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span class="string"&gt;/user&lt;/span&gt;&lt;span class="punct"&gt;')&lt;/span&gt;
  &lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;render_template&lt;/span&gt;&lt;span class="punct"&gt;("&lt;/span&gt;&lt;span class="string"&gt;common/user&lt;/span&gt;&lt;span class="punct"&gt;"))&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="comment"&gt;# specresponsetext&lt;/span&gt;
&lt;span class="ident"&gt;it&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;should return text 'You were signed out.' in response to post on '/signout'&lt;/span&gt;&lt;span class="punct"&gt;"&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
  &lt;span class="ident"&gt;post&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span class="string"&gt;/signout&lt;/span&gt;&lt;span class="punct"&gt;')&lt;/span&gt;
  &lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;have_text&lt;/span&gt;&lt;span class="punct"&gt;("&lt;/span&gt;&lt;span class="string"&gt;You were signed out.&lt;/span&gt;&lt;span class="punct"&gt;")&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="comment"&gt;# specresponseredirect&lt;/span&gt;
&lt;span class="ident"&gt;it&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;should redirect to action 'index' on post '/users'&lt;/span&gt;&lt;span class="punct"&gt;"&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
  &lt;span class="ident"&gt;post&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span class="string"&gt;/users&lt;/span&gt;&lt;span class="punct"&gt;')&lt;/span&gt;
  &lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;redirect_to&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="symbol"&gt;:action&lt;/span&gt; &lt;span class="punct"&gt;=&gt;&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span class="string"&gt;index&lt;/span&gt;&lt;span class="punct"&gt;')&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="comment"&gt;# specroutefor&lt;/span&gt;
&lt;span class="ident"&gt;it&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;should map #index to '/users'&lt;/span&gt;&lt;span class="punct"&gt;"&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
  &lt;span class="ident"&gt;route_for&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="symbol"&gt;:controller&lt;/span&gt; &lt;span class="punct"&gt;=&gt;&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;users&lt;/span&gt;&lt;span class="punct"&gt;",&lt;/span&gt; &lt;span class="symbol"&gt;:action&lt;/span&gt; &lt;span class="punct"&gt;=&gt;&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;index&lt;/span&gt;&lt;span class="punct"&gt;").&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="punct"&gt;==&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;/users&lt;/span&gt;&lt;span class="punct"&gt;"&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="comment"&gt;# specparamsfrom&lt;/span&gt;
&lt;span class="ident"&gt;it&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;should generate params for #update&lt;/span&gt;&lt;span class="punct"&gt;"&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
  &lt;span class="ident"&gt;params_from&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="symbol"&gt;:put&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;/users/1&lt;/span&gt;&lt;span class="punct"&gt;").&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="punct"&gt;==&lt;/span&gt; &lt;span class="punct"&gt;{&lt;/span&gt;&lt;span class="symbol"&gt;:controller&lt;/span&gt; &lt;span class="punct"&gt;=&gt;&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;users&lt;/span&gt;&lt;span class="punct"&gt;",&lt;/span&gt; &lt;span class="symbol"&gt;:action&lt;/span&gt; &lt;span class="punct"&gt;=&gt;&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;update&lt;/span&gt;&lt;span class="punct"&gt;",&lt;/span&gt; &lt;span class="symbol"&gt;:id&lt;/span&gt; &lt;span class="punct"&gt;=&gt;&lt;/span&gt; &lt;span class="punct"&gt;"&lt;/span&gt;&lt;span class="string"&gt;1&lt;/span&gt;&lt;span class="punct"&gt;"}&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="comment"&gt;# spectagexists&lt;/span&gt;
&lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;have_tag&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span class="string"&gt;ul.menu#sidebar&lt;/span&gt;&lt;span class="punct"&gt;')&lt;/span&gt;

&lt;span class="comment"&gt;# specnestedtagexists&lt;/span&gt;
&lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;have_tag&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span class="string"&gt;ul.menu&lt;/span&gt;&lt;span class="punct"&gt;')&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
  &lt;span class="ident"&gt;with_tag&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span class="string"&gt;li&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span class="string"&gt;my menu item&lt;/span&gt;&lt;span class="punct"&gt;')&lt;/span&gt;
  &lt;span class="comment"&gt;# specchildtagexists&lt;/span&gt;
  &lt;span class="ident"&gt;with_tag&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span class="string"&gt;li&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span class="string"&gt;my second menu item&lt;/span&gt;&lt;span class="punct"&gt;')&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="comment"&gt;# specblocktagexists&lt;/span&gt;
&lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;[&lt;/span&gt;&lt;span class="symbol"&gt;:sidebar&lt;/span&gt;&lt;span class="punct"&gt;].&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;have_tag&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span class="string"&gt;ul#sidebar&lt;/span&gt;&lt;span class="punct"&gt;')&lt;/span&gt;

&lt;span class="comment"&gt;# mockviewhelper&lt;/span&gt;
&lt;span class="ident"&gt;template&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should_receive&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="symbol"&gt;:curent_user&lt;/span&gt;&lt;span class="punct"&gt;).&lt;/span&gt;&lt;span class="ident"&gt;and_return&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;mock&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="constant"&gt;User&lt;/span&gt;&lt;span class="punct"&gt;))&lt;/span&gt;

&lt;span class="comment"&gt;# specsuccess&lt;/span&gt;
&lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;be_success&lt;/span&gt;

&lt;span class="comment"&gt;# specredirect&lt;/span&gt;
&lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;be_redirect&lt;/span&gt;

&lt;span class="comment"&gt;# feature&lt;/span&gt;
&lt;span class="constant"&gt;Feature&lt;/span&gt;&lt;span class="punct"&gt;:&lt;/span&gt; &lt;span class="constant"&gt;Search&lt;/span&gt; &lt;span class="constant"&gt;Jobs&lt;/span&gt;
  &lt;span class="constant"&gt;In&lt;/span&gt; &lt;span class="ident"&gt;order&lt;/span&gt; &lt;span class="ident"&gt;to&lt;/span&gt; &lt;span class="ident"&gt;find&lt;/span&gt; &lt;span class="ident"&gt;relevant&lt;/span&gt; &lt;span class="ident"&gt;jobs&lt;/span&gt;
  &lt;span class="constant"&gt;As&lt;/span&gt; &lt;span class="ident"&gt;a&lt;/span&gt; &lt;span class="constant"&gt;Job&lt;/span&gt; &lt;span class="constant"&gt;Seeker&lt;/span&gt;
  &lt;span class="constant"&gt;I&lt;/span&gt; &lt;span class="ident"&gt;need&lt;/span&gt; &lt;span class="ident"&gt;to&lt;/span&gt; &lt;span class="ident"&gt;find&lt;/span&gt; &lt;span class="ident"&gt;jobs&lt;/span&gt; &lt;span class="ident"&gt;relevant&lt;/span&gt; &lt;span class="ident"&gt;to&lt;/span&gt; &lt;span class="ident"&gt;me&lt;/span&gt;

  &lt;span class="constant"&gt;Scenario&lt;/span&gt;&lt;span class="punct"&gt;:&lt;/span&gt; &lt;span class="constant"&gt;Location&lt;/span&gt; &lt;span class="constant"&gt;Search&lt;/span&gt;
    &lt;span class="constant"&gt;Given&lt;/span&gt; &lt;span class="ident"&gt;a&lt;/span&gt; &lt;span class="ident"&gt;search&lt;/span&gt; &lt;span class="ident"&gt;form&lt;/span&gt;
    &lt;span class="constant"&gt;When&lt;/span&gt; &lt;span class="constant"&gt;I&lt;/span&gt; &lt;span class="ident"&gt;enter&lt;/span&gt; &lt;span class="ident"&gt;my&lt;/span&gt; &lt;span class="ident"&gt;zip&lt;/span&gt; &lt;span class="ident"&gt;code&lt;/span&gt;
    &lt;span class="constant"&gt;Then&lt;/span&gt; &lt;span class="constant"&gt;I&lt;/span&gt; &lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;see&lt;/span&gt; &lt;span class="ident"&gt;all&lt;/span&gt; &lt;span class="ident"&gt;jobs&lt;/span&gt; &lt;span class="keyword"&gt;in&lt;/span&gt; &lt;span class="keyword"&gt;and&lt;/span&gt; &lt;span class="ident"&gt;around&lt;/span&gt; &lt;span class="ident"&gt;my&lt;/span&gt; &lt;span class="ident"&gt;zip&lt;/span&gt; &lt;span class="ident"&gt;code&lt;/span&gt;
  &lt;span class="comment"&gt;# scenario&lt;/span&gt;
  &lt;span class="constant"&gt;Scenario&lt;/span&gt;&lt;span class="punct"&gt;:&lt;/span&gt; &lt;span class="constant"&gt;Skills&lt;/span&gt; &lt;span class="constant"&gt;Search&lt;/span&gt;
    &lt;span class="constant"&gt;Given&lt;/span&gt; &lt;span class="ident"&gt;a&lt;/span&gt; &lt;span class="ident"&gt;search&lt;/span&gt; &lt;span class="ident"&gt;form&lt;/span&gt;
    &lt;span class="constant"&gt;When&lt;/span&gt; &lt;span class="constant"&gt;I&lt;/span&gt; &lt;span class="ident"&gt;enter&lt;/span&gt; &lt;span class="ident"&gt;skill&lt;/span&gt; &lt;span class="ident"&gt;keywords&lt;/span&gt;
    &lt;span class="constant"&gt;Then&lt;/span&gt; &lt;span class="constant"&gt;I&lt;/span&gt; &lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;see&lt;/span&gt; &lt;span class="ident"&gt;all&lt;/span&gt; &lt;span class="ident"&gt;job&lt;/span&gt; &lt;span class="ident"&gt;descriptions&lt;/span&gt; &lt;span class="ident"&gt;that&lt;/span&gt; &lt;span class="ident"&gt;mention&lt;/span&gt; &lt;span class="ident"&gt;those&lt;/span&gt; &lt;span class="ident"&gt;keywords&lt;/span&gt;
&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-704071610769103721?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/704071610769103721/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=704071610769103721' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/704071610769103721'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/704071610769103721'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2009/01/gedit-extensions-project.html' title='GEdit Extensions Project'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-4336889545077205436</id><published>2008-11-03T10:50:00.008-06:00</published><updated>2008-11-03T11:03:10.239-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='coffee'/><category scheme='http://www.blogger.com/atom/ns#' term='election'/><category scheme='http://www.blogger.com/atom/ns#' term='merb'/><category scheme='http://www.blogger.com/atom/ns#' term='vote'/><title type='text'>Free Coffee for Voting</title><content type='html'>As if anyone needs any more enticement to vote this election season, Starbucks has decided to reward people who cast their vote Tuesday November 4th, 2008 with a free Tall coffee.  I do not believe you have to vote for anyone in particular:)  Just go and vote.

&lt;object width="425" height="344"&gt;&lt;param name="movie" value="http://www.youtube.com/v/a2J8KJDsqqY&amp;hl=en&amp;fs=1&amp;rel=0&amp;color1=0x3a3a3a&amp;color2=0x999999"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/a2J8KJDsqqY&amp;hl=en&amp;fs=1&amp;rel=0&amp;color1=0x3a3a3a&amp;color2=0x999999" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;

While you are enjoying your free coffee at Starbucks, perhaps explore the latest Merb 1.0 release candidate.  To install simply do the following:
&lt;pre&gt;&lt;code&gt;$ sudo gem update --system
$ gem --version # make sure it is at least 1.2.0
$ sudo gem install merb&lt;/code&gt;&lt;/pre&gt;
Now to create a new Merb application do the following:
&lt;pre&gt;&lt;code&gt;$ merb-gen app your-app-name-here
$ cd your-app-name-here
# now start editing the files you need to edit for you app.
# I promise more tutorials will be coming once I have 
# free time&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-4336889545077205436?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/4336889545077205436/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=4336889545077205436' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4336889545077205436'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4336889545077205436'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/11/free-coffee-for-voting.html' title='Free Coffee for Voting'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-6483095180237787444</id><published>2008-09-27T21:52:00.020-05:00</published><updated>2008-09-28T11:03:24.220-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby on rails'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='books'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyonrails'/><category scheme='http://www.blogger.com/atom/ns#' term='capistrano'/><category scheme='http://www.blogger.com/atom/ns#' term='consulting'/><category scheme='http://www.blogger.com/atom/ns#' term='book'/><title type='text'>Book Shelf Clearing</title><content type='html'>In an effort to reorganize my office, I am clearing off space on my bookshelves and selling books (on Amazon marketplace) that I have already read.  Since they are all software development related I thought I would list the most relevant below in case any blog readers are looking for books on the subject for cheap:
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;[SOLD]&lt;/strong&gt; &lt;strike&gt;Deploying Rails Applications: A Step-by-Step Guide (Facets of Ruby)&lt;/strike&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt; (&lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FDeploying-Rails-Applications-Step-Step%2Fdp%2F0978739205&amp;tag=illinilistn09-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325" target="_blank"&gt;buy it new&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt; or &lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2Fgp%2Foffer-listing%2F0978739205%2F&amp;tag=illinilistn09-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325" target="_blank"&gt;used&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt;)&lt;br/&gt;Very good for the beginner or intermediate Capistrano recipe writer.  It also contains some good pointers at the end of the book on benchmarking.  Released relatively recently (within 3 months I think?)&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2Fgp%2Foffer-listing%2F0596529554%3Fseller%3DA2GY6CQ7Y4MII2&amp;tag=illinilistn09-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325" target="_blank"&gt;SOA in Practice: The Art of Distributed System Design (Theory in Practice)&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt; (Published Aug 2007)&lt;br/&gt;This book is more for the unRESTful SOA types than the RESTful folk like me.  The author obviously has some old-time experience, so if you are into old school SOA standards (WSDL, SOAP, UDDI) then this would provide a lot of good architect-level areas to consider, which are often ignored including: service lifecycle, versioning, security, service management and more.  One gripe I had with the book is in the versioning section.  Let's just say his preference for naming is definitely NOT my style:)  Otherwise this contains good reading material for the architect just getting into SOA in an enterprise setting.&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2Fgp%2Foffer-listing%2F0596527314%3Fseller%3DA2GY6CQ7Y4MII2&amp;tag=illinilistn09-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325" target="_blank"&gt;Rails Cookbook (Cookbooks (O'Reilly))&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt;&lt;br/&gt;Great for starting Rails projects with limited Ruby on Rails experience.  It covers Rails 1.2, but most recipes will only require minor changes.&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2Fgp%2Foffer-listing%2F0596527675%3Fseller%3DA2GY6CQ7Y4MII2&amp;tag=illinilistn09-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325" target="_blank"&gt;The Art of Agile Development&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt;&lt;br/&gt;Covers all aspects of agile development from planning to delivery.  If you aren't already an agile development ninja, this is in my top three agile books.&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2Fgp%2Foffer-listing%2F1593271204%3Fseller%3DA2GY6CQ7Y4MII2&amp;tag=illinilistn09-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325" target="_blank"&gt;Webbots, Spiders, and Screen Scrapers: A Guide to Developing Internet Agents with PHP/CURL&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt;&lt;br/&gt;Last year I had to work (for a short while) with PHP in the crawling/bot arena.  This book contained got me developing bots and crawlers in no time in PHP.&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2Fgp%2Foffer-listing%2F0596527446%3Fseller%3DA2GY6CQ7Y4MII2&amp;tag=illinilistn09-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325" target="_blank"&gt;Ajax on Rails&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt;&lt;br/&gt;This would be a decent book for Rails developers that aren't believers in Unobtrusive Javascript (UJS).  I have to purge this book from my bookcase now that I am a big UJS convert:)  I hope you understand.  I got this when it first came out, before I had seen the light.  Seriously though, if you don't care about UJS principles, this book covers good ground on using AJAX Rails helpers.  You should realize that the book was written pre-2.0 (Rails release that is).&lt;/li&gt;&lt;/ul&gt;
Also those wanting to branch out on their own as a consultant, the following books might be for you as I found both very helpful when first starting my software consulting practice in 2001:
&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2Fgp%2Foffer-listing%2F0471384550%3Fseller%3DA2GY6CQ7Y4MII2&amp;tag=illinilistn09-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325" target="_blank"&gt;Getting Started in Consulting&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt;&lt;br/&gt;If I could only recommend one of these two consulting books it would be this one hands down, since this is more about general consulting principles and practices and the second book might be slightly dated in the initial chapters, because both were written before the software outsourcing/offshoring mania.&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2Fgp%2Foffer-listing%2F0471348139%3Fseller%3DA2GY6CQ7Y4MII2&amp;tag=illinilistn09-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325" target="_blank"&gt;Getting Started in Computer Consulting&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt;&lt;/li&gt;&lt;/ul&gt;
PS I have been using &lt;a href="http://www.amazon.com/gp/subs/primeclub/signup/extmain.html?
ref=prime_assoc_bt&amp;tag=illinilistn09-20" rel="nofollow" target="_blank"&gt;Amazon Prime&lt;/a&gt; for two months now and love it.  You can &lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2Fgp%2Fsubs%2Fprimeclub%2Fsignup%2Fextmain.html%3Fref%3Dprime%5Fassoc%5Fbt&amp;tag=illinilistn09-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325" rel="nofollow" target="_blank"&gt;try Amazon Prime FREE for one month&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt; if you like too.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-6483095180237787444?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/6483095180237787444/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=6483095180237787444' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6483095180237787444'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6483095180237787444'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/09/book-shelf-clearing.html' title='Book Shelf Clearing'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-3163567399148266115</id><published>2008-09-22T10:20:00.041-05:00</published><updated>2008-09-22T12:58:37.970-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='entrepreneur'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby on rails'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><title type='text'>Tips to Enterpreneurs Hiring Ruby on Rails Consulting Firms</title><content type='html'>I know I am taking a break from my DataMapper/Merb series of posts today, but I have seen enough fakers out there in the Ruby on Rails world that I just have to get a few things off my chest:

Below is a list of things to avoid when hiring a Ruby on Rails consulting firm:
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Overexcited marketing speak.&lt;/strong&gt;  Drop any firm that has something like the following on their website: &lt;em&gt;"We believe Ruby on Rails is the most elegant, powerful programming language on the planet"&lt;/em&gt;.  These people might know a thing or two about configuring WordPress or Drupal or perhaps only Photoshop, but definitely no serious Object Oriented development of large scale Ruby server applications.  Basically anyone that calls "Ruby on Rails" as a whole a programming language has an inadequate skill set to build a non-trivial web application.  In addition anyone that knows more than three non-similar programming languages will know Ruby is not &lt;strong&gt;&lt;a href="http://www.opensourcery.com/services/custom-software"&gt;"the most powerful programming language on the planet"&lt;/a&gt;&lt;/strong&gt;.  Sorry I had to find a quote on the web to prove I wasn't exaggerating.  There are many more fakers on the web too, so look out!  Remember you aren't looking for people using the most powerful programming language, you want results.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agile this, agile that, agile everywhere.&lt;/strong&gt;  Beware of firms using the word &lt;strong&gt;agile&lt;/strong&gt; like there is no tomorrow.  Some use is fine, but ask them what they mean by agile.  And what benefits you, the client, will receive from an agile team.  Don't get me wrong if a web application is built in an iterative fashion with lots of client involvement and sign off, with lots of useful automated tests/specs that are run often then this is almost certainly a great thing for the project.  However, beware of the firms or freelancers that are using &lt;em&gt;agile&lt;/em&gt; simply as a buzzword.  (Note: some firms like mine use agile a lot in web page titles for SEO purposes, so not all firms that use agile a lot in titles are faking.  So make sure to ask firms what they mean by the word agile when they use it).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Why Rails?&lt;/strong&gt;  If any firm tells you that a web application written using Django, TurboGears, Merb or other web frameworks will never be as good as a Rails web application, then drop them off your consideration list immediately.  The same is true if the converse is said.  If they answer with something more measured and sensible like "our engineers have found developing applications in Rails to be more productive than X, Y and Z, which we previously used, and it provides us with the right amount of control/flexibility without us needing to worry about all configuration than previous frameworks" then at least they aren't misleading you just to get a sale.  Of course, you might be looking for other points in the answer (other attributes of a web framework that would make &lt;em&gt;business&lt;/em&gt; sense like active community, skillset, etc.).  Also in general you should look for measured answers to all your questions, not answers that lack insight and promise they can do EVERYTHING under the sun for you in 2 weeks time.  You will be disappointed if you hire that firm or freelancer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expert in everything, expert in nothing.&lt;/strong&gt;  Consulting firms and/or freelancers need to choose a specific set of technical competencies so they can be experts in those areas.  Beware of consulting firms or freelancers that claim to be experts in every framework, programming language and operating system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ubiquitous buzzwords.&lt;/strong&gt;  Beware of too many buzzwords.  Even if you know what a buzzword means, ask the person using too much jargon to clarify so you can evaluate whether they really know what they are saying.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Everything AJAX.&lt;/strong&gt;  I previously worked through a consulting firm on the west coast (I'll not name names).  The consulting manager who led project management functions on all the firms' projects was convinced that if we just put an AJAX form in here and there it would solve all our problems.  My experience with this firm wasn't overly positive.  I like the people on a personal level, but their work quality and focus tended to create hard to use web applications.  Beware of people who think AJAX is the answer to everything.  It has its place, but don't go overboard.  PS Make sure the people you hire write unobtrusive Javascript rather than embedded Rails Prototype helpers.  &lt;acronym title="Unobtrusive JavaScript"&gt;UJS&lt;/acronym&gt; has many benefits including graceful degradation (benefiting those that do not have Javascript enabled), avoiding browser incompatibilities and improved code organization (separation of concerns).  Users of your web application will appreciate the forethought and your developers can be more productive!:)  Win-win.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Novice.has_many :problems&lt;/strong&gt;  Look for previous Object Oriented modeling experience for the engineers on the project team.  Even though ActiveRecord (the default Ruby on Rails Object Relational Mapping framework) provides a higher level of abstraction for &lt;acronym title="Object Relational Mapping"&gt;ORM&lt;/acronym&gt; associations than other frameworks, don't be fooled into thinking anyone can model your business domain correctly without experience and/or know-how.  Many developers who are used to using Hibernate or TOPLink (as only two examples) before in Java or Smalltalk respectively will have likely done a lot of &lt;acronym title="Object Oriented"&gt;OO&lt;/acronym&gt; modeling.  Ask them about the most complex business domain they have modeled previously to be able to guage their skill level and experience in this area.  This point is most important in applications that have more complex business domains.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Why should I care about your data?  Look at my cool Photoshop spinning logo.&lt;/strong&gt;  Rails consulting firms and/or freelancers should have a good understanding of the importance of data modeling.  Even if they do not have specific data modeling expertise they should at least know that indexing appropriate columns and defining column size limits where appropriate can save not just storage space, but also shave time off queries (depending on the query and usage scenario).  That way when your application gets to the point where the database needs to be fine tuned, the data architect you hire will have an easier job than if your schema was designed by a PHP weekender that thinks the Drupal or WordPress database schema is finely tuned (sorry, it's horrendous and yes you should know that anyway).  This is a very important consideration if your application is supposed to hold a considerable amount of data and/or if you have strong reporting requirements.  As a side note, make sure to ask them if different database schemas are sometimes needed for querying large data sets (classical reporting requirements) versus &lt;acronym title="Create-Read-Update-Delete"&gt;CRUD&lt;/acronym&gt; usages.  The answer should be yes and you should ask them to explain this a little to see how much they really &lt;strong&gt;understand&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I can design, develop, deploy and manage perfectly.&lt;/strong&gt;  Usually firms and freelancers are much better in one or two areas than all equally.  Some are better at graphic design and got into development to earn $20/hr extra.  That's fine, but you should know what you are getting yourself into.  Ask what their strengths and weaknesses are.  For example, I am excellent at developing and very good at deploying and systems engineering.  I can use Photoshop and Illustrator (just like graphic designers can copy Rails code from forums and paste it into their editor), but ask me to create a visually stunning logo that encapsulates your company's image and I am lost (just like the graphic designers that can't write original code themselves).  Know what is most important for your application needs and hire the appropriate people.  Usually outsourcing graphic design to a design firm and hiring a flexible developer that can work with external teams well, gets you the best value for money.  Throw away the firms and people that don't have any weaknesses.  They are lying.&lt;/li&gt;&lt;/ul&gt;
One point I would like to make to round this piece off is that if you find a young, eager engineer with higher than average aptitude, he/she can really do a lot for your project.  However, you should expect them to make a few mistakes.  Nobody is perfect and without prior experience with large scale server applications it is virtually impossible for someone not to make mistakes.  If you can afford a few mistakes here and there, then this might be the way to go.  Give them an equity stake if capital is an issue for you, they are more likely to take a risk with you than a seasoned consultant with a mortgage to pay or spouse and children to feed.  However, never hire someone who claims to be a Ruby/Rails expert at a high rate if they simply copy and paste sample code from Ruby/Rails forums online (you would be surprised how many there are out there).  It's a terrible waste of your capital and frankly it takes work away from better firms and/or freelancers out there and you will end up with badly written software that runs poorly.

As a final note, hire a firm, freelancer or in-house engineer that has similar energy level and compatible personality to yours.  Or at a minimum hire a firm or freelancer that you can trust and has been recommended, but not just through popularity contests like those at WorkingWithRails.com (I know three freelancers who are excellent Ruby engineers with only have 1 recommendation on the site and I have worked with one person that has more than ten recommendations and consider them subpar to most Ruby/Rails developers I know).&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-3163567399148266115?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/3163567399148266115/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=3163567399148266115' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3163567399148266115'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3163567399148266115'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/09/tips-to-enterpreneurs-hiring-ruby-on.html' title='Tips to Enterpreneurs Hiring Ruby on Rails Consulting Firms'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-1104836026982398911</id><published>2008-09-04T10:13:00.015-05:00</published><updated>2008-09-11T19:22:45.427-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='orm'/><category scheme='http://www.blogger.com/atom/ns#' term='datamapper'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>DataMapper: Flexibility of mapping model attributes to table columns</title><content type='html'>I know an esteemed ex-colleague of mine (even if he is an Apple zombie now - yes, becoming a fan of Apple on Facebook deserves some heckling;)) has reservations about the apparent unDRY-ness of DataMapper with migrations.  I used to share his reservations before I wrote two applications using DataMapper.  Neither Merb application was particularly spectacular, but both had to work with legacy database schemas.  Oh the joys of crazy DBA naming conventions!

Let's look at a brief example of DBA naming madness (some names were changed, but the conventions used are the same):
&lt;pre&gt;
ordersTable:
  - uid_int: int (PK)
  - acc_id_int: int
  - amt_int: int
  - ref_code_string: varchar(64) natural key
  - desc_string: varchar(128)
  - entered_dt: datetime
  - changed_dt: datetime
&lt;/pre&gt;
In another table managed by a different group we had:
&lt;pre&gt;
Account:
  - ID: int (PK)
  - Balance: int
  - UserID: int (FK)
  - EnteredDateTime: datetime
  - ChangedDateTime: datetime
&lt;/pre&gt;

I kid you not.  Two different conventions, both relatively unreadable as Ruby attributes as they both violate popular coding conventions in some way.

In ActiveRecord you would be left doing non-trivial coding to hide the ugly column names in the model and map Ruby-friendly names to the appropriate column.

In DataMapper this is not so.  Lending from the design pattern's primary purpose - to explicitly map model attributes to database columns to decouple the two - we can write something like the following using DM:
&lt;pre&gt;
&lt;code&gt;
class Order
  include DataMapper::Resource
  set_table_name("ordersTable") #ridiculous name really, but what can you do if other older applications rely on this already?

  has(1, :account, :child_key =&gt; 'acc_id_int', :repository =&gt; repository(:accounts))
  property(:id, Integer, :serial =&gt; true, :field =&gt; 'uid')
  property(:amount, Integer, :field =&gt; 'amt_int')
  property(:reference, String, :field =&gt; 'ref_code_string', :key =&gt; true)
  property(:description, String, :field =&gt; 'desc_string')
  property(:created_at, DateTime, :field =&gt; 'entered_dt')
  property(:changed_dt, DateTime, :field =&gt; 'changed_dt')
end

class Account
  include DataMapper::Resource
  set_table_name('Account')

  property(:id, Integer, :serial =&gt; true, :field =&gt; 'ID')
  property(:balance, Integer, :field =&gt; 'Balance')
  property(:created_at, DateTime, :field =&gt; 'EnteredDateTime')
  property(:updated_at, DateTime, :field =&gt; 'ChangedDateTime')
end
&lt;/code&gt;
&lt;/pre&gt;
This way we can write readable Ruby code with the model.

Next time we can look at lazy loading: how to switch it on and off and when to use it for greater effect.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-1104836026982398911?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/1104836026982398911/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=1104836026982398911' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1104836026982398911'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1104836026982398911'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/09/datamapper-flexibility-of-mapping-model.html' title='DataMapper: Flexibility of mapping model attributes to table columns'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-2198880354566669059</id><published>2008-09-04T09:27:00.007-05:00</published><updated>2008-09-04T10:03:06.203-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='datamapper'/><category scheme='http://www.blogger.com/atom/ns#' term='merb'/><title type='text'>DataMapper does have migrations</title><content type='html'>I wanted to bust a myth out there that DataMapper does not have regular migrations, just the auto migrations.  At least in post 0.9 versions DataMapper has both.

In other blogs about the topic I found quotes like: "DataMapper migrations pull your database structure directly from the Ruby code for your models, so there’s no need to write separate migration files...".  This, in my opinion, gives the wrong impression to those that are worried about non-trivial schema migrations.  The author of that quote appears to have never moved one column into multiple or vice versa (a non-trivial schema change), among other schema changes where a DataMapper auto migration would not work in a production environment.  

In merb you can just create a new DM migration with the following:
&lt;code&gt;$ merb-gen migration name_of_your_migration&lt;/code&gt;
(Assuming you have set the &lt;code&gt;use_orm&lt;/code&gt; setting to &lt;code&gt;:datamapper&lt;/code&gt; in config/init.rb.)

A simple DataMapper migration might look something like:
&lt;pre&gt;
&lt;code&gt;
migration 1, :create_orders  do
  up do
    create_table(:orders) do
      column(:id, Integer, :serial =&gt; true)
      column(:amount, Integer)
      column(:completed, Boolean)
      column(:description, String, :size =&gt; 255)
      column(:created_at, DateTime)
      column(:updated_at, DateTime)
    end
  end

  down do
    drop_table(:orders)
  end
end
&lt;/code&gt;
&lt;/pre&gt;
If you have problems with &lt;code&gt;Boolean&lt;/code&gt; not being recognized, throw in the line:
&lt;code&gt;include DataMapper::Types&lt;/code&gt; 
to the top of the migration (yes, this needs some finessing obviously).

Hope that helps a few people that may have been mislead by some blog posts inadvertently.  Migrations are needed for many cases I come across where auto migrations just will not cut it without losing data in production (not a good thing if data matters in your application:)).

BTW A minor annoyance of mine in Merb is that for DM migrations you need to use this ugly Rake task: &lt;code&gt;dm:db:migrate&lt;/code&gt; to migrate to latest version.  To rollback migrations you need to use this ugly thing: &lt;code&gt;dm:db:migrate:down[version]&lt;/code&gt;.  Where those using AR in Merb just keep using &lt;code&gt;db:migrate&lt;/code&gt; and &lt;code&gt;db:rollback&lt;/code&gt;.  This is an area that needs cleaning up to promote ORM agnosticism the way Merb is supposed to do.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-2198880354566669059?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/2198880354566669059/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=2198880354566669059' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2198880354566669059'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2198880354566669059'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/09/datamapper-does-have-migrations.html' title='DataMapper does have migrations'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-1749384692716695241</id><published>2008-08-30T08:34:00.012-05:00</published><updated>2009-02-17T15:39:54.334-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='RSS'/><category scheme='http://www.blogger.com/atom/ns#' term='xml'/><category scheme='http://www.blogger.com/atom/ns#' term='Feeds'/><title type='text'>Next Generation Syndication and Publishing</title><content type='html'>For the last year or so, I have been focusing on implementing RSS, Atom publishing and related standards for a particular client in the podcasting industry.  Both RSS and (to a lesser extent due to its standard being finalized much later) Atom are being heavily used not just for blogs, podcasts, videos and multimedia content, but also used heavily for web 2.0 (or social media) activity "watching".

Most people should be familiar with FriendFeed by now (if not, where have you been?  really!).  This month (August 2008) FriendFeed came out with their Simple Update Protocol (SUP) which sits on top of RSS/Atom publishing to make it easier for content consumers (very self-serving for FriendFeed, but at least they are coming up with some kind of "solution" instead of just bitching about the status quo) to identify feeds that have changed before they make a timed request for the feed every X minutes regardless of whether the feed has changes or not.

In a nutshell, SUP requires a meta JSON &lt;strong&gt;feed&lt;/strong&gt; that notifies consumers of newly updated feeds to publish data.  The first time an RSS/Atom feed is subscribed to, the consuming agent (e.g. FriendFeed) will strip out the SUP-ID and the SUP feed URL from the &lt;tt&gt;xpath:channel/link&lt;/tt&gt; element and associate this information with the relevant user/account or whatever.  Then using the identifier for a feed returned by the meta JSON feed, the consumer knows the URL associated with that ID for feeds they need to be updated by.

While this might solve some problems, it really does feel very much like a monkey patch.  On the flip side I do understand to some extent why they took this path of monkey patching as opposed to coming up with something more revolutionary.  Revolutionaries tend to get killed and only the second wave settlers reap the benefits of their blood, sweat and tears.

However, I did want to mention SixApart's efforts to broadcast frequently updating consumable data in what should be a much more obvious way for former enterprise MOM developers like myself.

The Six Apart Update Stream broadcasts over HTTP new public updates/posts as they happen.  It's an interesting idea and much closer to the XMPP ideal that companies like Twitter have attempted.

Any other former enterprise architects merely see this new trend in web [defacto] standards in publishing and consuming data as just another message oriented middleware (MOM)?  And I am hopeful that finally the web has found a MOM in XMPP that can stand the test of public scrutiny.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-1749384692716695241?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/1749384692716695241/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=1749384692716695241' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1749384692716695241'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1749384692716695241'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/08/next-generating-syndication-and.html' title='Next Generation Syndication and Publishing'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-8307808780952707707</id><published>2008-08-21T17:20:00.005-05:00</published><updated>2008-08-21T19:38:28.755-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='paypal'/><category scheme='http://www.blogger.com/atom/ns#' term='scam'/><category scheme='http://www.blogger.com/atom/ns#' term='fraud'/><title type='text'>NEVER use or trust Paypal</title><content type='html'>For those that have been screwed by Paypal and need to find another venue to conduct business here are some alternative services:
&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://checkout.google.com"&gt;Google Checkout&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://www.propay.com"&gt;ProPay&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://www.bidpay.com"&gt;Authorize.NET&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://corporate.digitalriver.com/store/driv/en_US/ContentTheme/pbPage.Homepage/sectionName.home"&gt;Digital River&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://www.alertpay.com/?5%2biyjHxo%2ftMvjzwkUb4KZg%3d%3d"&gt;AlertPay&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://www.e-junkie.com/"&gt;E-Junkie&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

For *some* of the many issues surrounding Paypal/Ebay policies and rules you can check out the following articles:
&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://money.cnn.com/2008/02/08/smbusiness/paypal_funds_freeze.fsb/index.htm"&gt;EBay's PayPal funds freeze plan draws fire &lt;/a&gt; (CNN Money)&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://www.consumeraffairs.com/online/paypal_debit.html"&gt;Paypal Debit Card Disputes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://www.ripoffreport.com/reports/0/324/RipOff0324012.htm"&gt;Ripoff Report: Paypal&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

I have recently been royally screwed by Paypal.  However, I have been advised not to comment on specifics until I play all my cards:)&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-8307808780952707707?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/8307808780952707707/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=8307808780952707707' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8307808780952707707'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8307808780952707707'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/08/never-use-or-trust-paypal.html' title='NEVER use or trust Paypal'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-3875087204352500306</id><published>2008-07-22T18:51:00.005-05:00</published><updated>2008-07-22T18:58:14.395-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>LA Times Travel uses Twitter4R</title><content type='html'>&lt;a href="http://travel.latimes.com/"&gt;LA Times Travel&lt;/a&gt; is now using &lt;a href="http://twitter4r.rubyforge.org" title="Twitter Ruby library"&gt;Twitter4R&lt;span style="display:none"&gt;&amp;nbsp;(Ruby library for the Twitter API)&lt;/span&gt;&lt;/a&gt;.

You can see they sent messages via the Twitter4R for a few hours today (2008-07-22) on their &lt;a href="http://twitter.com/latimestravel"&gt;Twitter timeline&lt;/a&gt; before changing the Twitter4R configuration to not sure which library/application they are using (or just waiting to get Alex to approve their application source code).

Please let me know if you see Twitter4R sightings being used by other well known or cutting edge applications.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-3875087204352500306?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/3875087204352500306/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=3875087204352500306' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3875087204352500306'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3875087204352500306'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/07/la-times-travel-uses-twitter4r.html' title='LA Times Travel uses Twitter4R'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-8556085172217297724</id><published>2008-07-18T08:44:00.005-05:00</published><updated>2008-07-18T16:31:32.457-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pradipta'/><category scheme='http://www.blogger.com/atom/ns#' term='serendipity'/><title type='text'>Serendipity: An original Pradipta's Rolodex member</title><content type='html'>Last night I got the &lt;a href="http://blog.reverberate.org/2008/07/17/416-random-people-with-ror-on-their-resume-reply-all-reverse-flash-mob/"&gt;Pradipta's Rolodex message&lt;/a&gt;.

Rock on Pradipta!:)
&lt;a href="http://thepradipta416.com" title="Proud Member of the Pradipta 416"&gt;&lt;img src="http://thepradipta416.com/img/badge2.gif" alt="The Few, The Proud, The Pradipta 416"/&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-8556085172217297724?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/8556085172217297724/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=8556085172217297724' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8556085172217297724'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8556085172217297724'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/07/serendipity-original-pradiptas-rolodex.html' title='Serendipity: An original Pradipta&apos;s Rolodex member'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-1920917351345516137</id><published>2008-06-30T22:04:00.003-05:00</published><updated>2008-07-01T10:13:53.471-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='github'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R Development Releases</title><content type='html'>Over the last week or so I have been putting Twitter4R sources in GitHub and getting the on-the-fly GemSpec to work "safely" enough for GitHub servers.  Tonight (June 30) I decided I would announce Twitter4R development micro-releases available only via the GitHub gem server.  To setup your environment to install the latest development release of &lt;a href="http://twitter4r.rubyforge.org"&gt;Twitter4R&lt;span style="display:none"&gt;: the most complete Ruby client bindings for the Twitter.com REST API&lt;/span&gt;&lt;/a&gt;, please read &lt;a href="http://github.com/mbbx6spp/twitter4r/wikis/howto-install-github-development-releases"&gt;HowTo: Install GitHub Development Releases&lt;/a&gt;

Official documentation will still exist through the official &lt;a href="http://twitter4r.rubyforge.org/"&gt;Rubyforge project site of Twitter4R&lt;/a&gt;, but any documentation related to current development tasks, features, bugfixes will now be documented on the &lt;a href="http://github.com/mbbx6spp/twitter4r/wikis"&gt;Official Twitter4R GitHub Wiki&lt;/a&gt;.

If you want to keep an eye on the Git repository, feel free to &lt;a href="http://github.com/mbbx6spp/twitter4r/toggle_watch"&gt;watch the mbbx6spp/twitter4r&lt;/a&gt; GitHub repository.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-1920917351345516137?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/1920917351345516137/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=1920917351345516137' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1920917351345516137'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1920917351345516137'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/06/twitter4r-development-releases.html' title='Twitter4R Development Releases'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-6162266326860941502</id><published>2008-05-20T14:12:00.015-05:00</published><updated>2008-05-20T15:09:07.834-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='apple'/><title type='text'>Rails rocks, X sucks: How Provincial</title><content type='html'>I seem to be coming across more and more [Steve] Jobs-clones or simply no-thinking Apple fan boys lately.  I don't mind the Apple fans that thought themselves about why they love their stylish new Macbook Pro, I have just had enough of the no-thinking variety of Apple zealots.

This part of the Rails community appears to be a large population unfortunately.  Because these fan boys are actually incapable of thinking on their own, they adopt other people's arguments and say things like: "Rails rules, X sucks because so-and-so said this...".  Hmmmm.   Need I say more?

On the other side I recently came across a Django fan boy who tried to convince his non-technical manager that Rails "cannot scale period".  Hmmmm.  How original.  A blanket statement like this is not only misleading, it is actually in many ways technically incorrect.  This is why in middle school we learned how to qualify our arguments.  Why is it that supposed college graduates cannot do this in their 20s and 30s?  It is one thing to strip out technical jargon for non-technical managers to understand a situation, but it is quite another to over simplify and mislead them because you have your own tech-religion agenda.  Be passionate, but admit to yourself and others when passion is getting the better of you.

Why is acknowledging your chosen solution's weakness bad?  Doesn't it actually make the solution implementation stronger if you have thought about scenario-based weaknesses?  

How about we each try to &lt;b&gt;*qualify*&lt;/b&gt; our positions on technology solutions out there, rather than figuratively urinate all over others without any basic respect for them.  I encourage criticism, but criticize constructively.  Say why.  On the flip side, we should also accept constructive criticism too.  

The first step to constructive criticism, need not necessarily be candy coating your critique, but having the right intentions behind your critique.  For example, if your motivation for giving criticism is to start a flame war (sorry that is probably a very out of date term now), then you will probably get what you want.  However, if you really want to help a project get better because you care, then that is the first step.  

In addition, the way you word and tone statements (as Twitter's Alex Payne should have learned all too well by now) has a big impact.  For example, Alex Payne's statements about Rails and Ruby cost me a contract at a former Rails shop that had a conniving PHP zealot waiting in the wings to jump on anything and take issue with Rails to upper management.  Lucky for him, it worked and he had 3 to 4 months of leading a team to miserable failure using Drupal as a "platform".  Not necessarily a reflection of PHP (or Drupal for CMS applications since it was far from a CMS application), but a reflection of this calculating pseudo-coder's inability to lead a project.  A "technical" manager who thought the kernel version on his Fiesty Ubuntu laptop was 7.0.4.  Enough said!:)

Overall, I hope we can start to have intelligent conversations about software stacks rather than "yours sucks ass".  Because the latter in the end favors nobody.

&lt;i&gt;PS Merb rulez and Rails suckz!:)  If you want to know why read my teaser entry titled &lt;a href="http://snakesgemscoffee.blogspot.com/2008/05/why-merb-is-delicious.html"&gt;Why Merb is delicious&lt;/a&gt;, though I plan on writing more soon.&lt;/i&gt;

&lt;b&gt;PPS Merb wouldn't be where it is today without Rails.&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-6162266326860941502?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/6162266326860941502/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=6162266326860941502' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6162266326860941502'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6162266326860941502'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/05/rails-rocks-x-sucks-how-provincial.html' title='Rails rocks, X sucks: How Provincial'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-2355125211359606383</id><published>2008-05-06T08:49:00.010-05:00</published><updated>2008-05-06T09:15:31.222-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='merb'/><title type='text'>Why Merb is delicious</title><content type='html'>Below are the reasons why &lt;a href="http://merbivore.com"&gt;Merb&lt;/a&gt; is more delicious than other MVC web frameworks I have worked with (including Rails):
&lt;ul&gt;&lt;li&gt;&lt;b&gt;On a diet...&lt;/b&gt;It is lighter weight than most, but still has enough oomph to implement common features with minimal code&lt;/li&gt;&lt;li&gt;&lt;b&gt;Religion-free...&lt;/b&gt;It is ORM, JavaScript/AJAX and testing/speccing library agnostic.&lt;/li&gt;&lt;li&gt;&lt;b&gt;The sum of it's parts...&lt;/b&gt;OK that was a lame segway, but &lt;b&gt;parts&lt;/b&gt; is a mightily useful feature where a plugin is just total overkill.  Just because Java calls something a "component" does not make it the general definition of a component in concept.  And now look at the Rails plugin debacle today.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Precious gems...&lt;/b&gt;Plugins are created, distributed and installed in the form of beautiful RubyGems as opposed to the Rails plugin catastrophe with Gigabytes of duplicate disk space and lame version/revision control&lt;/li&gt;&lt;li&gt;&lt;b&gt;It's exceptional...&lt;/b&gt;Merb actually thought about how to do exception handling in controllers before Rails came up with something and also came up with IMHO a better solution.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Speedy Gonzalez...&lt;/b&gt;Compared to especially Rails, Merb performs &lt;a href="http://www.webficient.com/2007/08/testing-various-configurations-of-rails.html"&gt;very nicely indeed&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Loose threads...&lt;/b&gt;Merb is thread-safe unlike Rails, so that means one process can handle multiple concurrent file uploads, where Rails cannot right now.&lt;/li&gt;&lt;/ul&gt;

All together I think most people will find Merb to be a much better thought out (conceptually) MVC framework than Rails.  Over the next few weeks I'll be talking a lot about Merb and it's delicious APIs, Plugins, etc. as it nears it's 1.0 release.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-2355125211359606383?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/2355125211359606383/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=2355125211359606383' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2355125211359606383'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2355125211359606383'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/05/why-merb-is-delicious.html' title='Why Merb is delicious'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-2880256725987487057</id><published>2008-04-04T10:23:00.001-05:00</published><updated>2008-04-04T11:54:50.304-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='chicago'/><category scheme='http://www.blogger.com/atom/ns#' term='jug'/><category scheme='http://www.blogger.com/atom/ns#' term='cjug'/><title type='text'>Comedy of Errors</title><content type='html'>Does anyone else find it comical that the &lt;a href="http://cjug.org/uger/directory/list"&gt;Chicago Java Users Group&lt;/a&gt; website is written in Rails?  And apparently not very well if it is not only sending a HTTP 500 error, but still using the default Rails generated 500.html page.  Poor all around.

An IM buddy mentioned it might be JRuby, but I do think it is still very ironic considering the hostility from the Java world regarding Rails in general (whether Ruby on Rails or JRuby on Rails).

I just had to share!:)

&lt;b&gt;Update:&lt;/b&gt; Someone fixed it shortly after I reported it to CJUG.&lt;br/&gt;
&lt;b&gt;Update:&lt;/b&gt; Actually it was only temporarily fixed, it is back to the default 500 Rails error page.&lt;br/&gt;
&lt;b&gt;Update:&lt;/b&gt; In addition they have exposed their SVN information.  See the &lt;a href="http://cjug.org/uger/.svn/entries"&gt;public entries&lt;/a&gt; and from that information you can find their &lt;a href="http://svn.oaktop.com/Uger/trunk/"&gt;non-password protected source code and check it out&lt;/a&gt; if you like.

If anyone from CJUG is reading, you can fix this by reading my blog entry from two months back called &lt;a href="http://snakesgemscoffee.blogspot.com/2008/02/preventing-information-leaks-part-3.html"&gt;Preventing Information Leaks, Part 3&lt;/a&gt;.  Although it might be useful to read &lt;a href="http://snakesgemscoffee.blogspot.com/2008/02/preventing-information-leaks-part-1.html"&gt;part 1&lt;/a&gt; and &lt;a href="http://snakesgemscoffee.blogspot.com/2008/02/preventing-information-leaks-part-2.html"&gt;part 2&lt;/a&gt; as well.  Cheers!

PS I also sent an email to &lt;code&gt;info at cjug.org&lt;/code&gt; and received an email delivery error.&lt;br/&gt;
&lt;b&gt;Update:&lt;/b&gt; I may have read too much into the SVN repository information exposure in this specific case since this is an open source Rails application project (I only just realized), thus the non-password protected repository, but I still stand by the rest!:)  It is also not good practice to expose such information from the outset of deploying an web application.&lt;br/&gt;
&lt;b&gt;Update (1.5 hours after trying to notify JUG and posting the blog post):&lt;/b&gt; The site has been fixed now for over 5 minutes, so good job fixing it CJUG!:)&lt;br/&gt;
&lt;b&gt;Update (8 minutes later):&lt;/b&gt; Ooooops!  It is back to the default Rails 500 error page.  Perhaps it is 2 out of three bad FastCGI processes that need to be killed on the server?  HTH someone at CJUG, but since I can't look at the system I can't say for sure.&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-2880256725987487057?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/2880256725987487057/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=2880256725987487057' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2880256725987487057'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2880256725987487057'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/04/comedy-of-errors.html' title='Comedy of Errors'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-125069007997943352</id><published>2008-04-01T10:34:00.008-05:00</published><updated>2009-07-31T09:56:11.464-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='php'/><category scheme='http://www.blogger.com/atom/ns#' term='coding'/><title type='text'>Coding with Explicit Intentions</title><content type='html'>Recently I have had the [mis]fortune to be working with PHP for integration purposes for a client.  A partner of my client's (in the advertising space) sent us some PHP code to add to our servers for resolving the content type of a file based on its extension.  The line of code in the PHP script they sent over that was supposed to determine the file extension was:
&lt;pre&gt;&lt;code&gt;  $ext = substr($file,-4);&lt;/code&gt;&lt;/pre&gt;
As you can see they are making a pretty big assumption - that the extension is exactly three letters long.  The media files that this script *may* support in the future are: Real Media (.rm), MPEG (.mpeg), Quicktime (.qt), etc.  These extensions are not exactly three letters long.  Why would someone want to write code that is quite likely to fail and also not communicate more explicitly their intentions?  

This partner already doesn't support PHP &lt; 4.0.3, so why not substitute the line above with:
&lt;pre&gt;&lt;code&gt;  $ext = ".".pathinfo($file, PATHINFO_EXTENSION);&lt;/code&gt;&lt;/pre&gt;
It is standard, will never fail (unless there is a defect in the PHP version you are using for the standard function pathinfo, but what can you do about that?) and communicates the explicit intent of the original author, thus improving code maintainability.

Anyway, just a pet peeve.  This is not by any means the only example I see on a day in day out basis, this just happened to be a great example for me to demonstrate my point.  While this example shows just a minor one-line example, if developers introduce even only 2-3 of these un-explicit intentioned lines of code a day that do not always do what you might want it to do, then the codebase quickly becomes a mine field.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-125069007997943352?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/125069007997943352/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=125069007997943352' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/125069007997943352'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/125069007997943352'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/04/coding-with-explicit-intentions.html' title='Coding with Explicit Intentions'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-8367580530705478249</id><published>2008-03-30T21:49:00.003-05:00</published><updated>2008-03-30T22:01:15.112-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='metafusion'/><title type='text'>Waking Up From Hibernation</title><content type='html'>Just a quick post to say that I will be waking up from OSS and blog hibernation at the beginning of May.  I have a stack of bug fixes to apply to Twitter4R for a pretty large 0.3.1 release (in terms of bug fixes and supporting Rails 2.x compatibility).  The next two major features on my Twitter4R v0.4.0 release radar are: (a) supporting Ruby 1.9 and (b) adding the newish "track" feature in some capacity (probably via XMPP instead of the REST API on Twitter.com).

I also have a couple of minor features to add to the &lt;a href="http://rubyforge.org/projects/metafusion" title="Ruby Metafusion-Crypography gem"&gt;&lt;code&gt;metafusion-crypto&lt;/code&gt;&lt;/a&gt; gem and finally release the first version of &lt;code&gt;metafusion-rails&lt;/code&gt;.  Then I will probably abandon OSS work explicit to Rails and write numerous Merb plugins/extensions.

The catch up work will likely take 2-3 weeks into May and then hopefully my new venture workload will be significantly smaller to sustain OSS development over the summer months.

Thanks for all your patience!&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-8367580530705478249?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/8367580530705478249/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=8367580530705478249' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8367580530705478249'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8367580530705478249'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/03/waking-up-from-hibernation.html' title='Waking Up From Hibernation'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-6254672166042759920</id><published>2008-02-12T10:44:00.000-06:00</published><updated>2008-02-12T16:49:35.064-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='information leaks'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='security'/><title type='text'>Preventing Information Leaks, Part 3</title><content type='html'>In the third part of the &lt;a href="http://snakesgemscoffee.blogspot.com/2008/02/preventing-information-leaks-part-1.html"&gt;how to prevent information leaks&lt;/a&gt; blog post series, we will look for unguarded "hidden" files where we can garnish quite a bit of information.  

Make sure you check out &lt;a href="http://snakesgemscoffee.blogspot.com/2008/02/preventing-information-leaks-part-1.html"&gt;part 1&lt;/a&gt; and &lt;a href="http://snakesgemscoffee.blogspot.com/2008/02/preventing-information-leaks-part-2.html"&gt;part 2 on how to prevent information leaks in web applications&lt;/a&gt; before continuing.

&lt;h4&gt;Seek Unguarded Hidden Files&lt;/h4&gt;
For web applications that use deployment tools that pull code from code repositories, you can find out quite a bit of information about the code repository (e.g. host, path, usernames, file listings), by finding unguarded hidden files.  One obvious example especially in the SVN saturated Rails world is the path &lt;code&gt;/.svn/entries&lt;/code&gt; and similar URLs.

You will want to make sure the following URIs are not accessible on your public sites if you use Subversion (SVN):
&lt;ul&gt;&lt;li&gt;&lt;code&gt;/.svn/entries&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;/javascripts/.svn/entries&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;/stylesheets/.svn/entries&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;/images/.svn/entries&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;

If you are using a superior SCM for your project like Git you ought to be looking for the following:
&lt;ul&gt;&lt;li&gt;&lt;code&gt;/.git/config&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;&lt;strike&gt;/javascripts/.git/config&lt;/strike&gt;&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;&lt;strike&gt;/stylesheets/.git/config&lt;/strike&gt;&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;&lt;strike&gt;/images/.git/config&lt;/strike&gt;&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;

If your project is still in the stone-age using CVS, then check the following:
&lt;ul&gt;&lt;li&gt;&lt;code&gt;/CVS/Entries&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;/javascripts/CVS/Entries&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;/stylesheets/CVS/Entries&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;/images/CVS/Entries&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;

Adapt these patterns for your application framework default structures.  The above is for Rails or Rails-based frameworks (e.g. Merb).  

When I first tried this last week on the Rails top 10 sites, three out of ten of these sites exposed their SVN information (and last week I had only checked SVN, no Git or CVS URIs).  Since last week at least one of those sites have fixed the problem!

What information is exposed in these files?  Quite a lot of information.  Do you want potential hackers knowing:
&lt;ul&gt;&lt;/li&gt;code repository URL (host, port, protocol, path)&lt;/li&gt;&lt;li&gt;usernames of committers&lt;/li&gt;&lt;li&gt;code repository listings of files in a directory&lt;/li&gt;&lt;/ul&gt;
All these things are not things you really want the potential hacker knowing.  On my travels investigating the top 100 Rails sites I found one top 20 site that exposed enough information from their SVN entries file that I found a prototyping directory that included a "specs.rtf" document, which I was able to download.  The document was not very uninteresting (at least not to me), but if they had written more in-depth specifications in the document, it may have served as a nice guide for a potential hacker to garnish enough information about their setup to take over!

&lt;b&gt;Moral of this story?&lt;/b&gt; Guard hidden files.  I recommend "forbidding" access to all troublesome path regex patterns.  

For example, one of the LigHTTPd servers I administer has the following at the top of the configuration file:
&lt;pre&gt;&lt;code&gt;
$HTTP["url"] =~ ".*/\..*" {
  url.access-deny = ("")
}
&lt;/code&gt;&lt;/pre&gt;

For Apache httpd servers I use something like the following in the &lt;code&gt;httpd.conf&lt;/code&gt;:
&lt;pre&gt;&lt;code&gt;
&amp;lt;DirectoryMatch "^.*\..*"&amp;gt;
  ErrorDocument 403 /403.html
  Order allow,deny
  Deny from all
  Satisfy All
&amp;lt;/DirectoryMatch&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

Nginx configuration might look something like:
&lt;pre&gt;&lt;code&gt;
location ^~ /\..* {
  deny    all;
}
&lt;/code&gt;&lt;/pre&gt;

This guards against URLs like: &lt;code&gt;/.svn/entries&lt;/code&gt;, &lt;code&gt;.git/config&lt;/code&gt; as well as &lt;code&gt;.htpasswd&lt;/code&gt;, etc.  This means that only some script-kiddie hackers will mistakenly think you are using one SCM system instead of another, but they will never be able to know for sure which SCM you use or any information about your code repository.  

Forbidding access to all paths containing "/." somewhere in the URL is generally a good idea (IMHO) as sometimes people change web servers and leave "secret" files in the directory structure, but the new server doesn't know that, for example, &lt;code&gt;.htaccess&lt;/code&gt; or &lt;code&gt;.htpasswd&lt;/code&gt; is "special", so it will just serve it without thinking.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-6254672166042759920?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/6254672166042759920/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=6254672166042759920' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6254672166042759920'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6254672166042759920'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/02/preventing-information-leaks-part-3.html' title='Preventing Information Leaks, Part 3'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-8032118076093709950</id><published>2008-02-11T13:04:00.000-06:00</published><updated>2008-02-11T13:12:38.120-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='information leaks'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='security'/><title type='text'>Preventing Information Leaks, Part 2</title><content type='html'>Continuing the series that looks at &lt;a href="http://snakesgemscoffee.blogspot.com/2008/02/preventing-information-leaks-part-1.html"&gt;how to prevent information leakage&lt;/a&gt;, today we look at the information leakage from web server HTTP headers.

&lt;h4&gt;HTTP Header Leakage&lt;/h4&gt;
Let's look at the information potential hackers can get from HTTP headers from just a &lt;code&gt;GET /&lt;/code&gt; HTTP request.  Let us look at http://www.cnn.com:
&lt;pre&gt;&lt;code&gt;$ curl -I http://www.cnn.com
HTTP/1.1 200 OK
Date: Thu, 07 Feb 2008 15:22:32 GMT
Server: Apache
Accept-Ranges: bytes
Cache-Control: max-age=60, private
Expires: Thu, 07 Feb 2008 15:23:23 GMT
Vary: Accept-Encoding,User-Agent
Content-Type: text/html
X-Pad: avoid browser bug
Content-Length: 90458
&lt;/code&gt;&lt;/pre&gt;
This example is pretty decent.  What you want to look for is the &lt;code&gt;Server&lt;/code&gt; HTTP header value.  In this case it is just "Apache".  Now it does identify the web server used, but it doesn't pinpoint the version being used.  Now I am going to try a popular Rails website:
&lt;pre&gt;&lt;code&gt;
HTTP/1.1 200 OK
Date: Thu, 07 Feb 2008 15:25:34 GMT
Server: Apache/2.2.2 (FreeBSD) mod_ssl/2.2.2 OpenSSL/0.9.8b DAV/2 PHP/5.1.4 SVN/1.3.2 mod_vd/2.0 mod_fastcgi/2.4.2 proxy_html/2.5
Last-Modified: Thu, 07 Feb 2008 14:58:58 GMT
ETag: "4da437-36ac-b69b1080"
Accept-Ranges: bytes
Content-Length: 13996
Vary: Accept-Encoding
Content-Type: text/html
&lt;/code&gt;&lt;/pre&gt;
In this case we can see what OS Apache is running on and the version of Apache.  Not only this, but we see all the enabled modules in Apache and their respective versions.  IMHO this is too much information especially considering this site supposedly (at least as far as I know) host the site on a fully controlled environment (either dedicated or VPS with root access).  Applications on shared hosts cannot help this much without assistance from the shared hosting company.  I have a couple of very small sites that get little traffic on a shared host, so I appreciate this obstacle.

&lt;b&gt;Moral of this story:&lt;/b&gt; If you have full control over your environment you should always either change the "Server" HTTP header to something generic (e.g. "Apache" as in the CNN example) or disable it from being returned to the client.  This setting is very easy in Apache, LigHTTPd and NGinx.  I assume this wouldn't be difficult in LiteSpeed either, but I do not have configuration experience with LiteSpeed.

Apache configuration:&lt;pre&gt;&lt;code&gt;Header unset Server&lt;/code&gt;&lt;/pre&gt;

LigHTTPd configuration:&lt;pre&gt;&lt;code&gt;server.tag = ""&lt;/code&gt;&lt;/pre&gt;

NGiNX configuration:&lt;pre&gt;&lt;code&gt;server_tokens off;&lt;/code&gt;&lt;/pre&gt;

Also make sure there aren't any other headers that give away too much information.  Especially look at the &lt;code&gt;X-&lt;/code&gt; HTTP headers.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-8032118076093709950?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/8032118076093709950/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=8032118076093709950' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8032118076093709950'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8032118076093709950'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/02/preventing-information-leaks-part-2.html' title='Preventing Information Leaks, Part 2'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-7664632921932248120</id><published>2008-02-09T12:17:00.000-06:00</published><updated>2008-02-09T12:44:30.956-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='datamapper'/><category scheme='http://www.blogger.com/atom/ns#' term='activerecord'/><title type='text'>DataMapper vs. ActiveRecord</title><content type='html'>Today I came across a thread on ruby-talk where I responded to the age old question of which Ruby ORM (specifically DM vs. AR).

I responded based on my production use of AR, development use of DM and my understanding of Fowler's PoEA of the same names that I have applied in Java and Python countless times.

As some readers of this blog might find this discussion interesting, I am linking to my response to the &lt;a href="http://www.ruby-forum.com/topic/141181#630493"&gt;DataMapper vs. ActiveRecord discussion here&lt;/a&gt;.

The executive summary (for the infinitely lazy) is:&lt;blockquote&gt;It appears that if DataMapper (the Ruby library) is able to sufficiently hide enough database logic in more complex business logic scenarios, then the DM library might be more beneficial to use when using a legacy database schema where you are not able to create primarily isomorphic relationships between class attributes and table columns.  Whereas AR would be a slam dunk and simpler to use in isomorphic schema scenarios where you have control over the database schema.&lt;/blockquote&gt;

I will be returning to the &lt;a href="http://snakesgemscoffee.blogspot.com/2008/02/preventing-information-leaks-part-1.html"&gt;Preventing Information Leaks&lt;/a&gt; series on Monday/Tuesday (it is already written, I just need to publish).&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-7664632921932248120?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/7664632921932248120/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=7664632921932248120' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7664632921932248120'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7664632921932248120'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/02/datamapper-vs-activerecord.html' title='DataMapper vs. ActiveRecord'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-2670866589089477559</id><published>2008-02-07T10:04:00.001-06:00</published><updated>2008-02-07T10:39:05.086-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='information leaks'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='security'/><title type='text'>Preventing Information Leaks, Part 1</title><content type='html'>Before I start I want to mention that the techniques suggested in this blog post are for readers to use to secure their own web applications.

This does not mean that you can take over the server with these techniques without further hacking, but these steps provide a lot of information about the environment the web application is deployed to or developed using, unless system administrators/engineers or software engineers prevent this information leakage.

Some of these techniques exploit settings that are usually found in Rails when default settings are used, but web applications written in different frameworks may also use these settings, which leak information.

This all just comes down to thinking through the security scenarios.  Just like developers need to consider the usage of their applications or APIs or frameworks from the user perspective, those tasked with securing up their applications need to consider how potential hackers might try to access key information to help them hack your systems.

&lt;h4&gt;Identifying the framework or "stack"&lt;/h4&gt;
Let us check if your application smells too much like you are using a particular framework or another.  This may or may not be terrible, BUT if you can tell which version of the framework you are using from publicly accessible information on your site, then you are leaking too much information.  In fact, some firms/developers advertise very publicly which frameworks or stacks they use, but they usually try not to talk too much about the exact versions they use.

For example, if an application responds with a 200 OK HTTP status for the majority of the following URLs, it is VERY likely it is a Rails application:
&lt;ul&gt;&lt;li&gt;&lt;code&gt;/javascripts/application.js&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;/404.html&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;/500.html&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;/422.html&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;
In the case of the last URL, we can identify that the application is written using Rails 2.0.  We can also look at the 404 to see if it looks like the default looking 404 file for different versions of Rails.  If &lt;code&gt;/422.html&lt;/code&gt; doesn't exist we might still be able to tell the difference between Rails 1.1 and 1.2 applications by what &lt;code&gt;/404.html&lt;/code&gt; looks like.

&lt;b&gt;Moral of this story:&lt;/b&gt; At a minimum always change the default error pages in your Rails application to fit your design.  Also consider changing the URLs of the error pages too.  Not only does the redesign prevent potential hackers from identifying which version of Rails you might be using it also looks more professional and makes the user experience in case of an error a little more acceptable!
If you are not using Rails, think about what files are created by default by your framework and change things around a little to prevent information leakage.

I will continue this series in subsequent parts.  Stay tuned!&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-2670866589089477559?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/2670866589089477559/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=2670866589089477559' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2670866589089477559'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2670866589089477559'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/02/preventing-information-leaks-part-1.html' title='Preventing Information Leaks, Part 1'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-7235516009279821948</id><published>2008-01-31T09:23:00.000-06:00</published><updated>2008-01-31T11:01:12.074-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='stupid'/><category scheme='http://www.blogger.com/atom/ns#' term='sin'/><category scheme='http://www.blogger.com/atom/ns#' term='language'/><title type='text'>Stupid Easy: 7 Deadly [Rails] Sins, Part 3</title><content type='html'>This is the third and final part in the &lt;a href="http://snakesgemscoffee.blogspot.com/2008/01/stupid-easy-7-deadly-rails-sins-part-1.html"&gt;Stupid Easy: 7 Deadly [Rails] Sins&lt;/a&gt; series.  See &lt;a href="http://snakesgemscoffee.blogspot.com/2008/01/stupid-easy-7-deadly-rails-sins-part-1.html"&gt;part 1&lt;/a&gt; and &lt;a href="http://snakesgemscoffee.blogspot.com/2008/01/stupid-easy-7-deadly-rails-sins-part-2.html"&gt;part 2&lt;/a&gt; if you haven't already.

I have left the most heinous sin to the end.  If you are a serious sinner in this aspect, there is little hope for you unless you take steps now to rectify your behavior in this aspect.

&lt;h4&gt;Stupid Easy Ruby&lt;/h4&gt;
&lt;blockquote&gt;When in Rome, do as the Romans do&lt;/blockquote&gt;
That's right.  This is the sin of not knowing how to really code in Ruby.  Sure some coders that move to Ruby can write code that does what they want it to do, but often at the severe expense of compromising project code maintainability (e.g. making things very unDRY, etc.) or even introducing significant performance issues by not understanding basics about the Ruby language.

Instead I see people using Java, Perl, PHP, C# or even Python idioms in Ruby instead of understanding the essence of the language of Ruby, which is distinct from each of these languages in different ways.

The same is true for any language/environment you work within.  I know former PHP people that moved to Java and do not have the first clue about basic Java idioms that work well.  This isn't just to pick on PHP heads as I imagine this occurs from any language migration path.  I have simply seen this sin committed most by former PHP coders moving toward Java, Ruby and Python.  However, I will say I see a LOT of former Java (massive static language proponents - Joshua Bloch you are a nonobjective snob) developers that do not understand or appreciate the non-static design mentality and they create large class hierarchies and don't understand what a Mixin is (or they pretend to be "cool" and talk alot of about these idioms, but don't really utilize them in the right way themselves).  The Ruby mindset is still different from it's more similar looking cousins: Python and Perl, but moving between Python, Perl and Ruby (in any direction) feels more intuitive (at least in my mind).

Now I should stress that I can no longer code in Java without pulling my hair out because the thought of creating 5 interfaces and a factory for every three class implementations drives me temporarily insane.  The SPI design principle allows Java to be fairly flexible (especially for a statically typed language), but at the expense of my personal sanity now that I prefer to think the non-static way (yes, I still appreciate Python's strongly-typed ways).  This is why I prefer to use JRuby, Jython or Groovy scripting in Java environments.

There is no magic bullet to stop sinning in this regard if you aren't willing to take a journey to learn and understand Ruby idioms (not just basic *knowledge*).  There are no shortcuts, it simply depends on how easily your brain can shift in gears.  This doesn't mean people who can't shift gears are "bad" developers, but not well suited to migrating to significantly different environments very often.

Just remember the rules in Ruby aren't the same.  Think in terms of sending messages to objects that may or may not respond to the message you send, instead of expected interfaces and you are half way there if you are coming from Java, C++ and similar languages.  Think of Java as a big government structure with a complicated tax code and Ruby as minimal government with a simplified tax code &lt;b&gt;without&lt;/b&gt; loopholes accessible only to the rich!  That means in the Ruby world wild things such as adult services can legally be procured between consenting adults, so bend your brain to think that way when working in Ruby...almost anything is possible especially in the realm of metaprogramming that is not possible in the Java world.
&lt;blockquote&gt;That government is best which governs least.&lt;br/&gt;--Thomas Paine&lt;/blockquote&gt;I personally happen to agree with Thomas Paine's statement (at least in the context of programming), but there are various programming philosophies that different languages cater for, so it comes down to your own preference.  Pick the ones you can live with at the time and think in terms of and you will be happier for it.

Obviously in a programming context some people (e.g. Joshua Bloch, who is still a static language snob) will oppose legal procurement of "adult services".  They have a moral objection to these activities and thus do not appreciate the beauty of dynamic design.  If you have such moral reservations in programming, do everyone a favor and stay in the Java world or at least static languages!  This is the beauty of different opinions.  Nobody is right in absolute terms, but you need to make the right choice for yourself.  It is the zealots who only accept "absolute correctness" that we should be weary of (on any side of the argument, but including Joshua Bloch).

My general philosophy when I had the Java Way ingrained in my brain was that as an API designer I had to protect stupid developers from their own stupidity.  Now my philosophy in the Ruby world is that I write APIs for smart developers and if you aren't [Ruby] smart, use at your own risk.  Of course, I oppose complication for the sake of appearing to be "smart".  But I try to write APIs that allow more advanced Ruby developers to take advantage of language features such as blocks, metaprogramming, etc. as opposed to providing an over-simplified, dumbed-down API that ends up looking ugly to the wiser Ruby developer due to being less DRY than might be possible without restriction.

&lt;h4&gt;Ruby Language Pointers&lt;/h4&gt;
If you haven't acquainted yourself with &lt;a href="http://whytheluckystiff.net/ruby/pickaxe/"&gt;the pickaxe book&lt;/a&gt; you should pay special attention to the following sections: &lt;a href="http://whytheluckystiff.net/ruby/pickaxe/html/tut_containers.html#S2"&gt;Blocks&lt;/a&gt;, &lt;a href="http://whytheluckystiff.net/ruby/pickaxe/html/tut_modules.html#S2"&gt;Mixins&lt;/a&gt;, &lt;a href="http://whytheluckystiff.net/ruby/pickaxe/html/ospace.html#S1"&gt;Inspecting Objects&lt;/a&gt;, &lt;a href="http://whytheluckystiff.net/ruby/pickaxe/html/tut_classes.html#S1"&gt;Inheritance &amp;amp; Messages&lt;/a&gt;, &lt;a href="http://whytheluckystiff.net/ruby/pickaxe/html/tut_classes.html#UA"&gt;Inheritance &amp;amp; Mixins&lt;/a&gt;, &lt;a href="http://whytheluckystiff.net/ruby/pickaxe/html/classes.html#S1"&gt;Classes &amp;amp; Objects Interacting&lt;/a&gt;, &lt;a href="http://whytheluckystiff.net/ruby/pickaxe/html/language.html"&gt;The Ruby Language&lt;/a&gt;.

Then I would look at the source code of the ActiveSupport open source project (part of Rails) for techniques that are very useful.  Some of these techniques I have discussed in previous blog posts: &lt;a href="http://snakesgemscoffee.blogspot.com/2006/08/rubyisms-reopening-classes.html"&gt;Ruby Idiom: Reopening Classes&lt;/a&gt;, &lt;a href="http://snakesgemscoffee.blogspot.com/2006/07/rubyisms-forwardables.html"&gt;Ruby Idiom: forwardables&lt;/a&gt;, &lt;a href="http://snakesgemscoffee.blogspot.com/2007/04/injecting-understanding.html"&gt;Ruby Idiom: Inject-ing Understanding&lt;/a&gt;, &lt;a href="http://snakesgemscoffee.blogspot.com/2007/05/ruby-equality.html"&gt;Ruby Equality&lt;/a&gt;, &lt;a href="http://snakesgemscoffee.blogspot.com/2007/06/higher-order-messaging.html"&gt;Ruby Higher Order Messaging&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-7235516009279821948?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/7235516009279821948/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=7235516009279821948' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7235516009279821948'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7235516009279821948'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/01/stupid-easy-7-deadly-rails-sins-part-3.html' title='Stupid Easy: 7 Deadly [Rails] Sins, Part 3'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-4840965527902167268</id><published>2008-01-22T09:43:00.000-06:00</published><updated>2008-01-22T11:05:18.934-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyonrails'/><title type='text'>Stupid Easy: 7 Deadly [Rails] Sins, Part 2</title><content type='html'>In the second part of &lt;a href="http://snakesgemscoffee.blogspot.com/2008/01/stupid-easy-7-deadly-rails-sins-part-1.html"&gt;Stupid Easy: 7 Deadly [Rails] Sins&lt;/a&gt;, we look at sins four through six and save the most heinous sin for last in the third part of this series.

&lt;h4&gt;4. Stupid Easy Views&lt;/h4&gt;
Everyone has seen spaghetti view code, either in the JSP, PHP, or other similar web templating environments.  So why do people honestly think (ok, only the no-thinkers) that when they use Rails that you can't violate MVC because it is built around the MVC "pattern".  Don't even get me started on the whole "pattern" thing...

If you see a piece of view code like the following, then you are violating MVC.  Yes, even when using Rails:
&lt;pre&gt;&lt;code&gt;
&amp;lt;% 
if current_user &amp;&amp; current_user.admin?
  @somevar = something goes here
  # and do something else here
else
  @somevar = something else goes here
end
%&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
This is a small (but very stupid) violation, but a violation none-the-less.  I have seen not just logic in view code, but oddly model-specific code (in one case I even saw some attributes of a model being updated at the END of the view template - go figure).  Now simply checking the admin flag of the &lt;code&gt;current_user&lt;/code&gt; to insert some view specific code, would not be violating MVC.

There are various ways of making sure you don't make this mistake.  Some people think using a specialized templating language in place of eRB is the way to go (one of the many potential replacements that I happen to know about is &lt;a href="http://haml.hamptoncatlin.com/"&gt;Haml&lt;/a&gt;).  These specialized template languages basically make it virtually impossible to embed any kind of logic into the view code.  Others (pro-thinkers, rather than no-thinkers) don't believe that level of enforcement is necessary and just make a point of highlighting to the team (and then quickly refactoring) any violations in view code to educate all the team members about this sin.  

I have even seen some Rake tasks written (ok, I may have written a couple) that flag any views that have specific keywords within the &lt;% %&gt; eRB brackets as potential violators so that we can look through the suspects and make judgments before a release is made (because you never want to release code that violate basic principles you believe in).  Another strategy is to only work with developers that you share a very similar development philosophy and know they wouldn't violate this basic rule of thumb in the first place.  

In any case, you need to incorporate one of these strategies so you can be sure to be rid of the Stupid Easy View &lt;b&gt;faux pas&lt;/b&gt; in Rails projects.

&lt;h4&gt;5. Stupid Easy Configuration (and Routes)&lt;/h4&gt;
In Rails 2.0 there is absolutely ZERO excuse for this (with very nice initializer hooks available), but even pre-2.0 it is hard to justify an &lt;code&gt;config/environment.rb&lt;/code&gt; consisting of more than 30 lines (including basic Rails configuration minus comments).

Separation of configuration into separate files that are required by &lt;code&gt;config/environment.rb&lt;/code&gt; were my personal preference in pre-2.0.

On the &lt;code&gt;config/routes.rb&lt;/code&gt; side, I would often see the default routes still uncommented.  In Rails post-1.2 there is really no excuse since almost everyone should be using RESTful routes with RESTful controllers (even if the way Rails generates a ridiculous amount of code, when it could be refactored nicely into a mixin or base class, is pretty dumb) anyway.  In fact, default routes are almost always an invitation for troublemakers to hack into your site (especially if they know it is a Rails site).  I can't say I have never deployed a Rails 1.2.x site with default routes uncommented, but I certainly haven't in the last year since Rails 1.2's official release.

&lt;h4&gt;6. Stupid Easy Library Code&lt;/h4&gt;
For some reason numerous popular Rails plugins promote the idea of putting generated files into your &lt;code&gt;RAILS_ROOT/lib&lt;/code&gt; directory (e.g. acts_as_authentication, restful_authentication, etc.).  Instead of being a real plugin they are basically just Rails generators.  Now we could debate how generators could be better supported in Rails, but that is really a tangent to the point.  Don't be fooled by these popular "plugins" (remember they are really only generators).  

If you have &lt;b&gt;significant&lt;/b&gt; code in your &lt;code&gt;RAILS_ROOT/lib&lt;/code&gt; that has the same focus (e.g. authentication, authorization, credit card processing, etc.), then you really ought to create your own plugin (even if it isn't going to be shared with other projects in the foreseeable future).  How you and/or your team defines &lt;b&gt;"significant"&lt;/b&gt; is a judgment call, but I personally think any unit of code that works together that is greater than 200 lines of elegant Ruby code is significant (minus empty lines and comments of course).  It isn't necessarily a hard or fast rule, but a general rule of thumb, which may have exceptions.

The main purpose of this is twofold from my perspective.  When you have a lot of code in your &lt;code&gt;RAILS_ROOT/lib&lt;/code&gt; it is a harder to be more disciplined about organization.  Whereas with a plugin you know where plugin initialization code goes vs. mixin or other code.  Another big benefit (which is related to code organization) is testing/specing.  It is much easier to see (or specifically notice) test/spec-coverage shortcomings of plugins than holes in your tests/specs for &lt;code&gt;RAILS_ROOT/lib&lt;/code&gt; code.  

Benefits of creating plugins include that it is forces you (the developer) to think more about:
&lt;ul&gt;&lt;li&gt;Testing/specing your code&lt;/li&gt;&lt;li&gt;code organization from a maintainability perspective&lt;/li&gt;&lt;li&gt;what chunks of code go together (should this be one or two plugins)&lt;/li&gt;&lt;li&gt;the scope of your plugin vs. what needs to be defined or written in your application code&lt;/li&gt;&lt;li&gt;who is responsible for developing this functionality, maybe it is the infrastructure/core/framework development team who needs to be responsible for authentication or authorization code rather than application developers.  With plugins you can separate our these responsibilities very easily&lt;/li&gt;&lt;/ul&gt;

In addition, a plugin is much more easily shareable across projects (in a more organized and disciplined way than just copying or even linking files/directories into your &lt;code&gt;RAILS_ROOT/lib&lt;/code&gt; directory).

Of course, I also find the way Rails plugins are supported by Rails a hindrance, especially when RubyGems &lt;b&gt;could&lt;/b&gt; provide a many facilities to support this in a more elegant way, but that is a topic for another day!:)

There is only one Stupid Easy Deadly [Rails] Sin left, which I will publish in the next two weeks.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-4840965527902167268?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/4840965527902167268/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=4840965527902167268' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4840965527902167268'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4840965527902167268'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/01/stupid-easy-7-deadly-rails-sins-part-2.html' title='Stupid Easy: 7 Deadly [Rails] Sins, Part 2'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-9132450953725179337</id><published>2008-01-12T10:46:00.000-06:00</published><updated>2008-01-12T11:39:27.629-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='stupid'/><category scheme='http://www.blogger.com/atom/ns#' term='sin'/><title type='text'>Stupid Easy: 7 Deadly [Rails] Sins, Part 1</title><content type='html'>Everyone has been talking about Rails for a few of years now as the framework that makes it "stupid easy" to create web applications.  The problem I have with this mentality is it creates a "stupid easy" sub-culture within the Rails community that promotes stupid laziness (as opposed to smart laziness).

Over the last 3 Rails contracts, I have worked with supposedly experienced developers that have switched to Rails from OO languages like Java and C++ (as opposed to pretend OO languages like PHP).  The results have been VERY disappointing.

The biggest problem I see is that some, even quite experienced developers, have the [stupid] lazy mentality burned into their brains.  They want to visit blogs and copy and paste code into their applications without thinking about it.  In fact, recently I found a ridiculous real world example of such madness.  No thought put into the pasted code AT ALL.  I could identify the blog posting on a popular Rails blog that had the EXACT same code.  The variables weren't EVEN renamed to be meaningful in the current application.  Instead a finance web application had references to customers and invoices in the controller and view code when there were no such entities/models involved in the application.  Not only is this a maintenance nightmare, it show how little thought went into the code just to get a small AJAX effect that (a) wasn't difficult to write from scratch (4 lines in total after refactoring from the 11 lines of code used) and (b) was probably not even that necessary from the user experience perspective.

These are the developers I think ought to return to their no-thinking PHP or Java/JEE recipe books and leave the Ruby world alone.  Of course, the authors of "recipe" books should also bare some of the blame for encouraging the no-thinking hackers out there to join the Ruby world in the first place.  Is that really the type of mindset we wish to harbor in our community?

Look at the PHP and Java universes today for a reference point of where we will end up very soon if we are not careful.

&lt;h4&gt;1. Stupid Easy Schemas&lt;/h4&gt;
One of the deadly sins I saw on my travels through their code was extending the "stupid easy" mentality in the form of not defining schema properly.  Sure they *used* Rails' migrations, but they didn't really create a usable schema based on the use stories and cross-story functional requirements.

While you might not want to use database specific features like triggers, stored procedures, or even foreign keys (the latter assumes you are using Rails-based equivalents in place of FKs), you should still make your schema sensible.

For example,
&lt;pre&gt;&lt;code&gt;
# Migration code
  create_table :countries do |t|
    t.column :code, :string
    t.column :name, :string
  end

# Model code
class Country &lt; ActiveRecord::Base
end
&lt;/code&gt;&lt;/pre&gt;
In this example we created a table called &lt;code&gt;countries&lt;/code&gt; that has two string fields: &lt;code&gt;code&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;.  In this case the code is the ISO country code that the application is going to use for almost all country lookups in the database.  We knew that pretty much from the beginning because of the nature of our application.  (Note: this example is a little contrived as I had to change things so as not to expose too much about the application this is from).

Not only do we know that the &lt;code&gt;Country&lt;/code&gt; model will almost always be looked up by its code (from the initial set of use(r) cases/stories we need to implement).  The ISO code is also always 3 characters long.  We also know that these ISO codes are unique for each country.  All these things we knew before we needed to create the schema.  This is the important information that is missing from the schema and the model is missing relevant validation.

Before committing this new model and migration for &lt;code&gt;Country&lt;/code&gt; the developer writing the code (one of the developers I am thinking of loves to tout how XP/agile and great his code is), should have had the following code on initial checkin (there really is no excuse IMHO):
&lt;pre&gt;&lt;code&gt;
# migration code
  create_table :countries do |t|
    t.column :code, :string, :limit =&gt; 3
    t.column :name, :string # You may even decide to cap the length of the name field too, but this is more of a DBA style thing.
  end
  add_index :countries, :code, :unique =&gt; true

# Model code
class Country &lt; ActiveRecord::Base
  validates_length_of :code, :is =&gt; 3
  validates_uniqueness_of :code
end
&lt;/code&gt;&lt;/pre&gt;
While on my soapbox about this point, another developer I respect said this might be premature optimization.  In this specific case, I totally disagree, however, this is a good point to make in general.  You don't want to do premature optimization either.  This is something we all need to be careful about and to be aware.  

This is the point where you need to &lt;a href="http://snakesgemscoffee.blogspot.com/2007/12/dont-outsource-your-thinking-process.html"&gt;think for yourself&lt;/a&gt;.  There is no cheat sheet on these types of thinking points.  The [stupid] lazy Rails developers should go back to hacking PHP senselessly or following J2EE/JEE blueprint patterns if they find thinking for themselves to develop their own rules of thumb too much work.  Remember [smart] lazy is the way to go.

&lt;h4&gt;2. Stupid Easy Models&lt;/h4&gt;
Another cardinal sin I often see in both client work I have inherited from others or open source projects is dumbing down models.  Instead of creating member methods of models that related to *what* they are and do, some Rails coders (usually not very experienced) write this functionality within the controller layer, which leads to drastic controller layer bloat.  Which is really ugly.  Remember if your code is starting to look ugly, do something about it.  The way to fix this is *almost* always to create relevant member methods on the corresponding models that this functionality works on.  

One easy example of this might be to add an &lt;code&gt;authenticate&lt;/code&gt; method to the &lt;code&gt;User&lt;/code&gt; model instead of having extra logic in the &lt;code&gt;ApplicationController&lt;/code&gt;.  Think CRC instead of procedural.

&lt;h4&gt;3. Stupid Easy Controller Methods&lt;/h4&gt;
This is perhaps the most obviously harmful (from a security perspective) sin.  While traveling through the Supid Easy Rails universe of code, I almost always see unprotected controller methods that are used as filters or for utility purposes in some capacity.  This is a sin that I myself must confess to, though I haven't done so since the first 3 months of my Rails development around 2005 Q4.

The following is typically what I see around:
&lt;pre&gt;&lt;code&gt;
# in SparksController
  def check_permissions
    return false unless current_user.has_permission?(:MANAGE_USERS)
    true
  end
&lt;/code&gt;&lt;/pre&gt;
The problem with this is that we can do the following (unless using resource routes where default routes are taken out):
&lt;pre&gt;GET /sparks/check_permissions?some_var=some_value&lt;/pre&gt;
And perhaps hack into the web application.  In this case we may not be able to do anything too interesting, but there are other scenarios that this could create a very open hole in the web application.

The way to fix this and keep your team's sanity from a maintenance perspective is to protect these filter or utility methods by scoping appropriate with &lt;code&gt;protected&lt;/code&gt; or &lt;code&gt;private&lt;/code&gt;, which is not difficult at all.  However, if you are a true Rubyist you will probably opt to keep your controller utility methods in &lt;code&gt;Acts::As&lt;/code&gt; mixins or similar.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-9132450953725179337?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/9132450953725179337/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=9132450953725179337' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/9132450953725179337'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/9132450953725179337'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2008/01/stupid-easy-7-deadly-rails-sins-part-1.html' title='Stupid Easy: 7 Deadly [Rails] Sins, Part 1'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-3040156868168082852</id><published>2008-01-04T19:10:00.000-06:00</published><updated>2008-01-04T12:07:17.855-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='benchmarking'/><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><title type='text'>'Twas the night before launch...</title><content type='html'>Oh fun times!  On such fateful nights you know you will find something that will screw things up unless immediately addressed.  It must be someone's law already, but if it isn't I call dibs on it and it should thus be called &lt;b&gt;"Potter's Law"&lt;/b&gt;:).

On a night before launch that I recently experienced, I had a heart stopping moment.  The last 5 days of the sprint I had been churning out last minute changes based on usability feedback from the client.  In the process, I had neglected to include benchmarking and memory leak testing with the same discipline I prefer.  On the night before launch I ran my usual benchmarking scripts.  The results were pretty good except on one action.  We could have lived with that performance, but the real problem came when I ran tests to detect memory leaks in my Rails application.  Let the real fun begin!

Before continuing, I should mention there is only thing I hate more than uber visual tasks (editing/creating graphics, layout tweaking, etc.) and that is debugging memory leaks.

I ran the &lt;code&gt;ab&lt;/code&gt; (Apache Bench) utility on a few different types of pages in the staging environment and didn't notice any problems.  The Rails application was consistently teetering under 50M RAM.  Excellent!  Then I tried the second most requested type of page (this was a static site we were rewriting in Rails to create an easy to use domain-specific CMS - so we had live production web statistics) and my heart skipped a beat or three.

Memory usage spiraled out of control.  From 49.5M the single mongrel process eventually grew to 250M after a few &lt;code&gt;ab -c2 -n100 ....&lt;/code&gt; runs.

I first checked out the view since I knew the controller action code was only 6 lines (how much could go wrong there?).

I tried removing different parts of the view code and rerunning my tests while monitoring the memory usage of the process.  Still no change.

So I reluctantly looked in the controller and saw something like the following:
&lt;pre&gt;&lt;code&gt;
  def show
    @eto = ExchangeTradedOption.find_active(params[:id])

    respond_to do |format|
      format.html # show.rhtml
      format.xml  { render :action =&gt; "xml", :layout =&gt; false }
      format.csv  { render :action =&gt; "csv", :layout =&gt; false }
    end
  end
&lt;/code&gt;&lt;/pre&gt;
What on earth was in the &lt;code&gt;ExchangeTradedOption.find_active&lt;/code&gt; method?
&lt;pre&gt;&lt;code&gt;
  class &lt;&lt; self
    def find_active(id)
      now = Time.now
      find(
        id,
        :include =&gt; [:exchange, {:vendor_symbols =&gt; [:vendor]}],
        :conditions =&gt; [
          %{options.expires_at &lt;= ? AND options.active = 1 AND 
            vendor_symbols.effective_date &lt;= ? AND vendor_symbols.expiration_date &gt;= ?}, 
          now, now, now
        ],
        :order =&gt; 'vendor_symbols.effective_date DESC'
      )
    end
  end
&lt;/code&gt;&lt;/pre&gt;
Ouch!  The problem was that I still needed all vendor symbols and vendors for the view in question and didn't want to make the extra SQL queries for the Vendor on each VendorSymbol as that would have added between 5-10 extra SQL queries per action invocation.

I guessed the nested includes in the find was most likely to be causing the issue.  So I refactored as followed:
&lt;pre&gt;&lt;code&gt;
# Controller action code: ExchangeTradedOptionsController#show
  def show
    @eto = ExchangeTradedOption.find_active(params[:id])
    @vendor_symbols = VendorSymbol.find_active_for(params[:id])

    respond_to do |format|
      format.html # show.rhtml
      format.xml  { render :action =&gt; "xml", :layout =&gt; false }
      format.csv  { render :action =&gt; "csv", :layout =&gt; false }
    end
  end

# Model finder code: ExchangeTradedOption.find_active
  class &lt;&lt; self
    def find_active(id)
      now = Time.now
      find(
        id,
        :include =&gt; [:exchange],
        :conditions =&gt; [%{options.expires_at &lt;= ? AND options.active = 1}, now],
      )
    end
  end

# The VendorSymbol.find_active_for method implementation is left as an exercise for the reader
# but it is very, very simple!

# Changed view to refer to @vendor_symbols array instead of @eto.vendor_symbols
# This also helped reduce indirection and prevented Law of Demeter violations in the view code!
&lt;/code&gt;&lt;/pre&gt;
Of course, I didn't leave it without verifying!  Never just guess that the problem is solved, you should always verify this with tests (either automated or manual).

There were a few solutions, of course.  One is the one I provided above.  The second solution I thought of trying was removing the &lt;code&gt;vendor_symbols&lt;/code&gt; SQL conditions and sorting in Ruby.  This seemed a waste of CPU to me and extra lines of code I felt was unnecessary (remember the less code you write the less you need to maintain!).  The third solution was to execute an optimized raw SQL query myself.  I also didn't like this option as the first one presented about seemed cleaner and more maintainable going forward.  The first solution does cause more than one SQL query to be executed each time the action is run, but it is scalable as it remains constant at two queries per invocation.  In addition I added one extra instance variable, which is also not ideal, but again this didn't seem to me to be the worst problem at this stage with only two instance variables in the action being set for the view.

The benchmarking results proved my assumptions correct regarding the various solutions.

The moral of the story is find the best solution based on the context rather than attempting to apply a &lt;b&gt;one-query-per-action-invocation&lt;/b&gt; rule to the whole application, which might be ideal, but occasionally unrealistic and/or catastrophic.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-3040156868168082852?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/3040156868168082852/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=3040156868168082852' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3040156868168082852'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3040156868168082852'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/01/twas-night-before-launch.html' title='&apos;Twas the night before launch...'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-5457698920128542166</id><published>2007-12-30T14:59:00.000-06:00</published><updated>2008-01-02T15:23:12.172-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='predictions'/><category scheme='http://www.blogger.com/atom/ns#' term='future'/><title type='text'>2008 Predictions</title><content type='html'>Since the end of the year is almost upon us close associates of mine have been asking my predictions for the new year.  While obliging them here I suspect in 12 months time I will review this blog posting in (most likely) absolute humiliation and horror as most that make predictions do.&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Web 2.0&lt;/b&gt; will die in 2008 and Web 3.0 will reach adolescence in comical fashion, but will not receive the same private equity interest of its predecessor.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Carbonless Datacenter&lt;/b&gt; will be the IT buzz phrase for a good part of 2008.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SaaS&lt;/b&gt; will morph into yet another acronym (hopefully one that makes more sense in terms of capitalization) that will take into account yet another business model twist.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DHH&lt;/b&gt; will continue to be perceived by many inside and outside the Ruby/Rails world as an arrogant a$$.&lt;/li&gt;&lt;/ul&gt;

Lastly, I predict that Tony Blair will concede and sincerely apologize for his disastrous 2002 prediction that Weapons of Mass Destruction (WMD) exist in Iraq that has already caused a much higher death toll (that continues to mount) orders of magnitude higher than the unmistakable military fiasco of the Charge of the Light Brigade.  Sorry I forgot Mr Blair doesn't have a sincere atom in his body and this is only a tech prediction list.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-5457698920128542166?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/5457698920128542166/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=5457698920128542166' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5457698920128542166'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5457698920128542166'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/12/2008-predictions.html' title='2008 Predictions'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-5223703299928477663</id><published>2007-12-06T22:54:00.000-06:00</published><updated>2007-12-07T01:40:19.360-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='opinion'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='outsourcing'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><category scheme='http://www.blogger.com/atom/ns#' term='outsource'/><category scheme='http://www.blogger.com/atom/ns#' term='think'/><category scheme='http://www.blogger.com/atom/ns#' term='politics'/><category scheme='http://www.blogger.com/atom/ns#' term='apple'/><title type='text'>Don't outsource your thinking process.  Think for yourself!</title><content type='html'>In the wirlwind of political turmoil and occasional nuance of late with the upcoming primary elections almost upon us, I realized just how offensive the two-party primary elections ought to be to a "free" country.  Why isn't everyone else as outraged as I am?

Traversing various circles I see Democrats hashing out their own political differences on foreign policy, social security and the overloaded and antiquated driver's license system to create a unified platform for the general election.  As if *ALL* Democrats think the exact same way.  Republicans on the other hand are dumb-founded and depressed by the lack of quality mainstream and viable candidate(s), not to mention trying to figure out what on earth their united platform is after a president that has probably done more to harm their party than any other individual in the last 25 years (hey, don't look at me or the Democrats, neither of us voted for him in the first place!:)).  Oh, I do enjoy being smug!

Being a foreigner I always found it odd that Americans (generally) seem more willing to outsource their political reasoning to one of two political parties (at least in contrast with my European experiences).  I have witnessed the deferral of political reasoning to their parties without thinking on so many occasions I have lost count.  If their *party* is anti-war, then they need to be too, no questions asked.  Alternatively if they are with the *other party* they need to buy into the whole Cheney-masterminded war on terror doctrine, where we must "take the war to the enemy", whatever that means.

Now back to the technical world (and this transcends borders).  I see the same type of decision/reasoning outsourcing.  In 1997, Apple coined the slogan "Think Different".  Fast forward 10 years and you have an ever growing Apple fan base that doesn't question (at least not Apple or Jobs), it just doesn't "think".  I am not saying Apple hasn't done anything good, but all you Apple fan boys (and girls) just think for yourselves once in a while, puh-lease!

To be fair and candid, it is not limited to the Apple fan base.  The Linux community (of which I am party) has been a huge propaganda machine for just as long.  A friend of mine who is primarily a Windoze user (sorry, I just couldn't resist) suggests that the Linux community has been pushing Linux (or at least certain distributions of it) as "just as usable out of the box as Windows", which he thinks is a sad and pathetic misrepresentation.  To be honest, I agree with him.  I don't think any Linux distribution is quite as end-user friendly as Windows out of the box and to me Linux is definitely NOT about that.  The Ubuntu weekender geeks that desperately try to be the geekiest person in the room should take note (among others).  Forget pushing Linux propaganda on others (remember we don't like Jobs-indoctrinated fan boys/girls selling us the Apple way either).  Accept Linux for what it is: a more exclusive club that sets the bar higher than the others!  Why should we be trying to lower the bar or grade?  Let the newbies reach for the higher bar to get started, so we can attract a higher quality user base than the other two options.  The BSD community is a great example!

I also feel this way on the Ruby front.  I do wish all those nuts-oh "Rails rulz" propagandists would steer clear of all the PHP and Java bashing and Rails hyping.  Do you really want all the subpar PHP and Java people (the ones that need hype so they can outsource their decision making to move over) in our world?  I personally only want to cherry pick the Java, PHP, Python, etc. developers that actually think for themselves and willingly and enthusiastically step over to the Ruby side.

In summary, if you want someone else to think on your behalf go ahead it is a "free" country in that respect (if you aren't in a free country remember nobody owns your internal thoughts so you may as well abuse that freedom as much as you can:)!).  However, you should also realize that it is only the free-thinkers that don't conform to mass expectations and opinions that are the ones changing things (mostly for the better, but feel free to disagree with me and form your own opinion:)).&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-5223703299928477663?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/5223703299928477663/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=5223703299928477663' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5223703299928477663'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5223703299928477663'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/12/dont-outsource-your-thinking-process.html' title='Don&apos;t outsource your thinking process.  Think for yourself!'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-1165049990959760831</id><published>2007-11-10T07:29:00.004-06:00</published><updated>2008-06-29T23:35:30.209-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='scm'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><title type='text'>My .gitconfig</title><content type='html'>&lt;pre&gt;&lt;code&gt;
[user]
  name = Susan Potter # make sure you change this
  email = me@susanpotter.net # make sure you change this
[color]
  diff = auto
  status = auto
  branch = auto
[diff]
  rename = copy
  color = auto
[apply]
  whitespace = strip
[pager]
  color = true
[status]
  color = auto
[alias]
  co = "checkout"
  ci = "commit"
  ciall = "commit -a -v"
  unmerge = "reset --hard ORIG_HEAD"
  lsbr = "branch -a" # list all branches, even remote ones
  mkbr = "branch" # create branch if you specify a branch name after it, e.g. git mkbr upgrading_rails
  # remove branch named after it, e.g. git rmbr upgrading_rails
  rmbr = "branch -d"
  # rename branch from one name to another
  mvbr = "branch -m"
  # 
  track = "branch --track"
  # list all tags, to keep commands consistent, e.g. git lstag
  lstag = "tag -l"
  # create a new tag based on specified commit
  mktag = "tag -a"
  # remove existing tag by name
  rmtag = "tag -d"
  # rename tag from one name to another
  mvtag = "tag -m"
  # create new remote repository for project
  mkrem = "remote add"
  # list remote repositories
  lsrem = "remote"
  # show status, keep same as svn command I used most frequently
  st = "status"
  # another alias for status that some scripts might use
  stat = "status"
  # fetch and rebase from svn repository
  spull = !git svn fetch &amp;&amp; git svn rebase
  # push keeping each local commit as atomic.
  spush = !git svn dcommit
  # initialize all submodules
  modinit = "submodule init"
  # update all submodules
  modup = "submodule update"
  # show status of all submodules
  modst = "submodule status"
  # add new submodule, i.e. git modadd module-name url
  modadd = "submodule add"
  # show last 15 log entries
  recentlog = "log -n 15"
  # push local committed changes to rubyforge and origin (usually GitHub)
  osspush = !git push rubyforge master &amp;&amp; git push origin master
  # pull changes from rubyforge and origin (usually GitHub)
  osspull = !git pull rubyforge master &amp;&amp; git pull origin master
  # sync (pull then push) from rubyforge and origin (usually GitHub)
  osssync = !git osspull &amp;&amp; git osspush
&lt;/code&gt;&lt;/pre&gt;
Place your &lt;code&gt;.gitconfig&lt;/code&gt; file in your home directory, e.g. &lt;code&gt;/home/username&lt;/code&gt;
Last updated: 2008-06-28&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-1165049990959760831?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/1165049990959760831/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=1165049990959760831' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1165049990959760831'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1165049990959760831'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/11/my-gitconfig.html' title='My .gitconfig'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-937334212437839340</id><published>2007-11-05T02:18:00.000-06:00</published><updated>2007-11-05T02:40:11.889-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R v0.3.0</title><content type='html'>After Twitter.com added a few extra APIs for favoriting statuses in October, I finally got around to adding them to Twitter4R tonight.  Since there is new functionality I have released it as version 0.3.0.

To install all you need to do is:
&lt;pre&gt;&lt;code&gt;$ sudo gem install twitter4r&lt;/code&gt;&lt;/pre&gt;
The Rubyforge mirrors might still be syncing if you are doing this soon after I post this message.

The changes to the library include:&lt;ul&gt;&lt;li&gt;Added &lt;code&gt;Twitter::Client#authenticate?&lt;/code&gt; method that is a lightweight way to verify a Twitter user's credentials&lt;/li&gt;&lt;li&gt;&lt;b&gt;&lt;em&gt;Favorites&lt;/em&gt;&lt;/b&gt;: allows Twitterers to add statuses to their favorites, list all their current favorites and remove any statuses from the list.&lt;/li&gt;&lt;/ul&gt;

Example code for verifying a user would look like:
&lt;pre&gt;&lt;code&gt;gem('twitter4r', '&gt;=0.3.0')
require('twitter')

client = Twitter::Client.new
unless client.authenticate?("macdeveloperthatlovesleopard", "iamasciolists")
  puts "Password does not describe user well!"
end

if client.authenticate?("developerthatdoesnotpretendtobegraphicdesigner", "seriousdevelopernotfakestevejobswannabe")
  puts "Password does describe user well!"
end&lt;/code&gt;&lt;/pre&gt;

Example code for using the favorites API:
&lt;pre&gt;&lt;code&gt;gem('twitter4r', '&gt;=0.3.0')
require('twitter')
require('twitter/console')

client = Twitter::Client.from_config('some/path.yml')
statuses = client.favorites
ids = statuses.collect {|s| s.id }
# clean favorites list by deleting
statuses.each {|s| client.favorite(:remove, s) }

# now add back first status id from original favorites list
client.favorite(:add, ids[0])
&lt;/code&gt;&lt;/pre&gt;
Enjoy!&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-937334212437839340?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/937334212437839340/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=937334212437839340' title='22 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/937334212437839340'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/937334212437839340'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/11/twitter4r-v030.html' title='Twitter4R v0.3.0'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>22</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-5015279754384886299</id><published>2007-10-30T23:11:00.000-05:00</published><updated>2007-10-31T00:00:41.551-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='cryptography'/><category scheme='http://www.blogger.com/atom/ns#' term='security'/><category scheme='http://www.blogger.com/atom/ns#' term='metafusion'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>The Democratic debate, Ann Coulter and cryptographic utilities for Ruby application developers</title><content type='html'>An unlikely trio, but...

After watching the flip flop of Clinton in the Democratic debate, the subsequent carcass ravaging by her competition, UFO spotting by spock (aka Kucinich) and two great stinging one-liners from Joe Biden about the presidential [dis]qualification of candidate 911, I stumbled across the infinitely dim and unimpressive Ann Coutler.  She was attempting to justify yet another deliberately outlandish statement she made last week, a desperate bid to remain in the limelight to which she is so addicted.

At this point, I remembered I wrote a RubyGem a few weekends ago that might be able to decrypt these events and make sense of it all (well maybe!?).

A few weekends ago I released &lt;code&gt;metafusion-crypto&lt;/code&gt; as a Ruby Gem.  I hadn't announced it yet as it got lost in the shuffle of some personal matters that forced me to take five days off work unexpectedly.  So here is the belated introduction to that small, humble Gem.

This &lt;code&gt;metafusion&lt;/code&gt; sub-project is basically just two utility classes: &lt;code&gt;Metafusion::Crypo::PrivateKey&lt;/code&gt; and &lt;code&gt;Metafusion::Crypo::DigitalSignature&lt;/code&gt; for application developers that don't want to worry about twiddling bits.  To install you only need to do the following:
&lt;pre&gt;&lt;code&gt;$ sudo gem install metafusion-crypto&lt;/code&gt;&lt;/pre&gt;
An example of &lt;code&gt;DigitalSignature&lt;/code&gt; usage might be:
&lt;pre&gt;&lt;code&gt;
require 'metafusion/crypto'

priv_key = Metafusion::Crypto.generate_key_pair
# this returns OpenSSL::PKey::RSA object into priv_key

include Metafusion::Crypto
sig = DigitalSignature.from_keys('rsa_key.pub', 'rsa_key')
original_text = "my clear text message"
crypted_text = sig.encrypt(original_text)
plain_text = sig.decrypt(crypted_text)
puts "It worked - let's celebrate!" if original_text == plain_text
&lt;/code&gt;&lt;/pre&gt;
An example of &lt;code&gt;PrivateKey&lt;/code&gt; usage might be:
&lt;pre&gt;&lt;code&gt;
include Metafusion::Crypto
pkey = PrivateKey.new('mypassphrase')
original_text = 'Yo yo yo.  What up dog?'
crypted_text = pkey.encrypt(original_text)
plain_text = pkey.decrypt(crypted_text)
puts "Let's roll and celebrate - it worked" if original_text == plain_text
&lt;/code&gt;&lt;/pre&gt;
I look forward to application developer's feedback.

I promise to return to the  agile anti-pattern "series" soon.

&lt;b&gt;Update:&lt;/b&gt; Since two people asked, I have stuck to my tradition of releasing with only 100% C0 code coverage of the project's RSpec examples.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-5015279754384886299?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/5015279754384886299/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=5015279754384886299' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5015279754384886299'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5015279754384886299'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/10/democratic-debate-ann-coulter-and.html' title='The Democratic debate, Ann Coulter and cryptographic utilities for Ruby application developers'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-2409759616232748903</id><published>2007-10-18T21:16:00.000-05:00</published><updated>2007-10-18T21:58:55.662-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='patterns'/><category scheme='http://www.blogger.com/atom/ns#' term='anti-patterns'/><category scheme='http://www.blogger.com/atom/ns#' term='kaizen'/><category scheme='http://www.blogger.com/atom/ns#' term='agile practices'/><title type='text'>Agile Anti-Patterns, Part 1</title><content type='html'>In the last 12 months I had the fortune to work at a client that calls themselves "agile". In fact they use marketing blurbs similar to "on the cutting edge of agile Ruby on Rails development" (I said *similar* because I didn't want to identify them uniquely).  

The problem is when I briefly worked on a project for them, the truth was very far from their marketing spiel.  At a minimum I can say that this was the case on the particular project I briefly worked on, but more likely to have been much more wide spread in the firm since the consulting manager managed all the client projects from what I could tell.

Why was I fortunate?  Well first of all, it was a very brief encounter.  I just couldn't bring myself to work in that environment for long (and thus you will not find it on my resume at all - nice try).  Perhaps that says something about me too (I know it does - it is a liability, I realize this and plan to work on it, but on the other hand I feel as if I shouldn't have to compromise my professional values to the level of this case).  Secondly, like I imagine is the case with most people, I learn more from bad experiences than good experiences.

I would also like to say this is by no means the only company that likely falls into this "let's talk about agile so much we pretend that in itself is being agile" trap.  I see 1,000s of job ads or consulting firm spiels saying agile-this, agile-that.  Probably only 10-20% of the spiels are decently aligned with reality (of course, I have no way of knowing or measuring this, but it seems like a sensible guess to me;)).

Now, is any agile team perfect?  No.  At least not in my experience.  Maybe there are perfect teams and I am just a loser that has never experienced one, but since I have never met a perfect person ever, I imagine this to be improbable.  The reason for my deduction is that since software agility is based on people and relationships NOT just tools, then only a team with perfect members could even dream of producing the perfect dynamic.  The thing is, you don't need an agile team to be perfect.  That is the premise of each agile practice I follow personally.  If you think about it, agile practices assume people at their core are faulty some of the time in some way, because we are mere mortals, not software gods (though at times developers do get a God-complex, which is one of our many faults - I am guilty here too)!

Ooops, tangent, but is serves a point here too.  I do not ever wish to imply that following the letter of all the agile practice "rules" will make you truly and *perfectly* agile.  I do not even think that is even the point.  For me it comes down to Kaizen, which in short means, to iteratively eliminate waste from a process each time you repeat and to break those iterations down into bite size pieces so you can track and measure progress (actually that is a very coarse way to describe Kaizen, which is a word I love so much I tattooed it on my body somewhere - just kidding.....or am I?).  Feel free to search the web for a better description (there are a few), but it is a word the more you read about (at least for me) the more you appreciate.  

Perhaps we will never be perfect agile developers.  Perhaps we will never be perfect business analysts, coders, managers, deployers, system administrators, database administrators, etc.  That to me is almost irrelevant.  The idea is we just need to strive to simply "do better" each time and have the right attitude.

This was really just the kick off piece (aka rant) for this "series" on anti-patterns relating to agile practices.  I'll have concrete examples coming soon in part 2, 3, etc.

Stay tuned.

PS If you are searching for an "agile" development post, don't despair, there are some decently agile places out there.  They may not be perfect, but hopefully better than the client project I will discuss.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-2409759616232748903?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/2409759616232748903/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=2409759616232748903' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2409759616232748903'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2409759616232748903'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/10/agile-anti-patterns-part-1.html' title='Agile Anti-Patterns, Part 1'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-4685028030080506695</id><published>2007-10-11T21:16:00.000-05:00</published><updated>2007-10-11T23:17:29.571-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='cdbaby'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='dhh'/><title type='text'>Coming out....in support of DHH [to some extent]</title><content type='html'>Since today is &lt;a href="http://www.hrc.org/issues/coming_out.asp"&gt;National Coming Out Day&lt;/a&gt; in the US I thought I would use it somehow in my blog entry.  Yes folks I am "coming out" in support of [some of] DHH's decisions.

Earlier today I noticed a lot of blog chatter regarding Rails and what various people think it *should* or should not be.  While I agree with some of these people's sentiments that DHH is an arrogant ass, I also think he has made *some* smart decisions (though on a number of mid/lower-level issues I think he has it totally wrong).  Now reread this paragraph until you get the point that I still think he is an arrogant ass with a God complex.

Firstly, let's discuss the "Twitter" issue.  Yes, I use Twitter (occasionally), though I see it's biggest impact (for my personal interests) in marketing rather than tweeting every time I use the bathroom (note: I am not part of the look-at-me-NOW-Gawd-damn-it generation - I was a humble 70s baby).  In fact, the marketing potential is what drove me to develop &lt;a href="http://twitter4r.rubyforge.org"&gt;Twitter4R&lt;/a&gt; in the first place.  What I don't like about Twitter is due to their own lack of foresight they desperately tried to blame a framework!  An application framework because they couldn't get their act together to do the necessary scalability testing of their whole deployment environment (which includes many things, only one of them is the application framework) before hand.  This is the same application framework that got them rolled out into production earlier than they would have with other frameworks.  The same framework that allows them NOW to scale to crazy amounts of hits per second!  I attempted to follow the dialog between the Twitter developers and Rails Core team, though not too successfully because it occurred through non-interlinked blog posts and comments.  However, from what I could gather the Twitter folk seemed to think it should be up to the application framework to create an already baked in way for them to setup replication models.  If they were using a PHP framework, they wouldn't have bothered asking since all the DB code is in the view anyway!  Why is that the responsibility of the application framework?  Now if Rails has somehow prevented them from being able to create ActiveRecord extensions to do this, I might understand, but as far as I can see....this just isn't so.

Secondly, the CDBaby rant.  If CDBaby took 2 years to release using Ruby/Rails, then they must have had much bigger problems than Rails itself!  Sorry to be blunt, but who takes that long anymore, even in a Java stack?

Ok, now for some leveling.  I still think DHH is an ass and an extremely arrogant ass at that.  I still think he has ridiculous ideas about trademarking logos (that he didn't even pay for), definitions of what components are, how vendor/* is better than utilizing Ruby Gems in virtualized or dedicated environments (WTF?) and general dependency loading in Rails.  Plus, did I mention I think he is an arrogant ass?  Not to mention a former PHP programmer...enough said!

Good night.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-4685028030080506695?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/4685028030080506695/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=4685028030080506695' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4685028030080506695'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4685028030080506695'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/10/coming-outin-support-of-dhh-to-some.html' title='Coming out....in support of DHH [to some extent]'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-4283818661445776536</id><published>2007-09-23T20:46:00.000-05:00</published><updated>2007-09-23T20:55:28.630-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R Announcements</title><content type='html'>First off I have been meaning to post an entry letting people know of a recently released Rails application written by Sergio Santos called &lt;a href="http://twitternotes.com"&gt;TwitterNotes&lt;/a&gt; that is using the &lt;a href="http://twitter4r.rubyforge.org" title="Definitive Ruby bindings for Twitter REST API"&gt;Twitter4R&lt;/a&gt; gem.

Thanks Sergio (and anyone else that worked on the project - sorry if I left you off).

Also Sergio emailed yesterday requesting message paging support with a suggested patch (thanks for contributing back - &lt;b&gt;muchas gracias&lt;/b&gt;).  This evening I released Twitter4R version 0.2.5 that incorporates the necessary code changes for this support.

What does this mean?

Previously you would have only been able to get the last 20 sent/received direct messages from the Twitter4R bindings doing something like the following:
&lt;pre&gt;&lt;code&gt;
messages = client.messages(:sent)
&lt;/code&gt;&lt;/pre&gt;
Now you can request specific pages like so:
&lt;pre&gt;&lt;code&gt;
messages = client.messages(:sent, :page =&gt; 2)
&lt;/code&gt;&lt;/pre&gt;
The same is true for the &lt;tt&gt;:received&lt;/tt&gt; case.

To install the Twitter4R RubyGem simply run the following in your terminal/console:
&lt;pre&gt;&lt;code&gt;$ sudo gem install twitter4r&lt;/code&gt;&lt;/pre&gt;

Give the Rubyforge mirrors a few hours to sync if you are only getting Twitter4R v0.2.4.

Thanks and enjoy!&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-4283818661445776536?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/4283818661445776536/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=4283818661445776536' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4283818661445776536'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4283818661445776536'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/09/twitter4r-announcements.html' title='Twitter4R Announcements'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-2211343703077995732</id><published>2007-09-21T00:45:00.000-05:00</published><updated>2007-09-21T12:34:11.882-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='data'/><category scheme='http://www.blogger.com/atom/ns#' term='activewarehouse'/><category scheme='http://www.blogger.com/atom/ns#' term='warehouse'/><category scheme='http://www.blogger.com/atom/ns#' term='activewarehouse-etl'/><category scheme='http://www.blogger.com/atom/ns#' term='ETL'/><category scheme='http://www.blogger.com/atom/ns#' term='datawarehouse'/><title type='text'>Custom Processors for ActiveWarehouse ETL</title><content type='html'>For anyone interested in extending &lt;a href="http://activewarehouse.rubyforge.org/etl/"&gt;ActiveWarehouse ETL&lt;/a&gt;'s features with custom pre/post processors, I thought I would share this piece of code that I wrote in August for a personal project I am working on.  The example should provide you with enough details for you to create your own custom processors.

&lt;pre&gt;&lt;code&gt;
# Written by Susan Potter &lt;mbbx6spp@rubyforge.org&gt; under open source MIT license.
# August 12, 2007.

require 'net/ftp'

module ETL
  module Processor
    # Custom pre-processor to download files via FTP before beginning control process.
    class FtpDownloaderProcessor &lt; ETL::Processor::Processor
      attr_reader :host
      attr_reader :port
      attr_reader :remote_dir
      attr_reader :files
      attr_reader :username
      attr_reader :local_dir
      
      # configuration options include:
      # * host - hostname or IP address of FTP server (required)
      # * port - port number for FTP server (default: 21)
      # * remote_dir - remote path on FTP server (default: /)
      # * files - list of files to download from FTP server (default: [])
      # * username - username for FTP server authentication (default: anonymous)
      # * password - password for FTP server authentication (default: nil)
      # * local_dir - local output directory to save downloaded files (default: '')
      # 
      # As an example you might write something like the following in your control process file:
      #  pre_process :ftp_downloader, {
      #    :host =&gt; 'ftp.sec.gov',
      #    :path =&gt; 'edgar/Feed/2007/QTR2',
      #    :files =&gt; ['20070402.nc.tar.gz', '20070403.nc.tar.gz', '20070404.nc.tar.gz', 
      #               '20070405.nc.tar.gz', '20070406.nc.tar.gz'],
      #    :local_dir =&gt; '/data/sec/2007/04',
      #  }
      # The above example will anonymously download via FTP the first week's worth of SEC filing feed data
      # from the second quarter of 2007 and download the files to the local directory +/data/sec/2007/04+.
      def initialize(control, configuration)
        @host = configuration[:host]
        @port = configuration[:port] || 21
        @remote_dir = configuration[:remote_dir] || '/'
        @files = configuration[:files] || []
        @username = configuration[:username] || 'anonymous'
        @password = configuration[:password]
        @local_dir = configuration[:local_dir] || ''
      end
      
      def process
        Net::FTP.open(@host) do |conn|
          conn.connect(@host, @port)
          conn.login(@username, @password)
          remote_files = conn.chdir(@remote_dir)
          @files.each do |f|
            conn.gettextfile(remote_file(f), local_file(f))
          end
        end
      end
      
      private
      attr_accessor :password
      
      def local_file(name)
        File.join(@local_dir, name)
      end
      
      def remote_file(name)
        File.join(@remote_dir, name)
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
The key things to note from this is that you are at present required to:
&lt;ul&gt;&lt;li&gt;define all your custom processors with in the &lt;code&gt;ETL::Processor&lt;/code&gt; module&lt;/li&gt;&lt;li&gt;name your custom processor class in the form XXXXProcessor&lt;/li&gt;&lt;li&gt;need to extend (or really just adhere to the message interface of) &lt;code&gt;ETL::Processor::Processor&lt;/code&gt; class defined in ActiveWarehouse ETL&lt;/li&gt;&lt;li&gt;define &lt;code&gt;initialize&lt;/code&gt; taking two arguments (look above for guidance)&lt;/li&gt;&lt;li&gt;define a &lt;code&gt;process&lt;/code&gt; method to do what you need do before or after the control process runs (for pre and post processors respectively)&lt;/li&gt;&lt;/ul&gt;
Hope this helps someone customize ActiveWarehouse more easily, since the only bad thing I have found with ActiveWarehouse is lack of documentation.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-2211343703077995732?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/2211343703077995732/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=2211343703077995732' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2211343703077995732'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2211343703077995732'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/09/custom-processors-for-activewarehouse.html' title='Custom Processors for ActiveWarehouse ETL'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-6062124064443135730</id><published>2007-09-13T21:16:00.000-05:00</published><updated>2007-09-13T21:46:41.534-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='plugins'/><category scheme='http://www.blogger.com/atom/ns#' term='layout'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='engines'/><title type='text'>Separating Rails Layout Associations</title><content type='html'>I just realized I hadn't shared this code yet on this blog.  I will also be including it in one of the &lt;a href="http://metafusion.rubyforge.org"&gt;metafusion&lt;/a&gt; subprojects (coming soon).  I've been using it when needed in my Rails projects on and off for the last several months.
&lt;h4&gt;The Problem&lt;/h4&gt;You have some controllers you include into your application from plugins or engines and you want to associate a particular layout to the plugin/engine controller from within your application (without changing anything in the plugin or engine - of course!).  There is not good way of doing this nicely in Rails presently as each controller usually defines controller-wide layouts within its definition.
&lt;h4&gt;The Solution&lt;/h4&gt;
&lt;pre&gt;
[finsignia/paths.rb]
&lt;code&gt;
# See:
# * Finsignia::Paths

# Contains helper methods related to paths and resolving modules and 
# classes from paths.  Provides for helpful mixin for various applications.
module Finsignia::Paths

  def self.included(base)
    base.extend ClassMethods
  end
  
  # Contains class methods for Finsignia::Paths mixin
  module ClassMethods
    @@element_postfixes = {:model =&gt; '', :controller =&gt; 'Controller'}

    def resolve_model(path)
      resolve_element :model, path
    end
  
    def resolve_controller(path)
      resolve_element :controller, path
    end
    
    def normalize_module_name(path)
      list = path.split('_')
      list.collect do |item|
        item.capitalize
      end.join
    end
    
    # Resolves path to a type of Rails element 
    # (e.g. model, controller, etc.).
    # 
    # Path refers to 'internal' path, NOT require path:
    #  'users/users' #=&gt; internal path
    #  'users/users_controller #=&gt; require path
    def resolve_element(type, path)
      first, rest = path.split('/')
      mod = ObjectSpace.const_get(normalize_module_name(first))
      while rest
        first, rest = rest.split('/')

        unless rest
          mod = mod.const_get(normalize_module_name("#{first}_#{@@element_postfixes[type].downcase}"))
        else
          mod = mod.const_get(normalize_module_name(first))
        end
        # I doubt this will ever get this far as per expeceted behavior of ObjectSpace and Module...so commented it out following unreachable line.
        #raise NameError.new("#{type} constant not found for internal path #{path}") unless mod
        return mod unless rest
      end      
    end
    
    # Separating this so we can stub this method out in specifications.
    def require_controller(path)
      require("#{path}_controller") unless "test" == ENV["RAILS_ENV"]
    end
    
    # Separating this so we can stub this method out in specifications.
    def require_model(path)
      require(path) unless "test" == ENV["RAILS_ENV"]
    end
  end
end
&lt;/code&gt;
[finsignia/layouts.rb]
&lt;code&gt;
# See:
# * Finsignia::Layouts
# * Finsignia::LayoutsError

# Raised when an exceptional condition arises in the Layouts 
# mapping process.
class Finsignia::LayoutsError &lt; Exception; end

# Provides a closure and declarative way to define layout
# mappings for an application that utilize controllers, views, 
# etc. from plugins.
#
# The closure approach simulates the Rails Routing approach 
# closely, like:
#  require 'application'
#  
#  Finsignia::Layouts.map do |map|
#    map.connect 'users/sessions', 'layout_name'
#  end
#
# The declarative approach looks like the following:
#  require 'application'
#  include Finsignia
#  Layouts.map 'users/sessions', 'layout_name'
# 
module Finsignia::Layouts
  class &lt;&lt; self
    def connect(controller_path, layout)
      require_controller(controller_path)
      # resolve controller class and call .layout(layout) on it.
      controller = resolve_controller(controller_path)
      controller.layout(layout)
    end
    
    def map(&amp;block)
      yield self if block_given?
    end
  end

#  private
    include Finsignia::Paths    
end
&lt;/code&gt;
&lt;/pre&gt;
So basically in a file like &lt;tt&gt;config/layouts.rb&lt;/tt&gt; of our application we have something like:
&lt;pre&gt;
&lt;code&gt;
Finsignia::Layouts.map do |map|
  map.connect 'users/sessions', 'session'
  map.connect 'users/users', 'users'
  map.connect 'accounts/transaction', 'account'
end
&lt;/code&gt;
&lt;/pre&gt;
Then include the &lt;tt&gt;config/layouts.rb&lt;/tt&gt; file in &lt;tt&gt;config/environment.rb&lt;/tt&gt; and you have separated your concerns relatively nicely and easily.

When I release metafusion-rails my plan is that instead of installing the &lt;tt&gt;finsignia/paths.rb&lt;/tt&gt; and &lt;tt&gt;finsignia/layouts.rb&lt;/tt&gt; in the &lt;tt&gt;lib&lt;/tt&gt; directory you would be able to do something like the following at the end of your &lt;tt&gt;config/environment.rb&lt;/tt&gt; file:
&lt;pre&gt;&lt;code&gt;
gem('metafusion-rails', '=MFR_VERSION')
require 'metafusion/rails'

Finsignia::Layouts.map do |map|
  map.connect 'namespace/resource', 'layout'
  # etc....
end
&lt;/code&gt;&lt;/pre&gt;
Alternatively you could still keep the layouts.rb file if your environment.rb is getting large and keep the layouts.rb include in your environment.rb.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-6062124064443135730?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/6062124064443135730/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=6062124064443135730' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6062124064443135730'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6062124064443135730'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/09/separating-rails-layout-associations.html' title='Separating Rails Layout Associations'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-9064198079083808365</id><published>2007-09-11T11:42:00.000-05:00</published><updated>2007-09-11T11:55:59.948-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='conductor'/><category scheme='http://www.blogger.com/atom/ns#' term='patterns'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='presenter'/><title type='text'>Model-Conductor-Controller fix</title><content type='html'>The New Bamboo blog talked about &lt;em&gt;Presenters &amp; Conductors&lt;/em&gt; last month.  The demonstration Rails application was broken, so the enclosed link to a zip file contains the fixes I made to get the application running as expected.

The offending code was in &lt;code&gt;lib/action_conductor/lib/action_conductor/errors.rb&lt;/code&gt;:
&lt;pre&gt;
&lt;code&gt;
module ActiveRecord
  class Errors
   def add_conductor_errors_for(record, mapping)
    return if record.valid?
    record.errors.each do |attribute, message|
      self.add(mapping[attribute.to_sym], message)
    end
   end
  end
end
&lt;/code&gt;
&lt;/pre&gt;
The fixed code changed one line as so:
&lt;pre&gt;
&lt;code&gt;
module ActiveRecord
  class Errors
   def add_conductor_errors_for(record, mapping)
    return if record.valid? &lt;b&gt;or record.new_record?&lt;/b&gt;
    record.errors.each do |attribute, message|
      self.add(mapping[attribute.to_sym], message)
    end
   end
  end
end
&lt;/code&gt;
&lt;/pre&gt;
The fixed zip file can be found at my
&lt;a href="http://www.4shared.com/file/24033810/a58d14dc/presenters_and_conductors-20070911.html"&gt;4shared&lt;/a&gt; account and also fixes the minor view errors.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-9064198079083808365?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='enclosure' type='application/zip' href='http://www.4shared.com/file/24033810/a58d14dc/presenters_and_conductors-20070911.html' length='0'/><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/9064198079083808365/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=9064198079083808365' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/9064198079083808365'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/9064198079083808365'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/09/model-conductor-controller-fix.html' title='Model-Conductor-Controller fix'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-3757329451494661049</id><published>2007-08-22T22:16:00.000-05:00</published><updated>2007-08-22T22:46:27.536-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='closures'/><title type='text'>Anonymous Classes: Java's Synthetic Closure, Part 2</title><content type='html'>So in the last installment we saw something like the following (paraphrased) in Java:
&lt;pre&gt;
&lt;code&gt;
    executeTransaction(session, new PersistenceExecutor() {
      public PersistenceExecutionResult execute() {
        return new PersistenceExecutionResult(session.save(persistableObject);
      }
    });
&lt;/code&gt;
&lt;/pre&gt;
To accomplish the same thing, though in a more error prone manner, we might just do the following:
&lt;code&gt;
&lt;pre&gt;
  session.open();
  Object obj = session.save(persistableObject);
  session.close();
&lt;/pre&gt;
&lt;/code&gt;
Now you say, well that looks better.  Well yes and no.  The problem with this approach arises when the logic in between &lt;tt&gt;session.open()&lt;/tt&gt; and &lt;tt&gt;session.close()&lt;/tt&gt; is more involved.  If we had 10 lines of code between the open/close markers and we came to refactor the logic, but omitted the call to close, our tests (if they are purely state-based tests from the TDD world) would probably not catch this omission at the level we would want to catch it.  Remember the more levels you need to debug to find the issue the more time you have wasted not doing useful work on new stories or fixing defects, etc.

Of course, in the Ruby world where closures are just there for the taking, we don't have to justify ugly looking code to help package reusable "wrapper" logic like above.  In a Ruby project that utilizes &lt;tt&gt;ActiveRecord&lt;/tt&gt; we can simply write:
&lt;pre&gt;
&lt;code&gt;
# When a corporate action like merger, spinoff, etc. occurs for an issuer, usually 
# a new option has a basket underlying instead of just a set of equities, so code might 
# look like the following.
Option.transaction(equityOption, basketOption) do
  equityOption.substitute_with(basketOption)
  equityOption.expire
  basketOption.activate
end
&lt;/code&gt;
&lt;/pre&gt;
So that I don't sound completely smug to my Java friends, I will let you decide which code snippet looks more beautiful!:)

&lt;b&gt;Update:&lt;/b&gt; One of my Java friends says there are no closures yet in Java 6, but there are talks about closures in 7 (or will it be version 96 next?), but it is all up in the air.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-3757329451494661049?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/3757329451494661049/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=3757329451494661049' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3757329451494661049'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3757329451494661049'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/08/anonymous-classes-javas-synthetic_22.html' title='Anonymous Classes: Java&apos;s Synthetic Closure, Part 2'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-3815299479059617307</id><published>2007-08-21T22:50:00.000-05:00</published><updated>2007-08-21T23:41:54.267-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='blocks'/><category scheme='http://www.blogger.com/atom/ns#' term='closures'/><category scheme='http://www.blogger.com/atom/ns#' term='oo'/><title type='text'>Anonymous Classes: Java's Synthetic Closure</title><content type='html'>I've been meaning to write this blog post since late last year when I revisited the Java world after a hiatus of adventures in the Ruby and Python worlds.

When I was a Java advocate (many moons ago it seems now), I really didn't appreciate the true beauty that is anonymous classes.  I saw them as ugly and reducing code readability.  Syntactically I still agree with that assessment, but conceptually I couldn't disagree more now.

When revisiting Java last year for a client, I found myself really missing blocks from the Ruby world.  The following common snippet of code in Ruby passes a block to a method:
&lt;pre&gt;
&lt;code&gt;
  connection = Net::HTTP.new
  connection.use_ssl = true if ssl?
  connection.start do |connection|
    request = yield connection if block_given?
    request.basic_auth(@login, @password) if require_auth
    response = connection.request(request, body)
    handle_rest_response(response)
    response
  end
&lt;/code&gt;
&lt;/pre&gt;
[This code is taken from the &lt;a href="http://twitter4r.rubyforge.org/"&gt;Twitter4R&lt;/a&gt; project and is slightly &lt;i&gt;paraphrased&lt;/i&gt;.]

What we are doing here is wrapping a block within a begin/end API construct.  So the previous snippet of code is effectively the same as (and in fact this is the way the above gets executed):
&lt;pre&gt;
&lt;code&gt;
  connection = Net::HTTP.new
  connection.use_ssl = true if ssl?
  connection.open
  request = yield connection if block_given?
  request.basic_auth(@login, @password) if require_auth
  response = connection.request(request, body)
  handle_rest_response(response)
  connection.close
  response
&lt;/code&gt;
&lt;/pre&gt;
Why is the first so much better from a code maintainability and readability perspective?  In my view the main benefits include (but are not limited to):
&lt;ul&gt;&lt;li&gt;Passing in a block ensures we close the connection (as in the above code example).  If we forgot to "end" the block in the first snippet we would get a &lt;tt&gt;SyntaxError&lt;/tt&gt; in Ruby which would remind us to close the block (and thus connection implicitly).  Which means less likely to introduce defects related to not closing connections unlike second code example.&lt;/li&gt;&lt;li&gt;Putting the code to be executed passed in with a block improves code readability and in my view that is always a good thing.  It is more obvious to fellow developers what is going on.&lt;/li&gt;&lt;/ul&gt;

Now in Java we can't simulate the code above in Java syntax...or can we?

I suggest with a few more &lt;tt&gt;{}&lt;/tt&gt; and &lt;tt&gt;()&lt;/tt&gt; and &lt;tt&gt;;&lt;/tt&gt; we can accomplish the same as above.  And if we cover our eyes with our hands and squint through the gaps in our fingers, we might be happy with the way it looks (OK, it is ugly, but this is the world of Java people!).

Below is an example of a similar technique as the Ruby example above, but this time in Java and related to database transaction (specifically in Hibernate3 below):
&lt;pre&gt;
&lt;code&gt;
// In PersistenceExecutionResult.java we define the following....
public class PersistenceExecutionResult {
  private Object result;
  public PersistenceExecutionResult(Object theResult) {
    result = theResult;
  }

  public Object getResult() { return result; }
  public void setResult(Object theResult) { result = theResult; }
}

// Now in PersistenceExecutor.java we define....
public interface PersistenceExecutor {
  public abstract PersistenceExecutionResult execute() throws Exception;
}

// Now in BaseDAO.java we have the following....
public class BaseDAO {
  public Long create(BaseObject persistableObject) throws Exception {
    final Session session = ....; // get session somehow - irrelevant to demonstration of synthetic closures in Java
    return (Long)executeTransaction(session, new PersistenceExecutor() {
      public PersistenceExecutionResult execute() {
        return new PersistenceExecutionResult(session.save(persistableObject);
      }
    });
  }

  // other stuff, but just adds noise to example.

  protected ExecutionResult executeTransaction(Session session, PersistenceExecutor executor)
    throws Exception {
    Transaction tx = null;
    PersistenceExecutionResult result = null;
    try  {
      tx = session.beginTransaction();
      result = executor.execute();
      tx.commit();
    } catch (Exception e) {
      if (null != tx) tx.rollback();
    } finally {
      session.close();
    }
    return result;
  }
}
&lt;/code&gt;
&lt;/pre&gt;
Now what on earth is going on?  Anyone?  Well I will explain in a subsequent posting and compare it to the equivalent code without using anonymous classes to wrap begin/end API constructs.  Right now I am too tired and lazy, but I will explain soon, fear not.

&lt;b&gt;Notes:&lt;/b&gt; the above works with JDK 1.4 because when I wrote this it had to be 1.4 compliant (therefore fancy generic interface definition to DAO was not possible).  I have only used JDK 1.5 (aka 5) and not 6.  Is there a nicer way of doing this in 6?&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-3815299479059617307?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/3815299479059617307/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=3815299479059617307' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3815299479059617307'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3815299479059617307'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/08/anonymous-classes-javas-synthetic.html' title='Anonymous Classes: Java&apos;s Synthetic Closure'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-4155962555468290999</id><published>2007-08-19T22:41:00.000-05:00</published><updated>2007-08-20T21:21:50.499-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='restful'/><category scheme='http://www.blogger.com/atom/ns#' term='HTTP'/><category scheme='http://www.blogger.com/atom/ns#' term='REST'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='web services'/><title type='text'>RESTful API &amp; Web Service Statuses</title><content type='html'>As I have browsed and read newsgroups, mailing lists, articles and even books related to RESTful APIs and Web Services very few tackle the issue of which HTTP status messages are most relevant for the communication at hand.

Below is a list of the few HTTP status codes that I most commonly use in the development of my own APIs and Web Services for my clients:
&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;404 “Resource Not Found”:&lt;/b&gt; Yes, you read that right.  When a resource of a specific &lt;i&gt;given&lt;/i&gt; ID does not exist in our datastore (whether due to it being an invalid ID or due to the resource having been deleted from our datastore).  For example, “GET /people/2349594934896107” may not exist in our database, so what do we display? Just the bare “people/show” view with a flash message saying no person with that ID exists? I should say not – at least not in our RESTful world.  However, if we are using something like &lt;tt&gt;acts_as_paranoid&lt;/tt&gt; or home-grown paranoid delete mechanism and we know the resource used to exist in our datastore, we could respond with &lt;b&gt;410 “Gone”&lt;/b&gt;.  The benefit of this is that we are communicating something useful back to the RESTful consumer, whether that is an end-user or another application or web service.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;401 “Unauthorized”:&lt;/b&gt; Returned when consumer either did not provide credentials to view restricted resource or the authentication and/or authorization failed.  This assumes your application uses the Basic or Digest HTTP Authentication scheme as according to the HTTP protocol the server needs to respond with specific HTTP headers in various circumstances of returning this status code.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;503 “Service Unavailable”:&lt;/b&gt; Used when taking the site down for maintenance.  I find this very handy indeed when upgrading the RESTful web services I deploy at work.  For my projects I create a stub Rails application that responds with a 503 for each valid type of request that comes in.  Clients of my services are usually services themselves or other applications, so this helps client developers that consume my web services know that this is a temporary blip and should be due to schedule maintenance (and a good reminder for them to check the emails I sent them over the weekend instead of ignoring them).  The best and easiest way to do this is to create a new routes.rb file that contains all valid requests and points it to a controller/action that responds with a 503.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;201 “Created”:&lt;/b&gt; Very handy, especially in the context of designing RESTful web services.  Status should be sent when replying to a POST that created a new resource on the server.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;202 “Accepted”:&lt;/b&gt; Yes, slightly obscure, but very handy.  Signals to consumer of application (whether end-user human or REST client application) that a long running/executing job has been submitted for processing, but we do not yet know the outcome of the “job” or “process”.  Of course, as with all of the above, to an end-user we would render a nicely designed web page telling them this, but if for some reason a bot (authorized or not) or web services client accessed our application, they would be able to determine some useful information from the status.&lt;/li&gt;
&lt;/ul&gt;
Please let me know any other HTTP status codes you find most useful when designing RESTful APIs.

&lt;b&gt;Update [2007-08-20]:&lt;/b&gt; Below are another couple of HTTP status codes, explanations and examples to aid in the more consistent development of true RESTful APIs:
&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;301 "Moved Permanently":&lt;/b&gt;  This is a good one when deprecating RESTful APIs.  For example, say you are no longer going to support the resource (and all related individual CRUD and custom methods) &lt;tt&gt;/corporate_bonds&lt;/tt&gt; and instead provide the following resource &lt;tt&gt;"/bonds/:type"&lt;/tt&gt; where &lt;tt&gt;:type&lt;/tt&gt; might include &lt;tt&gt;corporate&lt;/tt&gt;, &lt;tt&gt;municipal&lt;/tt&gt; and &lt;tt&gt;government&lt;/tt&gt;.  Whenever a valid request is made to the &lt;tt&gt;"/corporate_bond"&lt;/tt&gt; resource I will return a &lt;tt&gt;301&lt;/tt&gt; HTTP status to the consumer to inform them that they need to use the new API.&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;307 "Temporary Redirect":&lt;/b&gt;  This is a nifty little status when you have temporarily suspended a resource, for example a &lt;tt&gt;"/users"&lt;/tt&gt; resource.  If a user is delinquent on their latest monthly installment, you want to start redirecting all calls to their profile, etc. to a temporary page that informs consumers of the RESTful site that this account is not current.  There are probably many other good uses of this too.  If you know of one, please leave a comment and educate me further.&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-4155962555468290999?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/4155962555468290999/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=4155962555468290999' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4155962555468290999'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4155962555468290999'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/08/restful-api-web-service-statuses.html' title='RESTful API &amp; Web Service Statuses'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-8618387797468799403</id><published>2007-08-09T12:51:00.000-05:00</published><updated>2007-08-09T13:56:48.520-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rspec'/><category scheme='http://www.blogger.com/atom/ns#' term='rbehave'/><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='bdd'/><title type='text'>Story-level BDD specing using rBehave</title><content type='html'>For those in the BDD world that have been asleep at the wheel over the last month or two, Dan North has released &lt;tt&gt;rBehave&lt;/tt&gt; which wraps around &lt;tt&gt;RSpec&lt;/tt&gt; and allows developers to spec their code at the story level, which has been missed in &lt;tt&gt;RSpec&lt;/tt&gt; (though I don't know what I would do without &lt;tt&gt;RSpec&lt;/tt&gt;).

Dan mentions that he is discussing integrating his work into RSpec at some point in the future, but in the meantime Dan's &lt;tt&gt;rBehave&lt;/tt&gt; framework is currently at version 0.3.0 and is available for install as a Ruby Gem as usual: &lt;code&gt;sudo gem install rbehave&lt;/code&gt;

Dan gives an example of &lt;tt&gt;rBehave&lt;/tt&gt; using a bank transfer story on his blog.  Below I give another example of authenticating a user:
&lt;pre&gt;
require ‘rubygems’
require ‘rbehave’
require ’spec’ # for "should" method

require ‘user’ # the actual application code

Story "authenticate user",
%(As a user of the system I wish to authenticate and access my account information.) do

  Scenario "correct password is supplied but user has been suspended" do
    Given "my user account is created", User.new do |user|
      @user = User.new
    end
    Given "my username is", "myusername" do |username|
      @user.username = username
    end
    Given "my password is", "mypassword" do |password|
      @user.password = password
    end
    Given "my user account has been suspended", true do |suspended|
      @user.suspended = suspended
    end
    When "I attempt to authenticate with", "mypassword" do |password|
      @authenticated = @user.authentiate?(password)
    end
    Then "my user account should respond with authentication status of", false do |status|
      @authenticated.should be(status)
    end
  end

  Scenario "correct password is supplied and user is active and verified" do
    Given "my user account is created", User.new
    Given "my username is", "myusername"
    Given "my password is", "mypassword"
    Given "my user account has been suspended", false
    Given "my user account has been activated", true do |activated|
      @user.activated = activated
    end
    When "I attempt to authenticate with", "mypassword"
    Then "my user account should respond with authentication status of", true
  end

  # A few other scenarios go here, like non-activated user (but not suspended), etc.
  # Left as exercise to the reader.
end
&lt;/pre&gt;
Please note, i do not need to restate the Given, When or Then blocks passed in after they are defined once!&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-8618387797468799403?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/8618387797468799403/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=8618387797468799403' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8618387797468799403'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8618387797468799403'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/08/higher-leve-bdd-specing-using-rbehave.html' title='Story-level BDD specing using rBehave'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-8216800979760405150</id><published>2007-07-26T15:24:00.000-05:00</published><updated>2007-07-26T15:31:57.481-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyonrails'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R v0.2.4 Released</title><content type='html'>Version 0.2.4 of Twitter4R was released last night with the main change that makes the library much easier to use in Ruby on Rails applications.

To install/update Twitter4R v0.2.4 just use the following command: &lt;tt&gt;sudo gem install twitter4r&lt;/tt&gt;

For more information on how to integrate Twitter4R with Rails see the following links:
&lt;ul&gt;&lt;li&gt;&lt;a title="TweetApp:  Twitter4R on Rails reference application" href="http://www.devchix.com/2007/07/26/twitter4r-on-rails/"&gt;Twitter4R on Rails [DevChix blog post]&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a title="TweetApp:  Twitter4R on Rails reference application" href="http://twitter4r.rubyforge.org/rdoc/"&gt;TweetApp: Twitter4R on Rails [reference application]&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.youtube.com/watch?v=je_lMsdGYNw" title="Twitter4R on Rails Screencast"&gt;Twitter4R on Rails Screencast&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://twitter4r.rubyforge.org/rdoc/" title="Twitter4R RDoc"&gt;Twitter4R RDoc&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-8216800979760405150?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/8216800979760405150/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=8216800979760405150' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8216800979760405150'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8216800979760405150'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/twitter4r-v024-released.html' title='Twitter4R v0.2.4 Released'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-1026678953598673691</id><published>2007-07-24T11:14:00.000-05:00</published><updated>2007-07-24T11:21:50.551-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='power'/><category scheme='http://www.blogger.com/atom/ns#' term='blackle'/><category scheme='http://www.blogger.com/atom/ns#' term='google'/><category scheme='http://www.blogger.com/atom/ns#' term='energy'/><title type='text'>My Blackle Energy Consumption Experiment</title><content type='html'>I received a forward from a contact earlier today touting the energy savings of using Blackle instead of Google to search.  My immediate thought was surely there would be no difference for an LCD monitor, or would there?  I can, of course, see for CRT monitors energy savings are highly likely.  The following is the energy saving content from the email forward I received:
&lt;blockquote&gt;
We probably all use google several times a day - here's something to consider:

When your screen is white, be it an empty word page, or the Google page, your computer consumes 74 watts, and when its black it consumes only 59 watts. An article about the energy saving that would be achieved if Google had a black screen, taking into account the huge number of page views, worked out at a saving of 750 mega watts/hour per year.

In a response to this article Google created a black version of its search engine, called Blackle, with the exact same functions as the white version, but with a lower energy consumption, check it out: &lt;a href="http://www.blackle.com"&gt;http://www.blackle.com&lt;/a&gt;
&lt;/blockquote&gt;

Since I work for a very energy conscious firm and I have been cycling or walking EVERYWHERE around town this summer (except I allow myself use of my car one afternoon a week maximum), I was curious and had to get to the bottom of it.

&lt;h4&gt;Hypothesis&lt;/h4&gt;There will not be any significant change in power usage (aka wattage) when viewing Google.com in my web browser versus viewing Blackle.com in the same web browser window.  (I must confess I already knew how LCD monitors worked before conducting this experiment, but there are a surprising number of people who do not believe the backlight of an LCD monitor is on for the entire LCD regardless of what is being shown - ignorance is bliss for some).

&lt;h4&gt;Conditions&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Dimensions of browser on screen must remain constant for both page views&lt;/li&gt;&lt;/li&gt;Power reader is monitored for 30 seconds while each page is being displayed and all reading changes will be noted&lt;/li&gt;&lt;/ul&gt;

&lt;h4&gt;Equipment&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FP3-International-Kill-Electricity-Monitor%2Fdp%2FB00009MDBU%3Fie%3DUTF8%26s%3Delectronics%26qid%3D1185292894%26sr%3D1-1&amp;tag=illinilistn09-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325"&gt;P3 Kill-A-Watt reader (x1)&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt;&lt;/li&gt;&lt;li&gt;17" Dell LCD monitor (x1)&lt;/li&gt;&lt;li&gt;[Indirectly used] Dell workstation (x1)&lt;/li&gt;&lt;li&gt;Cell phone (to time page view durations)&lt;/li&gt;&lt;/ul&gt;

&lt;h4&gt;Preparation&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;Power off LCD monitor and unplug from power supply&lt;/li&gt;&lt;li&gt;Plug in power reader into power supply and plug LCD monitor plug into power reader socket&lt;/li&gt;&lt;li&gt;Select power reading (can choose from voltage, power and current "real-time" readings)&lt;/li&gt;&lt;li&gt;Record initial "power off" reading&lt;/li&gt;&lt;li&gt;Power on LCD monitor&lt;/li&gt;&lt;li&gt;Record initial "power on" reading&lt;/li&gt;&lt;/ol&gt;

&lt;h4&gt;Method&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;Open web browser and maximize window&lt;/li&gt;&lt;li&gt;Surf to &lt;tt&gt;http://google.com&lt;/tt&gt;&lt;/li&gt;&lt;li&gt;Start cell phone timer as soon as page is loaded&lt;/li&gt;&lt;li&gt;Monitor power reading for 30 seconds and note any changes to reading&lt;/li&gt;&lt;li&gt;In same web browser window surf to &lt;tt&gt;http://blackle.com&lt;/tt&gt;&lt;/li&gt;&lt;li&gt;Start cell phone timers as soon as page is loaded&lt;/li&gt;&lt;li&gt;Monitor power reading for 30 seconds and note any changes to reading&lt;/li&gt;&lt;/ol&gt;

&lt;h4&gt;Results&lt;/h4&gt;&lt;b&gt;Initial "power off" reading:&lt;/b&gt; 0W (zero Watts)
&lt;b&gt;Initial "power on" reading:&lt;/b&gt; 20W (twenty Watts) after 3-4 seconds
&lt;b&gt;Google reading:&lt;/b&gt; started at 20W, no change for 30 seconds, ended at 20W.
&lt;b&gt;Blackle reading:&lt;/b&gt; started at 20W, no change for 30 seconds, ended at 20W.

&lt;h4&gt;Analysis&lt;/h4&gt;As we can see there was no significant change in the reading (to stated equipment error of plus or minus 0.2%).

Therefore our hypothesis is shown to hold in this experiment using the equipment identified above and I conclude that Blackle has next to zero impact on energy consumption of LCD monitors.

The good news is that my LCD monitor consumes much less than Blackle's claim of 50-75 Watts regardless of the search engine presentation I use!:)&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-1026678953598673691?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/1026678953598673691/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=1026678953598673691' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1026678953598673691'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1026678953598673691'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/my-blackle-energy-consumption.html' title='My Blackle Energy Consumption Experiment'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-1278998586082131259</id><published>2007-07-22T12:50:00.001-05:00</published><updated>2007-07-22T12:56:47.542-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='release'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R v0.2.3 Released</title><content type='html'>As promised earlier this week Twitter4R v0.2.3 made it out this weekend.

Twitter4R v0.2.3 has just been uploaded to Rubyforge in Ruby Gem, tgz and zip formats.  It may take up to several hours for the Rubyforge download mirrors to sync, so you may not be able to download it for a while.

To upgrade/install using Ruby Gems:
&lt;code&gt;$ sudo gem install twitter4r&lt;/code&gt;

I am in the process of updating the &lt;a href="http://twitter4r.rubyforge.org"&gt;project website&lt;/a&gt;.  So bear with me while I take care of this.

Release Notes:&lt;ul&gt;&lt;li&gt;Fixed defect &lt;a href="http://retro.tautology.net/projects/twitter4r/ticket/31"&gt;#31&lt;/a&gt; such that passing string screen name as for user argument is handled correctly.&lt;/li&gt;&lt;li&gt;Fixed &lt;a href="http://retro.tautology.net/projects/twitter4r/ticket/30"&gt;#30&lt;/a&gt; typo: respond_to -&amp;gt; respond_to?&lt;/li&gt;&lt;li&gt;Added relevant exception handling for #message(:post, ...) case &lt;a href="http://retro.tautology.net/projects/twitter4r/ticket/32"&gt;#32&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Add ability to pass in Twitter::User object to Twitter::Client#user(...) &lt;a href="http://retro.tautology.net/projects/twitter4r/ticket/33"&gt;#33&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Added stats Rake task&lt;/li&gt;&lt;li&gt;Updated RDoc for Twitter::Client#user to warn against using it to get followers of authenticated user and updated ArgumentError raising logic as per &lt;a href="http://retro.tautology.net/projects/twitter4r/ticket/29"&gt;#29&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;
Enjoy!&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-1278998586082131259?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/1278998586082131259/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=1278998586082131259' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1278998586082131259'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1278998586082131259'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/twitter4r-v023-released.html' title='Twitter4R v0.2.3 Released'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-3427330924276056577</id><published>2007-07-18T17:21:00.000-05:00</published><updated>2007-07-18T17:26:27.114-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='release'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R v0.2.2 Released</title><content type='html'>As solo already noted in the comments for the &lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v021-released.html"&gt;Twitter4R v0.2.1 Released&lt;/a&gt; blog entry from yesterday, version 0.2.2 is already here:)

I usually wait 4 hours before I blog about it to let the Rubyforge gem servers sync.

Changes in the version 0.2.2 release include:
&lt;ul&gt;&lt;li&gt;Fixed URI paths for user, messaging and friendship APIs (#25)&lt;/li&gt;&lt;li&gt;Added action checks for Twitter::Client methods: #user, #my, #message, #messages, #status, #timeline, #friend (#26)&lt;/li&gt;&lt;li&gt;Added 'source' configuration documentation.&lt;/li&gt;&lt;li&gt;Added missing attributes for Twitter::User (#28)&lt;/li&gt;&lt;/ul&gt;

The most notable thing is that I add the &lt;tt&gt;protected&lt;/tt&gt; and &lt;tt&gt;profile_image_url&lt;/tt&gt; attributes to the &lt;tt&gt;Twitter::User&lt;/tt&gt; model class, which were missing previously.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-3427330924276056577?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/3427330924276056577/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=3427330924276056577' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3427330924276056577'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3427330924276056577'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/twitter4r-v022-released.html' title='Twitter4R v0.2.2 Released'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-52316730716089704</id><published>2007-07-17T10:30:00.000-05:00</published><updated>2007-07-17T10:36:11.749-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='release'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R v0.2.1 Released</title><content type='html'>To install/update Ruby gem use:
&lt;code&gt;sudo gem install twitter4r&lt;/code&gt;
OR
&lt;code&gt;sudo gem update twitter4r&lt;/code&gt;
Note: It may take some hours before the Rubyforge mirrors are in sync.

The ‘source’ feature was added to Twitter v0.2.1 such that in the web interface Twitter users can see which application the messages were sent from. The default is ‘twitter4r’ with link ‘http://twitter4r.rubyforge.org’. You can change this, but you will also need to contact Twitter developers (not Twitter4R) to set it up for you on the server side. To set relevant client configuration in Twitter4R you can do the following:
&lt;pre&gt;&lt;code&gt;
require('rubygems')
gem('twitter4r', '&gt;=0.2.1')
require('twitter')

Twitter::Client.configure do |conf|
  conf.source = 'mysourceidfromtwitter'
end
&lt;/code&gt;&lt;/pre&gt;
Remember you will need to contact the Twitter developers to setup a source id and url with them before the above code snippet will start working.

Enjoy!&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-52316730716089704?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/52316730716089704/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=52316730716089704' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/52316730716089704'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/52316730716089704'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/twitter4r-v021-released.html' title='Twitter4R v0.2.1 Released'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-1690681864217628731</id><published>2007-07-17T08:15:00.000-05:00</published><updated>2007-07-18T08:01:23.496-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='friendship'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R v0.2.0: Friendship API</title><content type='html'>After a week I realized I had not yet written about the &lt;b&gt;Friendship API&lt;/b&gt; in Twitter4R and it is one of the simpler the parts of the core library.

Below shows some code to add and remove a friend:
&lt;pre&gt;&lt;code&gt;
require('rubygems')
gem('twitter4r', '&gt;=0.2.0')
require('twitter')

client = Twitter::Client.new(:login =&gt; 'mylogin', :password =&gt; 'mypasswd')
# we can add a friend by using their unique integer id or user object.
screen_name = 'myfriend'
user = Twitter::User.find(screen_name)
id = user.id

# Like any of the following....
client.friend(:add, user)
client.friend(:add, id)

# We can also use any of the following APIs to do exactly the same thing...
client.friend(:remove, user)
client.friend(:remove, id)
&lt;/code&gt;&lt;/pre&gt;
See related &lt;a href="http://twitter.rubyforge.org/rdoc"&gt;Twitter4R RDoc&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-1690681864217628731?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/1690681864217628731/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=1690681864217628731' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1690681864217628731'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1690681864217628731'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/twitter4r-v020-friendship-api.html' title='Twitter4R v0.2.0: Friendship API'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-284782921713256508</id><published>2007-07-13T18:44:00.000-05:00</published><updated>2007-07-13T19:02:04.876-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R v0.2.0: Extras API</title><content type='html'>There are a couple of miscellaneous features in Twitter4R that I wasn't comfortable officially supporting like the &lt;i&gt;featured&lt;/i&gt; users method in the Twitter REST API that is documented to do one thing and implemented to do another by the actual Twitter developers.  Obviously I don't control what the Twitter developers do, so while I wanted to support this REST API in the library to achieve 100% Twitter REST API coverage I also wanted to emphasize that if the Twitter REST API implementation changes to work as documented then Twitter4R may not work well or at all for this method.  To get an Array of &lt;tt&gt;Twitter::User&lt;/tt&gt; objects that represent Twitter's current featured users we would write code like:
&lt;pre&gt;&lt;code&gt;
require('rubygems')
gem('twitter4r', '0.2.0')
require('twitter')

# Now we must include this extra file import or we will get a &lt;tt&gt;NoMethodError&lt;/tt&gt;
require('twitter/extras')

client = Twitter::Client.new
users = client.featured(:users)
&lt;/code&gt;&lt;/pre&gt;

Also in the Twitter4R &lt;b&gt;Extras API&lt;/b&gt; is a class helper method added to &lt;tt&gt;Twitter::Client&lt;/tt&gt; called &lt;tt&gt;from_config&lt;/tt&gt; which takes up to two arguments.  Here is an example:
&lt;pre&gt;&lt;code&gt;
require('rubygems')
gem('twitter4r', '0.2.0')
require('twitter')

# I have a 'twitter.yml' file in subdir 'config' that I will load credential from.
# This feature of &lt;tt&gt;Twitter::Client&lt;/tt&gt; is in the Extras API and you need to require
# twitter/console to access it.
require 'twitter/console'
config_file = File.join(File.dirname(__FILE__), 'config', 'twitter.yml')
twitter = Twitter::Client.from_config(config_file)

# By default the &lt;tt&gt;from_config&lt;/tt&gt; method selects the credentials defined
# for the 'test' environment.  The twitter.yml file will look something like this:
#  test:
#    login: mylogin
#    password: mypasswd
# If, however, we wanted another environment's credentials to be used we pass that 
# in as the second argument to &lt;tt&gt;from_config&lt;/tt&gt; like so...
twitter = Twitter::Client.from_config(config_file, 'dev')
&lt;/code&gt;&lt;/pre&gt;

Again &lt;a href="http://twitter4r.rubyforge.org/releases/v0.2.0/rdoc/"&gt;here is a link to the Twitter4R v0.2.0 RDoc&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-284782921713256508?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/284782921713256508/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=284782921713256508' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/284782921713256508'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/284782921713256508'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/twitter4r-v020-extras-api.html' title='Twitter4R v0.2.0: Extras API'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-2311278199329672731</id><published>2007-07-13T18:33:00.000-05:00</published><updated>2007-07-13T18:43:57.095-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='REST'/><category scheme='http://www.blogger.com/atom/ns#' term='user'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R v0.2.0: User API</title><content type='html'>The &lt;b&gt;User API&lt;/b&gt; provides an easy Ruby API that maps onto a less consistent Twitter REST API, which is good news for Twitter4R users:)

The following example code snippet shows how you would use the Twitter4R User API:
&lt;pre&gt;&lt;code&gt;
require('rubygems')
gem('twitter4r', '0.2.0')
require('twitter')

# I have a 'twitter.yml' file in subdir 'config' that I will load credential from.
# This feature of Twitter::Client is in the Extras API and you need to require
# &lt;tt&gt;twitter/console&lt;/tt&gt; to access it.
require 'twitter/console'
config_file = File.join('config', 'twitter.yml')
twitter = Twitter::Client.from_config(config_file)

# Gets the &lt;tt&gt;Twitter::User&lt;/tt&gt; object for user with screen name &lt;b&gt;otherlogin&lt;/b&gt;.
user = twitter.user('otherlogin')
# Gets an Array of &lt;tt&gt;Twitter::User&lt;/tt&gt; objects that represent the friends of user 
# with screen name &lt;b&gt;otherlogin&lt;/b&gt;.
friends = twitter.user('otherlogin', :friends)

# Gets the &lt;tt&gt;Twitter::User&lt;/tt&gt; object for the user we used to authenticate above
# using the &lt;tt&gt;Twitter::Client.from_config&lt;/tt&gt; method in the Extras API above.
me = twitter.my(:info)

# Gets the Array of &lt;tt&gt;Twitter::User&lt;/tt&gt; objects that represent the followers of 
# the user whose credentials were used to authenticate with the Twitter service.
myfollowers = twitter.my(:followers)
# The same can be done for friends, i.e.
myfriends = twitter.my(:friends)

# Alternative we can use the equivalent &lt;b&gt;Model APIs&lt;/b&gt; like so....
myfollowers = me.followers
myfriends = me.friends
&lt;/code&gt;&lt;/pre&gt;

Hope you find this helpful.  If you want to learn more about the &lt;b&gt;User API&lt;/b&gt; you should consult the &lt;a href="http://twitter4r.rubyforge.org/releases/v0.2.0/rdoc/"&gt;Twitter4R v0.2.0 RDoc&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-2311278199329672731?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/2311278199329672731/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=2311278199329672731' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2311278199329672731'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2311278199329672731'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/twitter4r-v020-user-api.html' title='Twitter4R v0.2.0: User API'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-7250742506761078582</id><published>2007-07-10T09:18:00.000-05:00</published><updated>2007-07-10T09:38:38.432-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='messaging'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R v0.2.0: Messaging API</title><content type='html'>Probably one of the more recent additions to the Twitter REST API has been that of direct messaging.  In the Twitter web interface we were introduced to the supported construct of direct messaging only a couple of months ago.  The &lt;b&gt;Messaging API&lt;/b&gt; in Twitter4R provides access to these new supported features via two instance methods on the &lt;tt&gt;Twitter::Client&lt;/tt&gt; class: &lt;tt&gt;#message&lt;/tt&gt; and &lt;tt&gt;#messages&lt;/tt&gt;.

Below we can see an example of a common non-trivial usage of this API:
&lt;pre&gt;&lt;code&gt;
gem('twitter4r', '&gt;=0.2.0')
require('twitter')

client = Twitter::Client.new(:login =&gt; 'mylogin', :password =&gt; 'mypassword')
received_messages = client.messages(:received)
# Now do whatever it is you wish to do with the Array returned and assigned to received_messages.
# It is an Array of Twitter::Message.  Twitter::Message has the following attributes:
# * sender (Twitter::User)
# * recipient (Twitter::User)
# * text (String)
# * created_at (Time)

new_message = client.message(:post, 'I am addicted to Twitter', 'myfriendslogin')
# Note: if we had a handle to our friend's Twitter::User object we could substitute that object
# for the screen name screen given (i.e. 'myfriendslogin' in the above example).  This would 
# look like:
#  new_message = client.message(:post, 'I am addicted to Twitter', user)

# Now we realized sending that message was a big mistake, so we try to delete is quickly:
deleted_message = client.message(:delete, new_message)
&lt;/code&gt;&lt;/pre&gt;
To find out more about the Messaging API of Twitter4R visit the &lt;a href="http://twitter4r.rubyforge.org/releases/v0.2.0/rdoc"&gt;v0.2.0 RDoc&lt;/a&gt; and select the &lt;b&gt;examples/messaging.rb&lt;/b&gt; link from the top left frame.

Also note that you can create new direct messages via the &lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-model-api.html"&gt;Model API&lt;/a&gt; of Twitter4R as well.

Happy direct messaging on Twitter using Twitter4R.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-7250742506761078582?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/7250742506761078582/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=7250742506761078582' title='15 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7250742506761078582'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7250742506761078582'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/twitter4r-v020-messaging-api.html' title='Twitter4R v0.2.0: Messaging API'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>15</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-2805663531424454151</id><published>2007-07-10T08:53:00.000-05:00</published><updated>2007-07-10T09:14:55.344-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='activerecord'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyonrails'/><category scheme='http://www.blogger.com/atom/ns#' term='model'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R v0.2.0: Model API</title><content type='html'>For those that are familiar with the &lt;b&gt;ActiveRecord&lt;/b&gt; API, the &lt;b&gt;Model API&lt;/b&gt; of Twitter4R should feel fairly natural for you.  Currently the raw Twitter REST API inherently exposes three types of models:&lt;ul&gt;&lt;li&gt;Status&lt;/li&gt;&lt;li&gt;User&lt;/li&gt;&lt;li&gt;Message&lt;/li&gt;&lt;/ul&gt;

These model classes are available via the Twitter4R classes &lt;tt&gt;Twitter::Status&lt;/tt&gt;, &lt;tt&gt;Twitter::User&lt;/tt&gt; and &lt;tt&gt;Twitter::Message&lt;/tt&gt; respectively.  What you will notice on these classes is that they have the familiar &lt;tt&gt;find&lt;/tt&gt; and &lt;tt&gt;create&lt;/tt&gt; class methods that behave almost identically to &lt;tt&gt;ActiveRecord::Base&lt;/tt&gt; subclasses (or models as we call them in Rails applications).  There is one difference, due in part to the slightly different way "connections" to the Twitter4R "datasource" is handled.  So below is a non-trivial example of using these ActiveRecord style methods:
&lt;pre&gt;&lt;code&gt;
gem('twitter4r', '&gt;=0.2.0')
require('twitter')

client = Twitter::Client.new(:login =&gt; 'mylogin', :password =&gt; 'mypassword')
# I want to post a new status to my timeline, which can also be do by:
#  client.status(:post, 'My new status message')
status = Twitter::Status.create(:client =&gt; client, :text =&gt; 'My new status message')

user = Twitter::User.find('dictionary', client)
message = Twitter::Message.create(:client =&gt; client, :recipient =&gt; user, :text =&gt; 'canadaphile')
&lt;/code&gt;&lt;/pre&gt;
What we see here is very similar to ActiveRecord style models, but not quite.  In the &lt;tt&gt;find&lt;/tt&gt; method invoked on &lt;tt&gt;Twitter::User&lt;/tt&gt; above we supplied a second argument, which is the &lt;tt&gt;client&lt;/tt&gt; (the client context or "connection") object.  We also make sure to pass in a &lt;tt&gt;:client&lt;/tt&gt; key-value pair in each &lt;tt&gt;create&lt;/tt&gt; method called above.  Without it an &lt;tt&gt;ArgumentError&lt;/tt&gt; would be raised by each &lt;tt&gt;create&lt;/tt&gt; call.

A few notes:&lt;ul&gt;&lt;li&gt;&lt;tt&gt;Twitter::User&lt;/tt&gt; does not define a meaningful &lt;tt&gt;create&lt;/tt&gt; method since Twitter doesn't allow the creation of new user account via their REST API&lt;/li&gt;&lt;li&gt;&lt;tt&gt;Twitter::User&lt;/tt&gt; model has some class and instance helper methods, which are described more in the RDoc (see link below)&lt;/ul&gt;

See the &lt;a href="http://twitter4r.rubyforge.org/releases/v0.2.0/rdoc/"&gt;v0.2.0 RDoc for Twitter4R&lt;/a&gt; and select the &lt;b&gt;examples/model.rb&lt;/b&gt; link from the top left frame.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-2805663531424454151?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/2805663531424454151/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=2805663531424454151' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2805663531424454151'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2805663531424454151'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/twitter4r-v020-model-api.html' title='Twitter4R v0.2.0: Model API'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-6792087507641281831</id><published>2007-07-08T21:35:00.000-05:00</published><updated>2007-07-08T21:55:01.311-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R v0.2.0: Status API</title><content type='html'>The &lt;b&gt;Status API&lt;/b&gt; deals with access to single status objects on the Twitter server.  Since "updating" (in the traditional sense) a status in Twitter is non-existent (you update your timeline with a brand new status, but you cannot edit individual status text after created), Twitter4R provide a CRD-style interface (rather than true CRUD-style).

For example, to post a new status to your timeline you might write code like the following:
&lt;pre&gt;&lt;code&gt;
gem('twitter4r', '&gt;=0.2.0')
require('twitter')
client = Twitter::Client.new(:login =&gt; 'mylogin', :password =&gt; 'mypassword')
status = client.status(:post, 'Learning the Twitter4R Status API in 60 seconds.')
&lt;/code&gt;&lt;/pre&gt;
To retrieve the full status object (&lt;tt&gt;Twitter::Status&lt;/tt&gt; instance - see Twitter4R Model API documentation) given a unique status ID you can code:
&lt;pre&gt;&lt;code&gt;
# Assume code in previous code snippet
status = client.status(:get, 140684282) # =&gt; should be announcement status of Twitter4R v0.2.0 release
# Now you can query the attributes of the status like...
puts "#{status.user.screen_name}: #{status.text} @ #{status.created_at}"
&lt;/code&gt;&lt;/pre&gt;
To delete the status we initially posted above (or any other status of yours that you have a handle to (a handle can be just the unique integer status ID or the status object itself) we can code:
&lt;pre&gt;&lt;code&gt;
# Again assume code from first code snippet above...
client.status(:delete, status) # here we can pass in the full object or just the id, it doesn't matter.  Same as in #status(:get, ...) case.
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Upgrading from 0.1.x&lt;/h4&gt;
To upgrade from Twitter4R v0.1.x you probably need to replace code like:
&lt;pre&gt;&lt;code&gt;
client.update('My status message text')
&lt;/code&gt;&lt;/pre&gt;
With code like the following:
&lt;pre&gt;&lt;code&gt;
client.status(:post, 'My status message text')
&lt;/code&gt;&lt;/pre&gt;
There were no APIs in Twitter4R v0.1.x for the single status &lt;tt&gt;:get&lt;/tt&gt; and &lt;tt&gt;:delete&lt;/tt&gt; use cases.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-6792087507641281831?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/6792087507641281831/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=6792087507641281831' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6792087507641281831'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6792087507641281831'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/twitter4r-v020-status-api.html' title='Twitter4R v0.2.0: Status API'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-1251647014648204935</id><published>2007-07-08T21:06:00.000-05:00</published><updated>2007-07-08T21:31:22.427-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R v0.2.0: Timeline API</title><content type='html'>The &lt;b&gt;Timeline API&lt;/b&gt; segment of Twitter4R provides access to the REST API that deals with public, friend and [public] user status timelines.  For example, to get an &lt;tt&gt;Array&lt;/tt&gt; of statuses representing your own timeline on Twitter all you would need to type was:
&lt;pre&gt;&lt;code&gt;
gem('twitter', '&gt;=0.2.0')
require('twitter')
client = Twitter::Client.new(:login =&gt; 'mylogin', :password =&gt; 'mypassword')
timeline = client.timeline_for(:me)
&lt;/code&gt;&lt;/pre&gt;
If your timeline is public and you do not need to invoke any &lt;em&gt;authenticated&lt;/em&gt; methods, you do not need to pass in login/password credentials to the &lt;tt&gt;Twitter::Client&lt;/tt&gt; constructor, just &lt;tt&gt;Twitter::Client.new&lt;/tt&gt; is all that is needed.

Other than the &lt;tt&gt;:me&lt;/tt&gt; timeline, the following are available:&lt;ul&gt;&lt;li&gt;:public - returns the public timeline on Twitter (last 20 statuses)&lt;/li&gt;&lt;li&gt;:friends - returns a timeline of all your friends' statuses&lt;/li&gt;&lt;li&gt;:friend - returns a timeline of one particular friend&lt;/li&gt;&lt;li&gt;:user - returns timeline of one particular user&lt;/li&gt;&lt;/ul&gt;

Different options are available with each of the timelines described above.  To read more consult the &lt;a href="http://twitter4r.rubyforge.org/releases/v0.2.0/rdoc/"&gt;Twitter v0.2.0 official RDoc documentation&lt;/a&gt;.
&lt;h4&gt;Upgrading from 0.1.x&lt;/h4&gt;
If you are upgrading from Twitter4R v0.1.x you will probably have code that looks similar to the following:
&lt;pre&gt;&lt;code&gt;
client = Twitter::Client.new(:login =&gt; 'mylogin', :password =&gt; 'mypassword')
timeline = client.public_timeline
&lt;/code&gt;&lt;/pre&gt;
All you will need to do is change the last (second) line above to:
&lt;pre&gt;&lt;code&gt;
timeline = client.timeline_for(:public)
&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-1251647014648204935?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/1251647014648204935/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=1251647014648204935' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1251647014648204935'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1251647014648204935'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/twitter4r-v020-timeline-api.html' title='Twitter4R v0.2.0: Timeline API'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-7551313027367640820</id><published>2007-07-08T14:48:00.000-05:00</published><updated>2007-07-08T21:32:06.246-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyonrails'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R v0.2.0: Configuration API</title><content type='html'>The &lt;b&gt;Configuration API&lt;/b&gt; allows applications using Twitter4R configure static connection options such as:
&lt;ul&gt;&lt;li&gt;connection protocol (e.g. HTTP and SSL)&lt;/li&gt;&lt;li&gt;host name and port (good for testing or if the Twitter REST API moved to a different subdomain)&lt;/li&gt;&lt;li&gt;proxy settings (i.e. proxy host, port, username and password)&lt;/li&gt;&lt;li&gt;user agent identifier&lt;/li&gt;&lt;li&gt;&lt;tt&gt;X-Twitter-Client*&lt;/tt&gt; HTTP headers for Twitter's internal usage analysis&lt;/li&gt;&lt;/ul&gt;
This API intentionally tries to look like the Rails initializer/configuration API:
&lt;pre&gt;&lt;code&gt;
require('rubygems')
gem('twitter4r', '0.2.0')
require('twitter')

Twitter::Client.configure do |conf|
  # We can set Twitter4R to use &lt;tt&gt;:ssl&lt;/tt&gt; or &lt;tt&gt;:http&lt;/tt&gt; to connect to the Twitter API.
  # Defaults to &lt;tt&gt;:ssl&lt;/tt&gt;
  conf.protocol = :ssl

  # We can set Twitter4R to use another host name (perhaps for internal
  # testing purposes).
  # Defaults to 'twitter.com'
  conf.host = 'twitter.com'

  # We can set Twitter4R to use another port (also for internal
  # testing purposes).
  # Defaults to 443
  conf.port = 443

  # We can set proxy information for Twitter4R
  # By default all following values are set to &lt;tt&gt;nil&lt;/tt&gt;.
  conf.proxy_host = 'myproxy.host'
  conf.proxy_port = 8080
  conf.proxy_user = 'myuser'
  conf.proxy_pass = 'mypass'

  # We can also change the User-Agent and X-Twitter-Client* HTTP headers
  conf.user_agent = 'MyAppAgentName'
  conf.application_name = 'MyAppName'
  conf.application_version = 'v1.5.6'
  conf.application_url = 'http://myapp.url'
end
&lt;/code&gt;&lt;/pre&gt;

If using &lt;a href="http://twitter4r.rubyforge.org"&gt;Twitter4R&lt;span style="display: none"&gt; (open source Ruby library for Twitter REST API)&lt;/span&gt;&lt;/a&gt; in a Rails application I would recommend adding the necessary Twitter4R configuration code in &lt;tt&gt;config/twitter.rb&lt;/tt&gt; then including it in the &lt;tt&gt;config/environment.rb&lt;/tt&gt; file to keep configurations separate.
&lt;h4&gt;Upgrading from 0.1.1&lt;/h4&gt;
In Twitter4R v0.1.1 you could configure protocol, server host, server port and proxy settings using the following code:
&lt;pre&gt;
Twitter::Client.conf(
  :ssl =&gt; true, 
  :port =&gt; 443, 
  :proxy_host =&gt; 'myproxy.host', 
  :proxy_port =&gt; 8080)
&lt;/pre&gt;
This API no longer exists, so if upgrading to Twitter4R 0.1.1 you will need to change the above to something like the following:
&lt;pre&gt;
Twitter::Client.configure do |conf|
  conf.protocol = :ssl
  conf.port = 443
  conf.proxy_host = 'myproxy.host'
  conf.proxy_port = 8080
end
&lt;/pre&gt;
This change shouldn't be too painful for people I hope.

Please refer to the &lt;a href="http://twitter4r.rubyforge.org/releases/v0.2.0/rdoc/"&gt;Twitter4R v0.2.0 RDoc&lt;/a&gt; for more information.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-7551313027367640820?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/7551313027367640820/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=7551313027367640820' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7551313027367640820'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7551313027367640820'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/twitter4r-v020-configuration-api.html' title='Twitter4R v0.2.0: Configuration API'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-5903984933549844748</id><published>2007-07-08T12:01:00.000-05:00</published><updated>2007-07-17T08:32:45.767-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='release'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R 0.2.0 Release</title><content type='html'>It's been almost two weeks, but Twitter4R had some major refactoring and updating between 0.1.1 and 0.2.0.  So I hope you will think the wait was worth it.  As usual you can grab the latest Ruby Gem (once all Rubyforge mirrors have synced) with: &lt;code&gt;sudo gem install twitter4r&lt;/code&gt;.

I am still in the process of pushing out the new Ruby Gem to Rubyforge and updating the website for the project, but the release is code complete.

Some changes are quite significant in some areas.  The basic idea of this release (0.2.0) was to cover the official published Twitter REST API 100% and remove some of the clutter the first two releases of Twitter4R introduced partly due to mirroring Twitter's inconsistent API.  In the same fashion as both previous releases of Twitter4R, version 0.2.0 has 100% C0 code coverage via RSpec specifications.

I separated out Twitter4R library into various API segments:
&lt;ul&gt;&lt;li&gt;&lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-configuration-api.html"&gt;Configuration&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-friendship-api.html"&gt;Friendship&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-messaging-api.html"&gt;Messaging&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-model-api.html"&gt;Model&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-status-api.html"&gt;Status&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-timeline-api.html"&gt;Timeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-user-api.html"&gt;User&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-extras-api.html"&gt;Extras&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;

I will write a blog entry as an introduction to each segment over the next couple of days.  In the meantime I will finish publishing the new website documentation and leave you with the &lt;a href="http://twitter4r.rubyforge.org/releases/v0.2.0/rdoc"&gt;RDoc&lt;/a&gt; for version 0.2.0.

&lt;b&gt;Updates:&lt;/b&gt;&lt;ul&gt;&lt;li&gt;New entry: &lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-configuration-api.html"&gt;Twitter4R v0.2.0: Configuration API&lt;/a&gt;&lt;/li&gt;&lt;li&gt;New entry: &lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-timeline-api.html"&gt;Twitter4R v0.2.0: Timeline API&lt;/a&gt;&lt;/li&gt;&lt;li&gt;New entry: &lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-status-api.html"&gt;Twitter4R v0.2.0: Status API&lt;/a&gt;&lt;/li&gt;&lt;li&gt;New entry: &lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-model-api.html"&gt;Twitter4R v0.2.0: Model API&lt;/a&gt;&lt;/li&gt;&lt;li&gt;New entry: &lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-messaging-api.html"&gt;Twitter4R v0.2.0: Messaging API&lt;/a&gt;&lt;/li&gt;&lt;li&gt;New entry: &lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-user-api.html"&gt;Twitter4R v0.2.0: User API&lt;/a&gt;&lt;/li&gt;&lt;li&gt;New entry: &lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-extras-api.html"&gt;Twitter4R v0.2.0: Extras API&lt;/a&gt;&lt;/li&gt;&lt;li&gt;New entry: &lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-v020-friendship-api.html"&gt;Twitter4R v0.2.0: Friendship API&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-5903984933549844748?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/5903984933549844748/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=5903984933549844748' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5903984933549844748'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5903984933549844748'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/twitter4r-020-release.html' title='Twitter4R 0.2.0 Release'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-4348463884518364819</id><published>2007-07-05T11:07:00.000-05:00</published><updated>2007-07-10T08:53:21.773-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='restful'/><category scheme='http://www.blogger.com/atom/ns#' term='REST'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='web services'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><title type='text'>Twitter API's unRESTfulness causing trouble</title><content type='html'>I am now designing my sixth (6th) set of web services on my new project.  The previous five (5) and this current venture were/are for the purpose of allowing partners and internal developers to integrate with a "platform" of some kind.  These clients varied from Fortune 500 companies, prestigious privately held firms and most recently a VC-funded startup.  

The previous two web service APIs were based on the REST "architecture" (yes, I know that word gets used too much, but I use it now for lack of a better word at this moment).  The previous REST API was in fact fully RESTful and my new software product design also has the aim of RESTfulness at all points.

During my travels meandering through the windy trails of designing REST APIs, I have realized just how important being truly RESTful is to REST APIs.  Yes, reread that sentence again. if it doesn't make sense I recommend reading &lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FRESTful-Web-Services-Leonard-Richardson%2Fdp%2F0596529260%3Fie%3DUTF8%26s%3Dbooks%26qid%3D1182785267%26sr%3D8-1&amp;tag=illinilistn09-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325"&gt;RESTful Web Services&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt;, which should clarify this distinction for you.

For me, the biggest plus of RESTful web services is that it promotes consistency at all levels.

Moving on...where was I going?  Ah, yes, Twitter...

The routes.rb for twitter.com seem to be a little misleading.  It seems a mix and match approach was taken to developing a "RESTful" API for twitter.com using Rails.  The current RESTful Rails convention is the following (in a nutshell):
&lt;ul&gt;&lt;li&gt;&lt;code&gt;GET /resources/:id.:format&lt;/code&gt; =&gt; get information about resource with specified :id&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /resources&lt;/code&gt; =&gt; create new resource using data given in request body&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PUT /resources/:id.:format&lt;/code&gt; =&gt; update resource with specified :id using data given in request body&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DELETE /resources/:id.:format&lt;/code&gt; =&gt; delete resource with specified :id and return resource in :format given&lt;/li&gt;
&lt;/ul&gt;
This is all great, however, in twitter.com it seems there was a previous ugly step-child of a "RESTful" design implemented before Rails 1.2 standardized on the above approach (just speculating here).  The evidence of this exists in multiple places in the Twitter API's inconsistency, however, one case in particular creates a defect in my view (and I am being kind by not considering all RESTful violations as defects, since the Twitter developers need to cater to the weekend PHP and VB.NET hackers that might not care about the beauty of REST's simple yet consistent design and purpose - I will give the Twitter developers the benefit of the doubt for this article):

In the users#show API there seems to be a conflict.  For example, &lt;code&gt;GET /users/show.json?id=mylogin&lt;/code&gt; will, in fact, return the user information for user with screen_name ‘show’ instead of ‘mylogin’, which is obviously not the intention of the above HTTP call, especially when &lt;tt&gt;/users/show&lt;/tt&gt; is an advertised way to access user information in the &lt;a href="http://groups.google.com/group/twitter-development-talk/web/api-documentation"&gt;official Twitter API docs&lt;/a&gt;.

Yes, I know I can call &lt;code&gt;GET /users/mylogin.json&lt;/code&gt; and be done with it, but the misleading defect still remains.  Interestingly, &lt;code&gt;GET /users/show?id=mylogin&lt;/code&gt; does yield the expected result assuming you have the &lt;tt&gt;Accept&lt;/tt&gt; HTTP header set to the appropriate mime-type (e.g. &lt;tt&gt;'text/x-json'&lt;/tt&gt;).

Screen names that cause potential conflicts (if using Rails defaults) are: create, destroy, update, new, show.  All of which exist at twitter.com.

My point here isn't to blast Twitter developers necessarily, simply to ask all developers (or if you must title yourself this way "architects") to consider the importance of creating consistent APIs, preferably based on open standards/recommendations like REST.  And most importantly, if a REST API isn't truly "RESTful", don't say it is!:)  You will get more respect for being honest from people that know what it really means.

&lt;b&gt;Related post:&lt;/b&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href="http://snakesgemscoffee.blogspot.com/2007/07/twitter4r-020-release.html"&gt;Twitter4R v0.2.0 Released&lt;/a&gt; - the ultimate open source library that provides Ruby bindings for 100% of the officially documented Twitter REST API.&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-4348463884518364819?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/4348463884518364819/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=4348463884518364819' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4348463884518364819'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4348463884518364819'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/twitter-apis-unrestfulness-causing.html' title='Twitter API&apos;s unRESTfulness causing trouble'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-8959100775577548678</id><published>2007-07-02T17:54:00.000-05:00</published><updated>2007-10-01T16:02:07.865-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rspec'/><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='autotest'/><category scheme='http://www.blogger.com/atom/ns#' term='gnome'/><title type='text'>Marrying Autotest with RSpec on Gnome</title><content type='html'>For those that don't know marrying Autotest with RSpec is a God send and I highly recommend it for pure Ruby as well as Rails application development.  

On the Mac OS X and KDE desktop environments there are builtin Autotest extensions that visually flag erroneous or successful RSpec runs with a standard desktop notification message.  

For Gnome users on Linux running Autotest with RSpec you may have seen a couple of &lt;tt&gt;${HOME}/.autotest&lt;/tt&gt; customizations out there that don't work with RSpec v1.x+.

Below is my &lt;tt&gt;${HOME}/.autotest&lt;/tt&gt; that works with RSpec 1.0.5:
&lt;pre&gt;&lt;code&gt;
require('autotest/redgreen')
require('autotest/timestamp')

module Autotest::GnomeNotify

  # Time notification will be displayed before disappearing automatically
  EXPIRATION_IN_SECONDS = 3
  ERROR_STOCK_ICON = "gtk-dialog-error"
  SUCCESS_STOCK_ICON = "gtk-dialog-info"

  RE_RSPEC_SUMMARY = Regexp.new(/(\d+) examples, (\d+) failure/)

  class &lt;&lt; self
    # Convenience method to send an error notification message
    #
    # [stock_icon]   Stock icon name of icon to display
    # [title]        Notification message title
    # [message]      Core message for the notification
    def notify(stock_icon, title, message)
      options = "-t #{EXPIRATION_IN_SECONDS * 2500} -i #{stock_icon}"
      system "notify-send #{options} '#{title}' '#{message}'"
    end

    def compose_message(at)
      specs, failures = 0, 0
      at.results.scan(RE_RSPEC_SUMMARY) do |s, f|
        specs = s.to_i
        failures = f.to_i
      end
      "#{specs} specs, #{failures} failures"
    end
  end

  Autotest.add_hook :red do |at|
    notify ERROR_STOCK_ICON, "Some specs failed.", compose_message(at)
  end

  Autotest.add_hook :green do |at|
    notify SUCCESS_STOCK_ICON, "All specs passed.  Have a beer!", compose_message(at)
  end
end
&lt;/code&gt;&lt;/pre&gt;
I assume there this will work with RSpec 1.x, but I have only tested with RSpec 1.0.5.

Enjoy!&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-8959100775577548678?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/8959100775577548678/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=8959100775577548678' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8959100775577548678'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8959100775577548678'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/marrying-autotest-with-rspec-on-gnome.html' title='Marrying Autotest with RSpec on Gnome'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-2454118493972635714</id><published>2007-07-01T16:51:00.000-05:00</published><updated>2007-07-02T15:54:15.443-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rspec'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='bdd'/><category scheme='http://www.blogger.com/atom/ns#' term='specifications'/><title type='text'>RSpec "should_yield"?</title><content type='html'>While writing some specifications for the 0.2.0 version of &lt;a href="http://twitter4r.rubyforge.org/"&gt;Twitter4R&lt;/a&gt; (soon to be released - honestly), I realized there was no clean way to do &lt;tt&gt;#should_yield&lt;/tt&gt;, so I thought I would offer my suggestion for the developers of RSpec to critique as appropriate.

Assume we have a method signature that takes a block and &lt;b&gt;should&lt;/b&gt; yield an object to the block given (if one is given).  Something like the following:
&lt;pre&gt;
class Client
  class &lt;&lt; self
    def configure(&amp;block)
      # implementation of Client.configure
    end
    # rest of class methods for Client class go here...
  end
  # rest of instance methods for Client class go here....
end
&lt;/pre&gt;
Where usage for the &lt;code&gt;Client.configure&lt;/code&gt; method should look something like the following:
&lt;pre&gt;
Client.configure do |conf|
  conf.proxy_host = 'myproxyhost'
  conf.proxy_port = 8080
  conf.proxy_user = 'me'
  conf.proxy_pass = 'mypass'
end
&lt;/pre&gt;
The object yielded to the block given is an instance of &lt;code&gt;Config&lt;/code&gt; which has settable attributes: &lt;code&gt;proxy_host&lt;/code&gt;, &lt;code&gt;proxy_port&lt;/code&gt;, &lt;code&gt;proxy_user&lt;/code&gt;, &lt;code&gt;proxy_pass&lt;/code&gt;.

The question is how do we write RSpec specifications for this?

First we need to define what we want to specify for the expected behavior.  My first attempt is to describe (in English) that:
&lt;ul&gt;&lt;li&gt;&lt;code&gt;Client.configure&lt;/code&gt; should&lt;ul&gt;&lt;li&gt;accept a block as argument&lt;/li&gt;&lt;li&gt;yield a &lt;code&gt;Config&lt;/code&gt; instance to the given block&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;
The way I did this in the &lt;a href="http://twitter4r.rubyforge.org"&gt;Twitter4R (open source Ruby library for the Twitter REST API)&lt;/a&gt; project specifications was something like the following:
&lt;pre&gt;
describe Client, ".configure" do
  before(:each) do
    @has_yielded = false
    @block = Proc.new do |conf|
      conf.is_a?(Config).should be_true
      @has_yielded = true
    end
  end

  it "should accept a block as argument" do
    lambda {
      Client.configure {}
    }.should_not raise_error
  end

  it "should yield a Config object to given block" do
    Client.configure(&amp;block)
    @has_yielded.should be_true
  end

  after(:each) do
    @has_yielded, @block = nil
  end
end
&lt;/pre&gt;
What I would really like to do something like the following instead:
&lt;pre&gt;
describe Client, ".configure" do
  it "should accept a block as argument" do
    lambda {
      Client.configure {}
    }.should_not raise_error
  end

  it "should yield a Config object to given block" do
    lambda {
      Client.configure(&amp;spec_block(1))
    }.should yield_with(Config)
  end
end
&lt;/pre&gt;
I haven't extended the &lt;code&gt;Proc&lt;/code&gt; class to this point, but if/when I do I will post it to this blog and perhaps also to the RSpec mailing list.

If you have suggestions on how to improve the more manual way (the first RSpec code listing above), please do let me know.  I have not yet played around with inheritable describe blocks yet, but I have hunch that would DRY up some of the code for if needed for many contexts.

Alternatively if you have suggestions on how to improve the second RSpec code listing (directly above) to be more clear, please do let me know that too.  I realize the developers of RSpec don't want to have too many &lt;code&gt;#should_XXX&lt;/code&gt; methods, so I am open to suggestions (since I also agree with that notion).

&lt;b&gt;UPDATE:&lt;/b&gt; I created a new feature request and dialog with RSpec developers at: &lt;a href="http://rubyforge.org/tracker/?func=detail&amp;atid=3152&amp;aid=11949&amp;group_id=797"&gt;[#11949]&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-2454118493972635714?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/2454118493972635714/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=2454118493972635714' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2454118493972635714'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2454118493972635714'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/07/rspec-shouldyield.html' title='RSpec &quot;should_yield&quot;?'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-6042358536594004522</id><published>2007-06-27T07:30:00.000-05:00</published><updated>2007-06-27T07:27:27.651-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='uk'/><category scheme='http://www.blogger.com/atom/ns#' term='tonyblair'/><category scheme='http://www.blogger.com/atom/ns#' term='england'/><category scheme='http://www.blogger.com/atom/ns#' term='tony blair'/><category scheme='http://www.blogger.com/atom/ns#' term='greatbritain'/><category scheme='http://www.blogger.com/atom/ns#' term='great britain'/><category scheme='http://www.blogger.com/atom/ns#' term='blair'/><category scheme='http://www.blogger.com/atom/ns#' term='britain'/><category scheme='http://www.blogger.com/atom/ns#' term='prime minister'/><title type='text'>Good Riddance to Bad Rubbish</title><content type='html'>While I usually do not express views about current affairs and political figures on this blog (except to highlight a point as applied to software), I couldn't let this beautiful day go by without a mention of the sheer delight I am experiencing being freed from the shackles and chains that was Tony Blair's premiership.  My only regret is this has come ten years too late.
&lt;img src="http://www.cartoonstock.com/newscartoons/cartoonists/kni/lowres/knin310l.jpg" width="355" height="400" alt="Tony Blair bitten by British bulldog"/&gt;&lt;br style="clear: both;"/&gt;
For those that do not know what our "fabulous" Tony has done domestically in UK, you should watch the British documentary &lt;a href="http://noliberties.com/"&gt;Taking Liberties&lt;/a&gt; released earlier this summer.  You can view the trailer below:
&lt;object width="425" height="350"&gt;&lt;param name="movie" value="http://www.youtube.com/v/bUsNQkV6o04"&gt;&lt;/param&gt;&lt;param name="wmode" value="transparent"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/bUsNQkV6o04" type="application/x-shockwave-flash" wmode="transparent" width="425" height="350"&gt;&lt;/embed&gt;&lt;/object&gt;
If you still want to know more about the police state in UK created by Blair (all in the convenient name of "terrorism"), just ask &lt;a href="http://news.bbc.co.uk/1/hi/world/americas/4726613.stm"&gt;Jean Charles de Menezes&lt;/a&gt;...oh wait, you can't.  This Brazilian man was shot dead by Blair's police state at the Stockwell Tube station on the London underground.  He was &lt;b&gt;NOT&lt;/b&gt; affiliated to the London bombings in 2005 or any other terrorist plot or organization.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-6042358536594004522?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/6042358536594004522/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=6042358536594004522' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6042358536594004522'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6042358536594004522'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/06/good-riddance-to-bad-rubbish.html' title='Good Riddance to Bad Rubbish'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-6477852132938137352</id><published>2007-06-25T12:36:00.000-05:00</published><updated>2007-06-26T09:12:37.498-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='release'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R 0.1.1 Released</title><content type='html'>&lt;a href="http://twitter4r.rubyforge.org"&gt;Twitter4R&lt;/a&gt;, the open source Ruby project for the Twitter REST API, version 0.1.1 has been released this afternoon as a Ruby Gem.  It may take a little time for the new Gem to appear on the Rubyforge gem servers, but once replicated to all mirrors you should be able to do the following:
&lt;code&gt;
require 'rubygems'
gem 'twitter4r', '0.1.1'
require 'twitter'

client = Twitter::Client.new(:login =&gt; 'mylogin', :password =&gt; 'mypassword')
timeline = client.public_timeline
timeline.each do |status|
  puts "#{status.user.screen_name}: #{status.message}"
end
&lt;/code&gt;

This will get you started.  Changes from 0.1.0 to 0.1.1 include:
&lt;ol&gt;&lt;li&gt;Addition of proxy support&lt;/li&gt;&lt;li&gt;Addition of SSL support&lt;/li&gt;&lt;li&gt;Migration from RSpec 0.8.2 to 1.0.x specifications&lt;/li&gt;&lt;/ol&gt;
For proxy support you can do the following:
&lt;code&gt;
require 'rubygems'
gem 'twitter4r', '0.1.1'
require 'twitter'

Twitter::Client.config(:proxy_host =&gt; 'myproxy.host', :proxy_port =&gt; 8080)
# continue using Twitter4R API as above.
&lt;/code&gt;
Or if you have a proxy server that requires authentication, you can do the following:
&lt;code&gt;
Twitter::Client.config(:proxy_host =&gt; 'myproxy.host', :proxy_port =&gt; 8080, :proxy_user =&gt; 'myproxyuser', :proxy_pass =&gt; 'myproxypass')
# continue using Twitter4R API as above.
&lt;/code&gt;

To force Twitter4R to use SSL, we would write the following configuration code before using the client API:
&lt;code&gt;
Twitter::Client.config(:ssl =&gt; true, :port =&gt; 443)
&lt;/code&gt;

Enjoy!&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-6477852132938137352?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/6477852132938137352/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=6477852132938137352' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6477852132938137352'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6477852132938137352'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/06/twitter4r-011-released.html' title='Twitter4R 0.1.1 Released'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-5659587045743141508</id><published>2007-06-25T09:14:00.002-05:00</published><updated>2007-07-08T12:01:16.101-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R Revamp</title><content type='html'>While nothing has yet to be release for Twitter4R since version 0.1.0, it has not be standing still.  A number of changes will be released within the next few business days.

Firstly, I will be releasing Twitter4R 0.1.1 with just two public API changes (and one internal change):
&lt;ul&gt;&lt;li&gt;[PUBLIC] SSL support&lt;/li&gt;&lt;li&gt;[PUBLIC] Proxy support&lt;/li&gt;&lt;li&gt;[INTERNAL] Migration from RSpec 0.8.2 to 1.0.x&lt;/li&gt;&lt;/ul&gt;
Secondly, I will also be releasing Twitter4R 0.2.0 very soon after 0.1.1, which will contain a number of API changes.  These changes are mostly documented in the comments of &lt;a href="http://retro.tautology.net/projects/twitter4r/ticket/6"&gt;ticket #6&lt;/a&gt; in Twitter4R's Retrospectiva project.  I plan writing much more RDoc documentation for 0.2.0 and subsequent 0.2.x releases.  Lack of documentation was a problem with the 0.1.0 release.

The reasoning for the quick succession of releases of Twitter4R is so that 0.1.x versions can have the same public API, whereas 0.2.x signifies a change in API to allow us to cover more of the Twitter REST API without cluttering the Ruby API too much.  

A major reason for the API revamp was to hide some of the very &lt;b&gt;unRESTful&lt;/b&gt; and asymmetric things the Twitter team have exposed in their public API.  If any of the Twitter team are reading this, I recommend &lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FRESTful-Web-Services-Leonard-Richardson%2Fdp%2F0596529260%3Fie%3DUTF8%26s%3Dbooks%26qid%3D1182785267%26sr%3D8-1&amp;tag=illinilistn09-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325"&gt;RESTful Web Services&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt;.

I admit to dropping the ball on the next release of Twitter4R gladly, because it is true.  I violated the first rule of &lt;b&gt;agile&lt;/b&gt; development: Do not bite off more than you can chew!

I apologize and will learn from it.  Thanks for your patience.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-5659587045743141508?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/5659587045743141508/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=5659587045743141508' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5659587045743141508'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5659587045743141508'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/06/twitter4r-revamp.html' title='Twitter4R Revamp'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-6179043190690106888</id><published>2007-06-16T19:15:00.000-05:00</published><updated>2007-06-25T16:34:46.303-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R API Changes</title><content type='html'>Twitter4R 0.1.1, will have three new features:
&lt;ul&gt;&lt;li&gt;Proxy support.  Thanks to Kaiichi Matsunaga for donating the code.&lt;/li&gt;&lt;li&gt;SSL support.  It will now be the default, but Twitter4R client can easily be configured to use HTTP instead&lt;/li&gt;&lt;li&gt;Major API refactoring such that Twitter4R feels more like Ruby and to obtain 100% programmable client coverage of the Twitter REST API.  Refer to &lt;a href="http://retro.tautology.net/projects/twitter4r/ticket/6#change-29"&gt;ticket #6&lt;/a&gt; for more information on this&lt;/li&gt;&lt;/ul&gt;

I'll be posting more on the changes and showing examples in later blog entries shortly after the release.

&lt;b&gt;Update:&lt;/b&gt; Refer to the following blog entries for updates to this:
&lt;ul&gt;&lt;li&gt;&lt;a href="http://snakesgemscoffee.blogspot.com/2007/06/twitter4r-revamp.html"&gt;Twitter4R Revamp&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://snakesgemscoffee.blogspot.com/2007/06/twitter4r-011-released.html"&gt;Twitter4R 0.1.1 Released&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-6179043190690106888?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/6179043190690106888/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=6179043190690106888' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6179043190690106888'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/6179043190690106888'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/06/twitter4r-011-api-changes.html' title='Twitter4R API Changes'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-7952727171762161030</id><published>2007-06-15T09:40:00.000-05:00</published><updated>2007-06-25T10:55:52.758-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='libertarian'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='propaganda'/><category scheme='http://www.blogger.com/atom/ns#' term='hitler'/><category scheme='http://www.blogger.com/atom/ns#' term='goebbels'/><category scheme='http://www.blogger.com/atom/ns#' term='joseph goebbels'/><category scheme='http://www.blogger.com/atom/ns#' term='liberty'/><title type='text'>The Java Dogma and Downfall</title><content type='html'>Despite applying his talents towards a despicable cause, Joseph Goebbels (Hitler's propaganda minister from 1933) had amazing insights into public behavior and described his most famous observations:
&lt;blockquote&gt;
If you tell a lie big enough and keep repeating it, people will eventually come to believe it. The lie can be maintained only for such time as the State can shield the people from the political, economic and/or military consequences of the lie. It thus becomes vitally important for the State to use all of its powers to repress dissent, for the truth is the mortal enemy of the lie, and thus by extension, the truth is the greatest enemy of the State.
&lt;/blockquote&gt;

Using Goebbels' wisdom Java&amp;trade; successfully launched their propaganda campaign initially against C/C++ with the rhetoric that Java guards against programming errors unlike its predecessor.  Yes, Java does lean very heavily toward a dictatorship style of governing.  This is without doubt.  But does this translate to guarding against developers' own stupidity?

When moving from C++ and Perl to Java back in 1997 I appreciated this dictatorship-style of governing very much.  It removed *many* cycles of thought from development and I became lazy.  Earlier in this new millennium I moved to a more democratic language called Python, where I enjoyed this new found freedom.  Soon after the Python doctrine embedded deep-rooted values in me, I discovered the [almost] Libertarian utopia that is Ruby (though not perfect, thus the &lt;b&gt;[almost]&lt;/b&gt;).

&lt;b&gt;Where am I going with this?  Excellent question.  Where was I?  Ah yes, Java&amp;trade;, propaganda, Goebbels, etc....&lt;/b&gt;

Now that Java&amp;trade; has new mortal enemies - Ruby, Python and other non-static languages that provide significant productivity gains as compared with large bloated development in Java - the Java&amp;trade; machine is using the same rhetoric.  Is it working?  In short, for the software Lemmings that don't think, it is working well.  Due in large part to the &lt;b&gt;Java Dogma&lt;/b&gt;.

In truth I find the liberty offered by these non-static languages (yes, Python that still includes you, weird *strongly*-typed cousin) provide a much greater guard against programmer stupidity than the forced dictatorship style of Java&amp;trade;.  This is because the developer and development team must take full ownership of the responsibility for their work and thus disciplined, smart and agile developers tend to flock to these languages, leaving the lazy developers only looking for a nice pay check for using the right buzzwords at the right time in the Java world.  

Java now faces a big problem: how to not let this lazy and uninspiring critical mass not weigh it's reputation down too much?  This is an excellent question and one I fear Ruby will need to find an answer to soon, if PHP hackers with no real software development understanding keep flocking to Ruby through Ruby on Rails.  

The growing popularity of Ruby due to Rails will bring the current community (you and I) problems that we will need to start thinking about now if we are to survive the consequences.  Or we will suffer a very similar fate to Java, which would be very sad indeed.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-7952727171762161030?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/7952727171762161030/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=7952727171762161030' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7952727171762161030'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7952727171762161030'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/06/java-dogma-and-downfall.html' title='The Java Dogma and Downfall'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-9187327156724228848</id><published>2007-06-08T08:08:00.000-05:00</published><updated>2007-06-08T14:56:44.516-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='cowboy'/><category scheme='http://www.blogger.com/atom/ns#' term='monkey'/><category scheme='http://www.blogger.com/atom/ns#' term='cowboy monkey'/><title type='text'>Are you a Cowboy Monkey?</title><content type='html'>Everyone of us has probably met one.  You may have worked for one as a subordinate.  You may even be one.  Though many people do not know what a &lt;b&gt;Cowboy Monkey&lt;/b&gt; is.

In a nutshell a &lt;b&gt;Cowboy Monkey&lt;/b&gt; is &lt;b&gt;that&lt;/b&gt; person on your team that jumps in to save the day with a hack.  They are the ones that repeatedly put out the same fires instead of making sure they don't start them again.  A &lt;b&gt;Cowboy Monkey&lt;/b&gt; can't be bothered to take 5 minutes a day to commit code changes into a SCM and resync their working copy, but spends half the day in meetings not achieving anything.  They are generally horrible dates because they spend all dinner talking about "quick and dirty" solutions.

Generally a &lt;b&gt;Cowboy Monkey&lt;/b&gt;:
&lt;ul&gt;
  &lt;li&gt;does not believe in any type of &lt;acronym title="Source Control Management"&gt;SCM&lt;/acronym&gt; system.&lt;/li&gt;
  &lt;li&gt;does not think about the longer-term consequences of their actions only the immediate effects.&lt;/li&gt;
  &lt;li&gt;does not believe in unit, functional, integration, performance or system testing or specifications of any kind.&lt;/li&gt;
  &lt;li&gt;prefers to test in production rather than create a whole new staging environment.&lt;/li&gt;
  &lt;li&gt;has not heard of best practices.&lt;/li&gt;
  &lt;li&gt;has a tendency to build silo systems.&lt;/li&gt;
  &lt;li&gt;has a total disregard for the software architecture of a project.&lt;/li&gt;
  &lt;li&gt;usually has no real understanding of design patterns of any kind.&lt;/li&gt;
  &lt;li&gt;thinks PHP is so good that it can solve the war in Iraq.&lt;/li&gt;
  &lt;li&gt;gives smiles and nods but delivers very few of the promises they make and almost nothing on time.&lt;/li&gt;
&lt;/ul&gt;
If you nodded at least 5 times in agreement with any of the statements above, you might be a full-fledged &lt;b&gt;Cowboy Monkey&lt;/b&gt;.  Do not fear, you are amongst friends and we will help you overcome this disorder.

If you are not one, how many &lt;b&gt;Cowboy Monkey's&lt;/b&gt; have you worked with thus far?  

Please feel free to share your (anonymous please) stories of the &lt;b&gt;Cowboy Monkey's&lt;/b&gt; you've met on the job in comments.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-9187327156724228848?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/9187327156724228848/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=9187327156724228848' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/9187327156724228848'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/9187327156724228848'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/06/are-you-cowboy-monkey.html' title='Are you a Cowboy Monkey?'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-5575708973350802682</id><published>2007-06-04T11:37:00.000-05:00</published><updated>2007-06-04T15:49:35.616-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='enumerations'/><category scheme='http://www.blogger.com/atom/ns#' term='dry'/><category scheme='http://www.blogger.com/atom/ns#' term='higher order messaging'/><category scheme='http://www.blogger.com/atom/ns#' term='enumerable'/><title type='text'>Higher Order Messaging</title><content type='html'>In response to a devChix blog entry called &lt;a href="http://www.devchix.com/2007/05/25/ruby-dry-up-your-enumerations/"&gt;RUBY: DRY up your Enumerations&lt;/a&gt;, I thought I would offer my alternative to the suggested notion of higher order messaging below:

&lt;pre&gt;
# In reference to http://www.devchix.com/2007/05/25/ruby-dry-up-your-enumerations/
# Examples by Susan Potter [http://susanpotter.net]

# Framework code...

class ProxyBase
  attr_accessor :collection
  
  def initialize(collection)
    @collection = collection
  end
end

class AnyProxy &lt; ProxyBase
  def method_missing(sym, *args)
    @collection.each do |item|
      return true if item.send(sym, *args)
    end
  end
end

class AllProxy &lt; ProxyBase
  def method_missing(sym, *args)
    @collection.each do |item|
      return false unless item.send(sym, *args)
    end
  end
end

module LinkingVerbMixin
  def self.included(base)
    base.extend(ClassMethods)
  end
  
  module ClassMethods
    def any(collection)
      AnyProxy.new(collection)
    end
    
    def all(collection)
      AllProxy.new(collection)
    end
  end
end

class Are
  include LinkingVerbMixin
end

class Do
  include LinkingVerbMixin
end

# Application code...

class Stooge
  attr_accessor :name, :bald
  
  def initialize(name, bald)
    @name = name
    @bald = bald
  end
  
  def bald?
    @bald
  end
end

mo = Stooge.new("Mo", true)
larry = Stooge.new("Larry", false)
curly = Stooge.new("Curly", false)
stooges = [mo, larry, curly]

Are.all(stooges).bald?
Are.any(stooges).bald?

# This can be extended further to do stuff like Do.all(stooges).have(:name, :eql?, "Mo"), etc.
&lt;/pre&gt;
Or see &lt;a href="http://pastie.caboo.se/67657"&gt;the pastie&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-5575708973350802682?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/5575708973350802682/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=5575708973350802682' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5575708973350802682'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/5575708973350802682'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/06/higher-order-messaging.html' title='Higher Order Messaging'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-3960542430585705791</id><published>2007-05-19T15:32:00.000-05:00</published><updated>2007-05-19T15:47:15.615-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rspec'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='release'/><category scheme='http://www.blogger.com/atom/ns#' term='bdd'/><title type='text'>RSpec 1.0.0 Finally Here!</title><content type='html'>Thanks to the RSpec team for finally delivering &lt;tt&gt;RSpec&lt;/tt&gt;, which I have been excited about for quite some time.

Having about 5 projects (2 pure Ruby, 3 Rails) using RSpec 0.8.2 and 2 other projects still on (0.7.5) I was a little worried about migrating to the new RSpec 1.0.0 compliant API, which is a fair bit different than some of what was allowed in 0.8.2 and prior.

However, the RSpec developers in all their wisdom foresaw this problem and wrote &lt;tt&gt;spec_translate&lt;/tt&gt; where you simply give it the from directory and to directory in that order on the command line.  Now it doesn't do everything, so don't expect too much, but it will convert all the &lt;tt&gt;context&lt;/tt&gt;, &lt;tt&gt;setup&lt;/tt&gt;, &lt;tt&gt;specify&lt;/tt&gt; and &lt;tt&gt;teardown&lt;/tt&gt; blocks to &lt;tt&gt;describe&lt;/tt&gt;, &lt;tt&gt;before&lt;/tt&gt;, &lt;tt&gt;it&lt;/tt&gt; and &lt;tt&gt;after&lt;/tt&gt; blocks respectively with very little fanfare or effort required from us.

The gotchas for those of us that ignored the Test::Unit Cheat Sheet on the website for recommended RSpec usage (I am going to own up to it, yes):
&lt;ol&gt;&lt;li&gt;&lt;code&gt;.should.eql expected_value&lt;/code&gt; becomes &lt;code&gt;.should eql(expected_value)&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;.should.not...&lt;/code&gt; becomes &lt;code&gt;.should not...&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;.should.be expected_result&lt;/code&gt; becomes &lt;code&gt;.should be(expected_result)&lt;/code&gt;&lt;/li&gt;&lt;/ol&gt;
Let me know if you find different varieties of the above API differences.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-3960542430585705791?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/3960542430585705791/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=3960542430585705791' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3960542430585705791'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3960542430585705791'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/05/rspec-100-finally-here.html' title='RSpec 1.0.0 Finally Here!'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-195033078086851700</id><published>2007-05-17T20:06:00.000-05:00</published><updated>2007-05-17T20:38:38.965-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='project management'/><category scheme='http://www.blogger.com/atom/ns#' term='pig'/><category scheme='http://www.blogger.com/atom/ns#' term='chicken'/><category scheme='http://www.blogger.com/atom/ns#' term='scrum'/><title type='text'>Are you a Pig or a Chicken?</title><content type='html'>Grey's Anatomy fans amongst us may remember an episode last season (season two) where a patient's boyfriend introduced us to the idea of whether you are a Pig or a Chicken in a relationship.  

The idea is that when you have a nice greasy breakfast of bacon and eggs, you have the Pig who is completely committed to delivering the breakfast and the Chicken who is only involved in contributing to the breakfast.  So in the Grey's Anatomy episode the boyfriend was trying to be a Pig in his relationship and be fully committed to his heavily pregnant girlfriend.

For me this wasn't a new concept, having read about Scrum as a software project management practice or adaptive process (if we are really forced to use the word "process") over the last 3 years.

Now in Scrum the idea is the same, except applied to the software project not a relationship between two people.  The Pigs are generally the people who are 100% committed to the project/product (e.g. Developers, DBAs, BAs, QAs, etc.) and the Chickens are the people that have a stake in the project but only contribute to the project.  They are not 100% committed (e.g. business stakeholders, management, etc.) just involved in the project.

Are you a Pig or a Chicken?

&lt;a href="http://www.implementingscrum.com/images/070212-scrumtoon.jpg"&gt;&lt;img src="http://www.implementingscrum.com/images/070212-scrumtoon.jpg" width="375" heigh="135" border="0" alt="Pig and Chicken cartoon"/&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-195033078086851700?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/195033078086851700/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=195033078086851700' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/195033078086851700'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/195033078086851700'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/05/are-you-pig-or-chicken.html' title='Are you a Pig or a Chicken?'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-1947382562176358636</id><published>2007-05-14T12:48:00.000-05:00</published><updated>2007-05-14T13:06:46.154-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='=='/><category scheme='http://www.blogger.com/atom/ns#' term='eql?'/><category scheme='http://www.blogger.com/atom/ns#' term='==='/><category scheme='http://www.blogger.com/atom/ns#' term='equality'/><category scheme='http://www.blogger.com/atom/ns#' term='equal?'/><title type='text'>Ruby Equality</title><content type='html'>Since two people asked me in the space of 5 days about this, I thought I'd post a quick demystification of Ruby's "equality" facilities.  Namely &lt;code&gt;equal?&lt;/code&gt;, &lt;code&gt;==&lt;/code&gt;, &lt;code&gt;eql?&lt;/code&gt; and &lt;code&gt;===&lt;/code&gt;.
&lt;ol&gt;
  &lt;li&gt;&lt;code&gt;equal?&lt;/code&gt; - always (or should be) purely identity equality, and *should never* be overridden by subclasses&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;==&lt;/code&gt; - by default is object identity equality, but *can* be overridden by subclasses&lt;/li&gt;
  &lt;li&gt;&lt;codee&gt;eql?&lt;/code&gt; - is object value equality and *should* be overridden by subclasses&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;===&lt;/code&gt; - by default same as #==.  It is overridden by subclasses that need a "case" equality defined to make sense when used in the &lt;code&gt;case&lt;/code&gt; statements in a &lt;code&gt;when&lt;/code&gt; construct.
&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-1947382562176358636?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/1947382562176358636/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=1947382562176358636' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1947382562176358636'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1947382562176358636'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/05/ruby-equality.html' title='Ruby Equality'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-8545667116069883712</id><published>2007-05-08T23:10:00.000-05:00</published><updated>2007-05-17T16:42:33.054-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby on rails'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='xml'/><category scheme='http://www.blogger.com/atom/ns#' term='activerecord'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyonrails'/><category scheme='http://www.blogger.com/atom/ns#' term='models'/><category scheme='http://www.blogger.com/atom/ns#' term='model'/><title type='text'>ActiveRecord::Base#to_xml woes</title><content type='html'>For us Railies that use namespaced models inside Ruby modules and are relying on ActiveRecord::Base#to_xml, do not fret any longer.

To reproduce the problem you just need to do something like:
&lt;code&gt;
users = Legacy::User.find(:all) #=&gt; []
users.to_xml #=&gt; &amp;lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&amp;gt;&amp;lt;legacy/users&amp;gt;&amp;lt;/legacy/users&amp;gt;
&lt;/code&gt;
The XML produced by &lt;code&gt;#to_xml&lt;/code&gt; is NOT valid XML for this case.

It's been a small thorn in my side for a week or so now and I've been living with it by adding the following to a file that is eventually/implicitly required by environment.rb:
&lt;pre&gt;
# Patch for Rails #8305 that I submitted on 2007-05-09
# We'll need to keep an eye out upon next Rails Gem upgrade whether or not this 
# fix has been applied to remove this or not.
require 'active_record'
require 'active_support'

class ActiveRecord::Base
  alias :old_to_xml :to_xml
  def to_xml(options = {}, &amp;block)
    options[:root] ||= self.class.name.tableize.singularize.gsub(/\//, ':') # for models that are namespaced in Ruby module
    old_to_xml(options, &amp;block)
  end
end

module ContainerPatchMixin
  def self.included(base)
    base.class_eval %{
      alias :old_to_xml :to_xml
    }
    base.send(:include, InstanceMethods)
  end
  
  module InstanceMethods
    def to_xml(options = {})
      contained_class = self.first.class.name
      options[:root] ||= contained_class.tableize.gsub(/\//, ':') # for models that are namespaced in Ruby module
      options[:children] ||= contained_class.tableize.singularize.gsub(/\//, ':') # for models that are namespaced in Ruby module
      old_to_xml(options)
    end
  end
end

class Array
  include ContainerPatchMixin
end
&lt;/pre&gt;
Now what is produced is something more along the lines of:
&lt;code&gt;
&amp;lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&amp;gt;&amp;lt;legacy:users&amp;gt;&amp;lt;/legacy:users&amp;gt;
&lt;/code&gt;
Which is valid XML.

Today I finally reported this edge case defect on dev.rubyonrails.org (&lt;a href="http://dev.rubyonrails.org/ticket/8305"&gt;#8305&lt;/a&gt;) as I hadn't seen anyone else report it yet.

Thankfully this is Ruby and this is all possible while the Rails Core team determine if my not so elegant fix is worthy to be in the next Rails version or not.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-8545667116069883712?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/8545667116069883712/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=8545667116069883712' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8545667116069883712'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8545667116069883712'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/05/activerecordbasetoxml-woes.html' title='ActiveRecord::Base#to_xml woes'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-8998324401135725029</id><published>2007-05-06T23:55:00.000-05:00</published><updated>2007-05-07T12:06:47.400-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyforge'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter4r'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Twitter4R 0.1.0 Released</title><content type='html'>This weekend I had a couple of hours to work on Twitter4R (finally).  Today (Sunday) saw the release of Twitter4R 0.1.0 on Rubyforge as a Gem.  It may take the Rubyforge gem mirrors a few hours to copy it over, but by Monday morning (US time) you should be able to do the following:
&lt;code&gt;
$ sudo gem install twitter4r
$ irb
irb(main):001:0&gt; require 'twitter'
=&gt; true
irb(main):002:0&gt; client = Twitter::Client.new :login =&gt;
'screen_name', :password =&gt; 'password'
=&gt; #&lt;Twitter::Client:0xb7a4ca74 @login="screen_name",
@password="password"&gt;
irb(main):003:0&gt; statuses = client.public_timeline
=&gt; [#&lt;Twitter::Status:0xb7a23764 ...&gt;, ...]
irb(main):004:0&gt; statuses.each do |status|
irb(main):005:1*   puts status.text, status.user.screen_name
irb(main):006:1&gt; end
the puts output would go here....
irb(main):007:0&gt; client.update 'Pure Ruby API for Twitter:
http://twitter4r.rubyforge.org'
=&gt; nil
&lt;/code&gt;
It supports utf8 strings so this should not be a worry when querying the public timeline!

See &lt;a href="http://twitter4r.rubyforge.org"&gt;Twitter4R Rubyforge Website&lt;/a&gt; for more details or the &lt;a href="http://retro.tautology.net/projects/twitter4r/blog"&gt;Twitter4R Project Blog&lt;/a&gt;.

Enjoy!&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-8998324401135725029?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://twitter4r.rubyforge.org' title='Twitter4R 0.1.0 Released'/><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/8998324401135725029/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=8998324401135725029' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8998324401135725029'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8998324401135725029'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/05/twitter4r-010-released.html' title='Twitter4R 0.1.0 Released'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-2102195044418167288</id><published>2007-05-03T11:29:00.000-05:00</published><updated>2007-05-03T11:34:25.232-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quote'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>Favorite Quote of the Week</title><content type='html'>A friend and old co-worker, who wants to be referred to as Bo "Fucking" Conroy, just sent me this on IM:
&lt;blockquote&gt;
yeah, php seems like something targeted at 16-22 year old porn site developers.
&lt;/blockquote&gt;
Thanks Bo!  Only you can verbalize these delicate things so precisely.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-2102195044418167288?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/2102195044418167288/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=2102195044418167288' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2102195044418167288'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2102195044418167288'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/05/favorite-quote-of-week.html' title='Favorite Quote of the Week'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-4728646173366110370</id><published>2007-05-03T09:34:00.005-05:00</published><updated>2010-08-16T12:24:13.105-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='performance'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby on rails'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='scalability'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyonrails'/><title type='text'>Rails Application Scaling Tips</title><content type='html'>This will be a howto on quickly enhancing performance, throughput or uptime of a Rails application: &lt;ul&gt;&lt;li&gt;Use &lt;a href="http://www.danga.com/memcached/"&gt;memcached&lt;/a&gt; for sessions&lt;/li&gt;
&lt;li&gt;Move static assets to separate lighttpd or httpd web server optimized for just static content&lt;/li&gt;
&lt;li&gt;Use more efficient templating engine such as &lt;a href="http://www.kuwata-lab.com/erubis/"&gt;erubis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Use memcached for other high-frequency read AR models&lt;/li&gt;
&lt;li&gt;Configure Rails to cache pages, actions and fragments where relevant&lt;/li&gt;
&lt;li&gt;Use BackgrounDRB to delegate longer running tasks such as sending emails, etc. &lt;em&gt;Note:&lt;/em&gt; This is out of date right now, I might recommend Cloud Crowd or other plugins at this point for background jobs or even using message oriented middleware (MOMs) like AMQP services like RabbitMQ.&lt;/li&gt;
&lt;li&gt;If your application requires a large number of concurrent large file uploads (say you are building the next YouTube) you should consider utilizing &lt;a href="http://merb.rubyforge.org/files/README.html"&gt;Merb&lt;/a&gt;.  &lt;em&gt;Note:&lt;/em&gt; this is also out of date since I wrote it in May 2007.&lt;/li&gt;
&lt;/ul&gt;At this point you just need to focus on the standard scaling concerns: &lt;ul&gt;&lt;li&gt;database server tweaking (including database caching)&lt;/li&gt;
&lt;li&gt;removing unnecessary file system I/O&lt;/li&gt;
&lt;li&gt;removing unnecessary or consolidating network I/O&lt;/li&gt;
&lt;li&gt;Removing unnecessary computation or caching computational results where necessary&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;h4&gt;Update (2010-08-15)&lt;/h4&gt;&lt;p&gt;The following tools/plugins can be used to optimize the how many and how much data is passed by the SQL queries generated by ActiveRecord:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a target="_blank" href="http://github.com/flyerhzm/bullet"&gt;Bullet&lt;/a&gt; - helps you optimize your SQL queries by notifying you when not eager loading or using counter caches.&lt;/li&gt;
&lt;li&gt;&lt;a target="_blank" href="http://github.com/methodmissing/scrooge"&gt;Scrooge&lt;/a&gt; - SQL query optimizer which can reduce the amount of data getting sent from your database to your Rails application.&lt;/li&gt;
&lt;li&gt;&lt;a target="_blank" href="http://github.com/brynary/rack-bug"&gt;Rack-bug&lt;/a&gt; - reports stats about each request in development mode so you can see how many queries are being executed on each request (and a number of other useful request stats).&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;p&gt;For identifying memory bloat or leaks in the applications the following might be useful (when we have a need for such tools):&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a target="_blank" href="http://github.com/binarylogic/memorylogic"&gt;Memorylogic&lt;/a&gt; - logging memory usage.&lt;/li&gt;
&lt;li&gt;&lt;a target="_blank" href="http://github.com/noahd1/oink"&gt;Oink&lt;/a&gt; - for easily finding actions that significantly increase VM heap size.&lt;/li&gt;
&lt;li&gt;&lt;a target="_blank" href="http://github.com/fauna/bleak_house"&gt;BleakHouse&lt;/a&gt; - used this before for finding memory leaks in a Rails 1.2.3 app a couple of years back.  I believe it is working against Rails 2.3.4 apps now too.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;p&gt;Also nesting includes on eager loading in ActiveRecord used to have memory leak implications.  I wrote about my experience in 2007 here: &lt;a href="http://geek.susanpotter.net/2007/01/twas-night-before-launch.html" target="_blank"&gt;Twas the night before launch&lt;/a&gt;.&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;The memory leak from the &lt;a target="_blank" href="http://geek.susanpotter.net/2007/01/twas-night-before-launch.html"&gt;Twas the night before launch&lt;/a&gt; blog post may have been fixed in subsequent releases of ActiveRecord since July 2007 when I encountered it, in the case of nesting includes for eager loading in all one query, but the morale of the story is never assume that just because you are eager loading all in one query there aren't going to be other implications.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-4728646173366110370?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/4728646173366110370/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=4728646173366110370' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4728646173366110370'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4728646173366110370'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/05/rails-application-scaling-tips.html' title='Rails Application Scaling Tips'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-8215698814746425332</id><published>2007-04-30T20:59:00.000-05:00</published><updated>2007-05-04T16:52:30.012-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gedit'/><category scheme='http://www.blogger.com/atom/ns#' term='editor'/><category scheme='http://www.blogger.com/atom/ns#' term='configuration'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='gnomevfs'/><category scheme='http://www.blogger.com/atom/ns#' term='config'/><category scheme='http://www.blogger.com/atom/ns#' term='gnome'/><title type='text'>Registering Rails File Extensions in GEdit</title><content type='html'>I just wanted to slap a quick howto for a couple of my professional and open source associates to get going with GEdit quickly while developing Ruby on Rails applications.

GEdit uses Gnome's VFS for mime-type determination.  To register new file mime-types for Gnome's VF do the following:
&lt;ol&gt;&lt;li&gt;Create XML file with the following contents:
&lt;pre&gt;
&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;
&amp;lt;mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info"&amp;gt;
  &amp;lt;mime-type type="application/x-ruby"&amp;gt;
    &amp;lt;alias type="application/ruby" /&amp;gt;
    &amp;lt;comment xml:lang="en"&amp;gt;Ruby Rake Tasks&amp;lt;/comment&amp;gt;
    &amp;lt;glob pattern="*.rake"/&amp;gt;
  &amp;lt;/mime-type&amp;gt;
  &amp;lt;mime-type type="application/x-ruby"&amp;gt;
    &amp;lt;comment xml:lang="en"&amp;gt;Rakefile&amp;lt;/comment&amp;gt
    &amp;lt;glob pattern="Rakefile"/&amp;gt;
  &amp;lt;/mime-type&amp;gt;
  &amp;lt;mime-type type="text/html"&amp;gt;
    &amp;lt;comment xml:lang="en"&amp;gt;eRB HTML Markup&amp;lt;/comment&amp;gt;
    &amp;lt;glob pattern="*.rhtml"/&amp;gt;
  &amp;lt;/mime-type&amp;gt;
  &amp;lt;mime-type type="application/x-javascript"&amp;gt;
    &amp;lt;comment xml:lang="en"&amp;gt;eRB JavaScript&amp;lt;/comment&amp;gt;
    &amp;lt;glob pattern="*.rjs"/&amp;gt;
  &amp;lt;/mime-type&amp;gt;
  &amp;lt;mime-type type="text/xml"&amp;gt;
    &amp;lt;comment xml:lang="en"&amp;gt;eRB XML&amp;lt;/comment&amp;gt;
    &amp;lt;glob pattern="*.rxml"/&amp;gt;
  &amp;lt;/mime-type&amp;gt;
  &amp;lt;mime-type type="text/x-yaml"&amp;gt;
    &amp;lt;comment xml:lang="en"&amp;gt;YAML&amp;lt;/comment&amp;gt;
    &amp;lt;glob pattern="*.yml"/&amp;gt;
  &amp;lt;/mime-type&amp;gt;
&amp;lt;/mime-info&amp;gt;
&lt;/pre&gt;&lt;li&gt;Copy file to &lt;code&gt;/usr/share/mime/packages/rails.xml&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Execute &lt;code&gt;sudo update-mime-database /usr/share/mime&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Enjoy!&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-8215698814746425332?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/8215698814746425332/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=8215698814746425332' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8215698814746425332'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8215698814746425332'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/04/registering-rails-file-extensions-in.html' title='Registering Rails File Extensions in GEdit'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-4181034311415156504</id><published>2007-04-20T11:21:00.000-05:00</published><updated>2007-04-20T11:22:32.717-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='commercial'/><category scheme='http://www.blogger.com/atom/ns#' term='oss'/><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='windows'/><category scheme='http://www.blogger.com/atom/ns#' term='mac'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>Is Linux Now Cool?</title><content type='html'>&lt;object width="425" height="350"&gt;&lt;param name="movie" value="http://www.youtube.com/v/7eTguZ5OzJ4"&gt;&lt;/param&gt;&lt;param name="wmode" value="transparent"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/7eTguZ5OzJ4" type="application/x-shockwave-flash" wmode="transparent" width="425" height="350"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-4181034311415156504?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/4181034311415156504/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=4181034311415156504' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4181034311415156504'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/4181034311415156504'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/04/is-linux-now-cool.html' title='Is Linux Now Cool?'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-3167353270369933165</id><published>2007-04-19T15:42:00.000-05:00</published><updated>2007-04-19T16:29:35.011-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='inject'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby idioms'/><category scheme='http://www.blogger.com/atom/ns#' term='mathematics'/><category scheme='http://www.blogger.com/atom/ns#' term='enumerable'/><category scheme='http://www.blogger.com/atom/ns#' term='math'/><title type='text'>Injecting Understanding</title><content type='html'>Over the past 4 weeks numerous people (Java heads trying out Ruby for the first time, or even coding neophytes that are jumping on board the Ruby train) have asked me about the &lt;code&gt;.inject&lt;/code&gt; method.  So I will try to demystify a method that (to me) epitomizes Ruby, yet baffles many that first encounter it.

First, let's start with the basic concept.  Here is some code you might find around in Ruby examples, etc., to demonstrate the purpose of &lt;code&gt;inject&lt;/code&gt;:
&lt;pre&gt;
(1..10).inject(0) { |cumulative_sum, item| cumulative_sum + item } #=&gt; 55
&lt;/pre&gt;
Let's dissect: 
&lt;ol&gt;&lt;li&gt;&lt;code&gt;(1..10)&lt;/code&gt; is an inclusive &lt;code&gt;Range&lt;/code&gt; (if you don't know what a &lt;code&gt;Range&lt;/code&gt; is in Ruby...&lt;a href="http://www.ruby-doc.org/core/classes/Range.html"&gt;better read up here&lt;/a&gt;, as it is out of scope for this article) from 1 to 10 (inclusive).&lt;/li&gt;
&lt;li&gt;Then because &lt;code&gt;Range&lt;/code&gt; is &lt;code&gt;Enumerable&lt;/code&gt; (again if you don't know what &lt;code&gt;Enumerable&lt;/code&gt; in Ruby means...&lt;a href="http://www.ruby-doc.org/core/classes/Enumerable.html"&gt;read up about it here&lt;/a&gt;, again out of scope of this posting) it has &lt;code&gt;inject&lt;/code&gt; available, so it bears remembering that it isn't just Arrays &lt;code&gt;inject&lt;/code&gt; will work on.&lt;/li&gt;
&lt;li&gt;We initially pass in 0 (yes, that's zero) as the argument to inject, which tells inject to start the "balance" at 0 (yes, zero again).  Then it iterates through all the elements in the Range from 1 to 10 (inclusively remember because we used two dots not three) passing in the cumulative sum (or balance) and the current item into the block you define.  The block we defined above is an expression that evaluates to an integer in this case.&lt;/li&gt;
&lt;li&gt;The result of this expression is now set to the cumulative_sum (or "balance") carried forward into the next iteration.  So as we should have all learned in school Mathematics when getting to summation series', the cumulative sum of 1 through 10 is 55 (this is the result we get from inject from the above code - and not by accident either - this is exactly what &lt;code&gt;inject&lt;/code&gt; is all about).&lt;/li&gt;
&lt;/ol&gt;
You might also remember (or perhaps it is just sad Math geeks like myself that remember this stuff) that the sum of any set of consecutive numbers (starting from 1) is defined the by formula: &lt;code&gt;(N*(N + 1))/2&lt;/code&gt;, where N is the largest of all consecutive numbers in the set.  With this knowledge we can sanity check (for our own understanding) whether it does what you should expect it to do or if it was just coincidence above:
&lt;code&gt;
def sum(n)
 n*(n+1)/2
end

(1..9).inject(0) { |cumulative_sum, item| cumulative_sum + item } #=&gt; 45
sum(9) #=&gt; 45

(1..100).inject(0) { |cumulative_sum, item| cumulative_sum + item } #=&gt; 5050
sum(100) #=&gt; 100

(1..7890).inject(0) { |cumulative_sum, item| cumulative_sum + item } #=&gt; 31129995
sum(7890) #=&gt; 31129995
&lt;/code&gt;
See told you it wasn't just coincidence - or what everything above a coincidence? Hmmmm..let's not go there right now!:).

I'll bet you could very easily figure out the more general formula for any sum of consecutive positive integers not necessarily starting at 1 (yes, that is one).  At least that is what my Mathematics teacher would ask us to do for homework (really not very much homework to be honest - I had a nice teacher)!  I'll not make you prove it by induction today though;)  Maybe tomorrow...

Now you can do more interesting things instead of just summing consecutive positive integers.  Don't just move on to geometric progressions either - the world is your oyster...like finding numerical solutions using an iterative method of your choice using this streamlined apporach!&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-3167353270369933165?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/3167353270369933165/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=3167353270369933165' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3167353270369933165'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3167353270369933165'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/04/injecting-understanding.html' title='Injecting Understanding'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-7355738609334294627</id><published>2007-03-28T20:53:00.000-05:00</published><updated>2007-03-31T15:30:58.683-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='beautiful code'/><category scheme='http://www.blogger.com/atom/ns#' term='aesthetics'/><category scheme='http://www.blogger.com/atom/ns#' term='beauty'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='dhh'/><title type='text'>The Beautiful Code Debate</title><content type='html'>On my reading travels I came across &lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FMachine-Beauty-Elegance-Computing-Masterminds%2Fdp%2F046504316X%3Fie%3DUTF8%26s%3Dbooks%26qid%3D1175133396%26sr%3D8-1&amp;amp;tag=illinilistn09-20&amp;linkCode=ur2&amp;amp;camp=1789&amp;creative=9325"&gt;Machine Beauty: Elegance and the Heart of Computing&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=illinilistn09-20&amp;amp;amp;amp;l=ur2&amp;o=1" alt="" style="border: medium none  ! important; margin: 0px ! important;" border="0" height="1" width="1" /&gt;.  For those still debating whether or not the pursuit of beauty is beneficial it is a must read.

Another resource on this topic is David Heinemeier Hanson's talk on &lt;a href="http://media.rubyonrails.org/presentations/pursuitofbeauty.pdf"&gt;Rails: The Pursuit of Beauty&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-7355738609334294627?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/7355738609334294627/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=7355738609334294627' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7355738609334294627'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/7355738609334294627'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/03/beautiful-code-debate.html' title='The Beautiful Code Debate'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-8540155316948958247</id><published>2007-03-19T18:37:00.000-05:00</published><updated>2007-03-19T17:18:30.306-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='web 2.0'/><category scheme='http://www.blogger.com/atom/ns#' term='REST'/><category scheme='http://www.blogger.com/atom/ns#' term='web services'/><category scheme='http://www.blogger.com/atom/ns#' term='ROA'/><category scheme='http://www.blogger.com/atom/ns#' term='json'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><title type='text'>Twittering Around in Ruby</title><content type='html'>I just spent 2 minutes hacking around with twitter.com and thought I would share since a friend of mine wanted to use twitter to automate a few tasks for his websites in a similar vain to Urban Dictionary's use of twitter:
&lt;code&gt;
require 'net/http'
require 'uri'
require 'json'

HOST='twitter.com'
PORT=80
URI='/statuses/friends_timeline.json'

Net::HTTP.start(HOST, PORT) do |http|
  request = Net::HTTP::Get.new(URI)
  request.basic_auth('login', 'password')
  response = http.request(request)

  #Do something with the response.
  puts "Code: #{response.code}" 
  puts "Message: #{response.message}"
  puts "Body:\n #{response.body}"
  # to parse the JSON do something like: 
  #hash = JSON.parse(response.body)
  #and then do something with it.
end
&lt;/code&gt;
Prep work before you run this Ruby code is:
&lt;pre&gt;
$ sudo gem install json
&lt;/pre&gt;
&lt;b&gt;WARNING: The fastest Rails application on the web, Twitter aint!&lt;/b&gt;
I tried running against SSL, but got timed out too many times to test.  Though if you aren't having problems you can try the SSL version of the code:
&lt;code&gt;
require 'net/https'
require 'uri'
require 'json'

HOST='twitter.com'
PORT=443
URI='/statuses/friends_timeline.json'

http = Net::HTTP.new(HOST, PORT)
http.use_ssl = true
http.start do |http|
  request = Net::HTTP::Get.new(URI)
  request.basic_auth('login', 'password')
  response = http.request(request)

  #Do something with the response.
  puts "Code: #{response.code}" 
  puts "Message: #{response.message}"
  puts "Body:\n #{response.body}"
  # to parse the JSON do something like: 
  #hash = JSON.parse(response.body)
  #and then do something with it.
end
&lt;/code&gt;
You will get a warning when running it like the following:
&lt;blockquote&gt;warning: peer certificate won't be verified in this SSL session&lt;/blockquote&gt;
Though if you refer to the Net::HTTP rdoc you will find how to work with this if you wish.

I should also mention that you can edit the URI to the following values to get at different types of data:
&lt;ul&gt;  &lt;li&gt;&lt;code&gt;/statuses/public_timeline.json&lt;/code&gt; - JSON representation of public timeline on twitter&lt;/li&gt;  &lt;li&gt;&lt;code&gt;/statuses/friends.json&lt;/code&gt; - JSON representation of friends and their current messages on twitter&lt;/li&gt;  &lt;li&gt;&lt;code&gt;/statuses/followers.json&lt;/code&gt; - JSON representation of your followers and current messages on twitter&lt;/li&gt;&lt;/ul&gt;
Two things to note with the above:
&lt;ol&gt;  &lt;li&gt;By substituting .json with .xml twitter will send back XML format.&lt;/li&gt; &lt;li&gt;/statuses/update.json can be used in a HTTP POST request sent with a status=message+url+encoded name-value pair to update your own status.  To accomplish this you will just need to swap out the &lt;code&gt;request = Net::HTTP::GET.new&lt;/code&gt; line of code with something like the following:
&lt;code&gt;
&amp;nbsp;&amp;nbsp;request = Net::HTTP::Post.new(URI, {'status' =&gt; 'my new message'})
&lt;/code&gt;
&lt;/li&gt;&lt;/ol&gt;Hope this helps!&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-8540155316948958247?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/8540155316948958247/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=8540155316948958247' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8540155316948958247'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8540155316948958247'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/03/twittering-around.html' title='Twittering Around in Ruby'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-8094059110115608123</id><published>2007-03-19T10:14:00.000-05:00</published><updated>2007-03-19T10:32:37.904-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='agile software'/><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='quote'/><category scheme='http://www.blogger.com/atom/ns#' term='agile methods'/><category scheme='http://www.blogger.com/atom/ns#' term='buddha'/><category scheme='http://www.blogger.com/atom/ns#' term='agile process'/><category scheme='http://www.blogger.com/atom/ns#' term='agile practices'/><title type='text'>Was Buddha Agile?</title><content type='html'>I just found two interesting Buddha quotes this morning:
&lt;blockquote&gt;
Ennui has made more gamblers than avarice, more drunkards than thirst, and perhaps as many suicides as despair. 
&lt;/blockquote&gt;
The above quote demonstrates a mindset to dissuade ourselves from getting stuck in a rut through boredom.  I've seen the effects of boredom on traditional software projects to know the above prophecy to be very true in this application.
&lt;blockquote&gt;
I never see what has been done; I only see what remains to be done. 
&lt;/blockquote&gt;
This next quote captures (to me) the essence of agile development practices: focusing on what needs to be accomplished rather than composing lengthy status reports for clients or managers to look like you are doing "stuff".  I've worked with far too many "hot air" consultants from large consulting firms yelling the latest (at the time) buzz acronyms such as &lt;acronym title="Service Oriented Architecture"&gt;SOA&lt;/acronym&gt;, &lt;acronym title="Enterprise Service Bus"&gt;ESB&lt;/acronym&gt;, &lt;acronym title="Enterprise Architecture Integration"&gt;EAI&lt;/acronym&gt;, etc to know that these types of individuals do not accomplish anything in the real world.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-8094059110115608123?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/8094059110115608123/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=8094059110115608123' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8094059110115608123'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/8094059110115608123'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/03/was-buddha-agile.html' title='Was Buddha Agile?'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-1083999202935280222</id><published>2007-02-19T16:59:00.000-06:00</published><updated>2007-07-02T21:24:47.885-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='urbana'/><category scheme='http://www.blogger.com/atom/ns#' term='illinois'/><category scheme='http://www.blogger.com/atom/ns#' term='illini'/><category scheme='http://www.blogger.com/atom/ns#' term='champaign'/><category scheme='http://www.blogger.com/atom/ns#' term='chambana'/><category scheme='http://www.blogger.com/atom/ns#' term='illinilist'/><category scheme='http://www.blogger.com/atom/ns#' term='champaign urbana'/><title type='text'>Getting Local with Illinilist.NET</title><content type='html'>Living in a small, sleepy college town I have realized how few online resources there really are that are (a) usable; (b) not regularly spammed; (c) not just for advertising purposes; and (d) not a poor translation of an offline concept, such as classifieds in particular.

Yes, I know there is a Champaign-Urbana Craigslist, but let's be honest, the smaller Craigslist sub-sites are spammed without regard AND does anyone really think the website is all that nice to use?  You would think after being in production for so many years and having so many users they would have come up with an "account" where you can manage all your listings well before last year!  Not to mention the fact that Craigslist is a one-size-fits-all, cookie cutter approach to "going local".  Each sub-site is exactly the same except the content.  Is Craigslist really local?  &lt;b&gt;I think not!&lt;/b&gt;

As for locally run websites: I have found a couple, but they look like they were all coded in the 1990s with no ability to do anything functional (i.e. it is a static site that needs to be manually updated) or the website is just an excuse to put up big advertisements with inconsequential substance.

Now I am no web design fashionista, so my comments on the local websites written in the 1990's might be a little below the belt, but there is a lot that web developers should have realized since the 1990's in terms of usability on the web, not just how to make it look prettier.

After being frustrated for far too long with the status quo, I finally decided to put a website together relatively quickly to begin to create an online venue for classifieds (but hopefully translated to the web in a meaningful way and for the target audience).  

&lt;a href="http://illinilist.net"&gt;Illinilist.NET&lt;/a&gt; was officially launched last week, but haven't had the time to really promote yet.  So if you are a Champaign-Urbana resident looking for such an online local resource, check it out and let me know what you think about it.

Illinilist.NET currently only supports browsing, searching (by tags) and posting Events, Jobs and Rentals for the local area.  However, I am planning on beefing up the search capabilities, especially for Rental postings AND adding Real Estate, Resume and Personal listings within the next month.  See the &lt;a href="http://blog.illinilist.net"&gt;Illinilist.NET Blog&lt;/a&gt; to keep informed of the latest developments.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-1083999202935280222?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://illinilist.net' title='Getting Local with Illinilist.NET'/><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/1083999202935280222/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=1083999202935280222' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1083999202935280222'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1083999202935280222'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/02/getting-local-with-illinilistnet.html' title='Getting Local with Illinilist.NET'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-2627364343391874265</id><published>2007-01-25T21:58:00.000-06:00</published><updated>2007-01-25T22:55:58.877-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='scm'/><category scheme='http://www.blogger.com/atom/ns#' term='clearcase'/><category scheme='http://www.blogger.com/atom/ns#' term='perforce'/><category scheme='http://www.blogger.com/atom/ns#' term='darcs'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='subversion'/><category scheme='http://www.blogger.com/atom/ns#' term='source control management'/><category scheme='http://www.blogger.com/atom/ns#' term='cvs'/><category scheme='http://www.blogger.com/atom/ns#' term='svn'/><category scheme='http://www.blogger.com/atom/ns#' term='source control'/><title type='text'>SCM: The Next Generation</title><content type='html'>I am sure most developers (whether Java, Ruby, Python, or other) have used Subversion, Perforce or CVS to manage and control their projects' source, but have you used &lt;code&gt;darcs&lt;/code&gt; or &lt;code&gt;git&lt;/code&gt;?

I am sure most of you would have heard of &lt;code&gt;git&lt;/code&gt; - it hosts the Linux kernel code currently.  A few less have probably encountered &lt;code&gt;darcs&lt;/code&gt; before on an obscure European open source project you were interested in once or twice.  Prior to this evening, the most notable point of &lt;code&gt;darcs&lt;/code&gt;, to me, is that it is written in Haskell - a language I plan on reading more about because I have heard so many good things about it.  Both &lt;code&gt;git&lt;/code&gt; and &lt;code&gt;darcs&lt;/code&gt; are examples of distributed version control systems of which there has been an explosion of products (both commercial and open source) since &lt;code&gt;GNU's Arch&lt;/code&gt;.

Tonight I had the fortune of experiencing &lt;code&gt;darcs&lt;/code&gt; for the first time.  Previously I had used &lt;code&gt;darcs get URL&lt;/code&gt; to download code before, but never actually utilized any of the features before.  Tonight I submitted a patch to a Ruby open source project (RForum) and found the experience very pleasurable as a developer.

I have read that &lt;code&gt;darcs&lt;/code&gt; is a user friendly version of &lt;code&gt;git&lt;/code&gt; minus some powerful options.

Perhaps there are &lt;code&gt;git&lt;/code&gt; or &lt;code&gt;darcs&lt;/code&gt; gurus reading that can shed more light on these two less mainstream SCM systems?  If so, I would love to hear your thoughts on them especially if you can compare and contrast with any non-trivial Subversion, Perforce or CVS experiences.

Background: I currently use Subversion, however, I usually work in smaller teams, so concurrent work is not that much of an issue at present, but things will be changing.  The largest team I worked in on a Subversion project was 6 active developers (9 for the one Perforce project).  Of course, I have worked on a project with over 80 developers, but that was using &lt;code&gt;ClearCase&lt;/code&gt; and there were so many problems with that project that I can't determine what was caused by &lt;code&gt;ClearCase&lt;/code&gt; or the rest of the project's [many] issues.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-2627364343391874265?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/2627364343391874265/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=2627364343391874265' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2627364343391874265'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/2627364343391874265'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/01/scm-next-generation.html' title='SCM: The Next Generation'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-1739328682294915268</id><published>2007-01-24T18:48:00.000-06:00</published><updated>2007-10-25T16:07:03.557-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby idioms'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby on rails'/><category scheme='http://www.blogger.com/atom/ns#' term='rubyonrails'/><category scheme='http://www.blogger.com/atom/ns#' term='idioms'/><title type='text'>Ruby Idioms, Part 7</title><content type='html'>This idiom I have seen a little more in Rails than I have seen in Ruby, but I am putting it in this Ruby Idioms series anyway.

First off, most of you will know by know that you can "transparently" provide a method in Ruby a Hash.  What do I mean by "transparently"?  Well have a look at the code example below:
&lt;pre&gt;&lt;code&gt;
user = User.find(:first, :include =&gt; :preferences)
&lt;/code&gt;&lt;/pre&gt;
The last argument is actually a Hash even though you do not see the curly braces at all.

In my opinion it makes for more readable code.  So let us look behind the scenes at what the User.find method might do if it wasn't a magic method from ActiveRecord:
&lt;pre&gt;&lt;code&gt;
class User
  # ... some stuff here
  class &lt;&lt; self
    def find(type, params = {})
      options = @@DEFAULTS.merge(params)
      case type
      when :first
        options[:limit] = 1
      when :all
        # do stuff with options and query database
        # bla bla bla
      end
      # rest of implementation
    end
  end
  # ... some stuff here
end
&lt;/code&gt;&lt;/pre&gt;

This is quite useful, but one of the idioms in the same vain that I really appreciate is the following usage:
&lt;pre&gt;&lt;code&gt;
  validates_length_of :name, :title, :company, :in =&gt; 2..128
&lt;/code&gt;&lt;/pre&gt;
This provides another example of supplying a transparent &lt;code&gt;Hash&lt;/code&gt; arguments at the end of a list of arguments, the length of which is unknown.  For example we may decide that the code needs to actually be more like:
&lt;pre&gt;&lt;code&gt;
  validates_length_of :name, :company, :in =&gt; 2..64
  validates_length_of :title, :in =&gt; 2..128, :allow_nil =&gt; true # to cater for those ridiculously long job titles out there or those that do not have a job title at all
&lt;/code&gt;&lt;/pre&gt;
Now you will notice that the &lt;code&gt;validates_length_of&lt;/code&gt; method accepts a list of variable size for the list of attribute symbols to check and then at the end an option hash to specify length checking specifics.  How does the method know there is an option Hash at the end to use for these things and/or how long the list of attributes is?

The secret is in the sauce.  If we were to look in the source code (though I am not, because I have seen it so many times before), we would see something like:
&lt;pre&gt;&lt;code&gt;
def validates_length_of(*attrs)
  options = @@DEFAULT_OPTIONS.merge(attrs.pop.symbolize_keys) if attrs.last.is_a?(Hash)
  # then continue with rest of implementation.
end
&lt;/code&gt;&lt;/pre&gt;
All it is doing here is in the signature, specifying that there is a list of unknown length to be supplied to the method.  Then it pops the last item off that list (&lt;code&gt;Array&lt;/code&gt;) if and only if the last element of the list is a &lt;code&gt;Hash&lt;/code&gt; for use as the options that the implementation will use.

Until the next time enjoy Ruby's "transparent" &lt;code&gt;Hash&lt;/code&gt; idioms.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-1739328682294915268?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/1739328682294915268/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=1739328682294915268' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1739328682294915268'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/1739328682294915268'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/01/ruby-idioms-part-7.html' title='Ruby Idioms, Part 7'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-3108894095134049902</id><published>2007-01-23T22:28:00.000-06:00</published><updated>2007-01-23T22:55:55.425-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blocks'/><category scheme='http://www.blogger.com/atom/ns#' term='natural language'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby idioms'/><category scheme='http://www.blogger.com/atom/ns#' term='loops'/><category scheme='http://www.blogger.com/atom/ns#' term='idioms'/><category scheme='http://www.blogger.com/atom/ns#' term='looping'/><title type='text'>Ruby Idioms, Part 6</title><content type='html'>Tonight I remembered couple more idioms in Ruby and felt compelled to share.

In Javafied Ruby code (below) we often see something like the following:
&lt;pre&gt;
if something
  for role in user.roles
    return true if @@roles.include? role
  end
end
&lt;/pre&gt;
Now when I first started writing Ruby code almost 3 years ago, I thought looping through a collection like above was the nicest way.  I quickly found Ruby like the following snippet in code I inherited:
&lt;pre&gt;
user.roles.each do |role|
  return true if @@roles.include? role
end
&lt;/pre&gt;
At first it took me some time to understand it (remember I was just a poor Java developer with a little Python experience), and I wondered why people would write loops this way.  At the time it made no sense.  Using this form of looping in Ruby does somewhat violate my "reads more like natural language" coding style preferences, but for some reason this form of looping is now the only one I use in Ruby code.  Despite this violation, it feels much more natural.  If you are unsure about this yourself, I would encourage you to look into the power of blocks in Ruby.  Once you fully appreciate blocks, I think you might be persuaded, (though different opinions make this world more colorful and less bland).

The second Ruby idiom is less odd and does enhance code readability more like natural language (again in my opinion):
&lt;pre&gt;
user.roles.each do |role|
  return true if @@roles.include? role
end if something
&lt;/pre&gt;
Usually when people are having a conversation in English (at least native English speakers), we tend to say things like: We need to verify at least one role the user belongs to is in a pre-defined list of roles if [something].  Non-techies tend to say "if something" at the end of sentences, not at the very beginning (at least this is my experience).  So for this reason I think this idiom enhances readability of the code to be more like natural English.  For those that start every sentence that contains a conditional with the conditional clause, you have probably been a developer for far too long - go be a beach bum to save yourself before it is too late.&lt;div class="blogger-post-footer"&gt;2006-2008 Copyright (c) Susan Potter [http://susanpotter.net].  All rights reserved.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25066620-3108894095134049902?l=geek.susanpotter.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geek.susanpotter.net/feeds/3108894095134049902/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25066620&amp;postID=3108894095134049902' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3108894095134049902'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25066620/posts/default/3108894095134049902'/><link rel='alternate' type='text/html' href='http://geek.susanpotter.net/2007/01/ruby-idioms-part-6.html' title='Ruby Idioms, Part 6'/><author><name>S. Potter</name><uri>http://www.blogger.com/profile/17341145424164713662</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25066620.post-7542838880478449744</id><published>2007-01-22T19:43:00.000-06:00</published><updated>2007-01-22T19:48:04.641-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='natural language'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby idioms'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby ranges'/><category scheme='http://www.blogger.com/atom/ns#' term='idioms'/><category scheme='http://www.blogger.com/atom/ns#' term='range'/><category scheme='http://www.blogger.com/atom/ns#' term='ranges'/><title type='text'>Ruby Idioms, Part 5</title><content type='html'>Some of you may say this isn't strictly an idiom, but it is dependent on Ruby's core API and classes, so I have included it.  Ranges are a very nice low-level abstraction and can save Ruby developers a lot of time for certain coding needs.  In a previous idiom example on this blog I had a list of positive odd numbers under 7.  Well typing this out by hand is hardly consuming so I typed [1, 3, 5] by hand.  However, say we wanted to check if a number given by the user is an odd number under, say, 1001.  How can we do that in as few lines of code, while still maintaining or even enhancing code readability?  Look no further:
&lt;pre&gt;
ls = []
(1...1001).step(2) {|x| ls &lt;&lt; x} #=&gt; [1, 3, ..., 997, 999]
&lt;/pre&gt;
For Java heads that don't understand blocks - too bad that is out of the scope of this blog entry:)  Go and do you homework then revisit.  Google should be able to help you out in this regard.

Of course, Ruby developers could and would debate wh
