Year In Review

In this episode, Peter and Jason do a round up of new gem releases, Ruby releases, and Christmas.

Dead-End Jobs: Are You Suffering From Stockholm Syndrome?

Have you heard of Stockholm Syndrome? It’s a name given to the condition wherein hostages develop positive feelings toward their captors despite being held in negative, unfavorable and even life-threatening conditions. Victims of Stockholm Syndrome will even inexplicably stay with their captors even when given the chance at freedom.

Hopefully nobody reading this is literally being held hostage right now. If you are, good luck!

For the rest of you, why might I suggest that you are suffering from Stockholm Syndrome? Because employment relationships can manifest themselves in this very way.

In the article, Love and Stockholm Syndrome: The Mystery of Loving an Abuser, Dr. Joseph Carver says that the following four situations serve as a foundation for the development of Stockholm Syndrome:

<quote>

  • The presence of a perceived threat to one’s physical or psychological survival and the belief that the abuser would carry out the threat.
  • The presence of a perceived small kindness from the abuser to the victim
  • Isolation from perspectives other than those of the abuser
  • The perceived inability to escape the situation

</quote>

Looking back at my own career (specifically some of the extremely intelligent people I’ve met who are stagnating in oppressive companies or positions) I have recognized that many of these people (and sometimes myself) have felt “stuck” for no obvious reason. Some people seem just plain crazy when you look at their skill sets, ability, and the low quality of work or environment they’re willing to put up with.

So I contacted Joseph Carver to ask his opinion. Could this be Stockholm Syndrome? He agreed. In email, he said “SS is most likely to develop when the employee feels trapped, perhaps by a high salary, fear of losing a career, or fear of humiliation.” So let’s look at his four conditions:

Perceived threat:

Getting fired, being humiliated, not being a “top 20%” employee, not getting a raise. Employers wield a lot of perceived power over employees, especially for those in very traditional corporate jobs. The employer must be willing to carry out the threat. Every business is under the right conditions. It’s how businesses work.

Small kindness

Got a Christmas bonus once when you really needed it? Make a competitive salary? Great benefits? Get to work on a technology you don’t think you’d be able to work on elsewhere? There ya go.

Isolation from other perspectives

Again, a big corporate environment is ripe for this kind of isolation. If you work for BigCo, you learn to do things The BigCo way. The company’s organizational structure becomes a blueprint for your career progression. You start to lose sight of what industry pay and incentives look like since you have a homogeneous population to compare with. Unfortunately, from what I’ve seen even the best run companies create this kind of isolation of perspective and group-think. Charismatic leaders are particularly capable of creating a culture vacuum around a cult of personality.

Perceived inability to escape

According to the Bureau of Labor statistics, American adults spend by far more time working than any other activity. That’s a lot of your waking time being trapped in a routine. In a Stockholm Syndrome situation, the captor chips away at the self-esteem of the captive. So for most of our waking hours, those of us trapped in dead end jobs like these are exposed to environments which systematically destroy our self-confidence. Not only that, a persistent fear and feeling of failure makes it harder to actually explore the options for leaving the bad situation. The instinctive self-preservation reaction in this kind of situation is to work harder to try to avoid the perceived threat coming to fruition.


So, what if this describes your job? You owe it to yourself to find a way out. Hopefully recognizing the signs will show you that the real situation is far less grim than you might believe and that you have control over how you choose to spend the majority of your adult life.

I’m writing this for the many people I’ve met (and the countless I haven’t) that are senselessly stuck in bad job situations. Please stop wasting your precious time.

Dead-End Jobs: Are You Suffering From Stockholm Syndrome?

Have you heard of Stockholm Syndrome? It’s a name given to the condition wherein hostages develop positive feelings toward their captors despite being held in negative, unfavorable and even life-threatening conditions. Victims of Stockholm Syndrome will even inexplicably stay with their captors even when given the chance at freedom.

Hopefully nobody reading this is literally being held hostage right now. If you are, good luck!

For the rest of you, why might I suggest that you are suffering from Stockholm Syndrome? Because employment relationships can manifest themselves in this very way.

In the article, Love and Stockholm Syndrome: The Mystery of Loving an Abuser, Dr. Joseph Carver says that the following four situations serve as a foundation for the development of Stockholm Syndrome:

<quote>

  • The presence of a perceived threat to one’s physical or psychological survival and the belief that the abuser would carry out the threat.
  • The presence of a perceived small kindness from the abuser to the victim
  • Isolation from perspectives other than those of the abuser
  • The perceived inability to escape the situation

</quote>

Looking back at my own career (specifically some of the extremely intelligent people I’ve met who are stagnating in oppressive companies or positions) I have recognized that many of these people (and sometimes myself) have felt “stuck” for no obvious reason. Some people seem just plain crazy when you look at their skill sets, ability, and the low quality of work or environment they’re willing to put up with.

So I contacted Joseph Carver to ask his opinion. Could this be Stockholm Syndrome? He agreed. In email, he said “SS is most likely to develop when the employee feels trapped, perhaps by a high salary, fear of losing a career, or fear of humiliation.” So let’s look at his four conditions:

Perceived threat:

Getting fired, being humiliated, not being a “top 20%” employee, not getting a raise. Employers wield a lot of perceived power over employees, especially for those in very traditional corporate jobs. The employer must be willing to carry out the threat. Every business is under the right conditions. It’s how businesses work.

Small kindness

Got a Christmas bonus once when you really needed it? Make a competitive salary? Great benefits? Get to work on a technology you don’t think you’d be able to work on elsewhere? There ya go.

Isolation from other perspectives

Again, a big corporate environment is ripe for this kind of isolation. If you work for BigCo, you learn to do things The BigCo way. The company’s organizational structure becomes a blueprint for your career progression. You start to lose sight of what industry pay and incentives look like since you have a homogeneous population to compare with. Unfortunately, from what I’ve seen even the best run companies create this kind of isolation of perspective and group-think. Charismatic leaders are particularly capable of creating a culture vacuum around a cult of personality.

Perceived inability to escape

According to the Bureau of Labor statistics, American adults spend by far more time working than any other activity. That’s a lot of your waking time being trapped in a routine. In a Stockholm Syndrome situation, the captor chips away at the self-esteem of the captive. So for most of our waking hours, those of us trapped in dead end jobs like these are exposed to environments which systematically destroy our self-confidence. Not only that, a persistent fear and feeling of failure makes it harder to actually explore the options for leaving the bad situation. The instinctive self-preservation reaction in this kind of situation is to work harder to try to avoid the perceived threat coming to fruition.


So, what if this describes your job? You owe it to yourself to find a way out. Hopefully recognizing the signs will show you that the real situation is far less grim than you might believe and that you have control over how you choose to spend the majority of your adult life.

I’m writing this for the many people I’ve met (and the countless I haven’t) that are senselessly stuck in bad job situations. Please stop wasting your precious time.

Maze Generation: Eller’s Algorithm

Last time I talked about the recursive backtracker algorithm for maze generation. That’s probably always going to be my favorite algorithm for generating mazes, for a variety of reasons, but that’s not going to stop me from looking at others.

For one thing, there are some pretty crazy algorithms out there for generating mazes.

Eller’s algorithm is one of the craziest. It’s also one of the fastest. And it’s the only one I know that let’s you generate mazes of an infinite size. In linear time.

Yeah, it’s that crazy.

It does this by building the maze one row at a time, using sets to keep track of which columns are ultimately connected. But it never needs to look at more than a single row, and when it finishes, it always produces a perfect maze.

Like I did for the recursive backtracking algorithm, here’s the “mile-high” overview of Eller’s algorithm:

  1. Initialize the cells of the first row to each exist in their own set.
  2. Now, randomly join adjacent cells, but only if they are not in the same set. When joining adjacent cells, merge the cells of both sets into a single set, indicating that all cells in both sets are now connected (there is a path that connects any two cells in the set).
  3. For each set, randomly create vertical connections downward to the next row. Each remaining set must have at least one vertical connection. The cells in the next row thus connected must share the set of the cell above them.
  4. Flesh out the next row by putting any remaining cells into their own sets.
  5. Repeat until the last row is reached.
  6. For the last row, join all adjacent cells that do not share a set, and omit the vertical connections, and you’re done!

If you’re at all like me, your head is probably spinning at this point. Let’s back up and work through an example manually, to help you see how the algorithm works in practice. Let’s begin with a simple 5-column row.

An example

First, we initialize each cell in that row to be in its own set. I’ll just assign each cell a number, indicating the set it belongs to:

   ___________________
  |   |   |   |   |   |
  | 1 | 2 | 3 | 4 | 5 |
  |___|___|___|___|___|

Next, we randomly join adjacent cells that belong to different sets. The cells so joined also are merged into the same set:

   ___________________
  |           |       |
  | 1   1   1 | 4   4 |
  |___________|_______|

Now we randomly determine the vertical connections, at least one per set. The cells in the next row that we connected to must be assigned to the set of the cell above them:

   ___________________
  |           |       |
  | 1   1   1 | 4   4 |
  |    ___    |___    |
  |   |   |   |   |   |
  | 1 |   | 1 |   | 4 |
  |___|   |___|   |___|

Next, we flesh out the next row, assigning each remaining cell to its own set:

   ___________________
  |           |       |
  | 1   1   1 | 4   4 |
  |    ___    |___    |
  |   |   |   |   |   |
  | 1 | 6 | 1 | 7 | 4 |
  |___|___|___|___|___|

At this point, we can actually discard the first row, because the algorithm is done with it. We’ll keep it around for now, though, for the sake of illustration. I’ll just put a little space between the previous rows, and the current row:

   ___________________
  |           |       |
  | 1   1   1 | 4   4 |
  |    ___    |___    |
       ___     ___     
  |   |   |   |   |   |
  | 1 | 6 | 1 | 7 | 4 |
  |___|___|___|___|___|

Now, we just repeat the previous steps on our new row. We randomly connect adjacent sets that do not share a set. Something like this:

       ___     ___     
  |       |       |   |
  | 1   1 | 1   1 | 4 |
  |_______|_______|___|

Now we add at least one vertical connection for each set:

       ___     ___     
  |       |       |   |
  | 1   1 | 1   1 | 4 |
  |___    |_______|   |
      |   |       |   |
      | 1 |       | 4 |
      |___|       |___|

And then we flesh out the next row (I’m reusing some extinct set numbers here, for the sake of single-digits):

       ___     ___     
  |       |       |   |
  | 1   1 | 1   1 | 4 |
  |___    |_______|   |
  |   |   |   |   |   |
  | 8 | 1 | 9 | 2 | 4 |
  |___|___|___|___|___|

This is our current state, with history, now:

   ___________________
  |           |       |
  | 1   1   1 | 4   4 |
  |    ___    |___    |
  |       |       |   |
  | 1   1 | 1   1 | 4 |
  |___    |_______|   |
   ___     ___ ___
  |   |   |   |   |   |
  | 8 | 1 | 9 | 2 | 4 |
  |___|___|___|___|___|

It’s starting to look like a maze! Let’s do one more iteration, and then finish it out. So, first, randomly join adjacent cells from different sets:

   ___     ___ ___
  |   |   |           |
  | 8 | 1 | 9   9   9 |
  |___|___|___ ___ ___|

Then, add at least one veritcal connection for each set:

   ___     ___ ___
  |   |   |           |
  | 8 | 1 | 9   9   9 |
  |   |   |___     ___|
  |   |   |   |   |
  | 8 | 1 |   | 9 |
  |___|___|   |___|

And flesh out the next row:

   ___________________
  |           |       |
  | 1   1   1 | 9   9 |
  |    ___    |___    |
  |       |       |   |
  | 1   1 | 1   1 | 9 |
  |___    |_______|   |
  |   |   |           |
  | 8 | 1 | 9   9   9 |
  |   |   |___     ___|
           ___     ___ 
  |   |   |   |   |   |
  | 8 | 1 | 3 | 9 | 5 |
  |___|___|___|___|___|

And now the last row. This time, we must connect ALL adjacent (but disjoint) cells. In this case, that means all of them:

           ___     ___ 
  |                   |
  | 8   8   8   8   8 |
  |___________________|

Since this is the last row, we skip the bit where we add verticals…and that means we’re done! The result, with set numbers removed, is:

   ___________________
  |           |       |
  |           |       |
  |    ___    |___    |
  |       |       |   |
  |       |       |   |
  |___    |_______|   |
  |   |   |           |
  |   |   |           |
  |   |   |___     ___|
  |                   |
  |                   |
  |___________________|

A perfect maze!

Analysis

Let’s analyze that a bit. It seemed to come together pretty magically, considering we weren’t looking at anything but the current row (and the next row, briefly). The key to it all are the sets.

The set that a cell belongs to tells the algorithm who its siblings were, are, and will be. It’s the crystal ball that lets the algorithm gaze into the future (and the past!) and avoid adding cycles and isolates to the maze.

Cells that share a set, also share a path between them. (If you don’t believe me, look at the example I just gave, above. Every cell that shares a set identifier is connected; cells in different sets are not connected.)

If the algorithm allowed us to create a passage between two cells that shared a set, it would be introducing a second path between those two cells. That’s essentially the definition of a loop or cycle in the graph, and since we don’t want cycles in our maze, we disallow that.

Conversely, cells that do not share a set, are not connected (they are disjoint). By the time we reach the end of the maze, every cell must be connected to every other cell, and the only way we can do that is if every set is eventually merged into a single set.

We can’t do that if a set does not propogate itself to the next row. This is why the algorithm requires that at least one vertical passage be created for each set in the row. Otherwise, any set that didn’t create a vertical passage would become extinct after the current row. The result would be an isolate, an orphaned collection of cells that could never be reached from outside that set.

Then, at the end, the algorithm joins all disjoint sets, allowing every cell in the the entire maze to be connected by a single, unique path to any other cell in the maze. And we’re done!

Implementation

How would you implement this? The key, for me, turned out to be implementing the sets. You need to be able to quickly determine the set of any given cell in a row, as well as determine the list of cells in any given set. I did this by maintaining a hash of arrays that mapped sets to cells, and another hash that mapped cells to sets. As I did in the example above, I simply used a unique integer to identify each set.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@sets = Hash.new { |h,k| h[k] = [] }
@cells = {}

def same?(cell1, cell2)
  @cells[cell1] == @cells[cell2]
end

def add(cell, set)
  @cells[cell] = set
  @sets[set] << cell
  self
end

def each_set
  @sets.each do |id, set|
    yield id, set
  end
end

The process of merging two sets is O(n) as I’ve implemented it, but given that n is fairly small, that didn’t worry me too much:

1
2
3
4
5
6
7
def merge(sink_cell, target_cell)
  sink, target = @cells[sink_cell], @cells[target_cell]

  @sets[sink].concat(@sets[target])
  @sets[target].each { |cell| @cells[cell] = sink }
  @sets.delete(target)
end

There’s plenty of room for optimization there, though.

Lastly, assigning set ids is done via a #populate method:

1
2
3
4
5
6
7
8
9
10
11
def populate
  width.times do |cell|
    unless @cells[cell]
      set = @next_set += 1
      @sets[set] << cell
      @cells[cell] = set
    end
  end

  self
end

Once I had these routines (encapsulated into a State class), the algorithm itself came fairly neatly. It works in two steps, plus a third to convert the representation into something easier to display.

The first step looks over the row and randomly connects adjacent cells (if they exist in disjoint sets):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
connected_sets = []
connected_set = [0]

(state.width-1).times do |c|
  if state.same?(c, c+1) || (!finish && rand(2) > 0)
    # cells are not joined by a passage, so we start a new connected set
    connected_sets << connected_set
    connected_set = [c+1]
  else
    state.merge(c, c+1)
    connected_set << c+1
  end
end

connected_sets << connected_set

As you can see, the process simply looks at each cell and its neighbor, comparing their states and then either adding the cells to a “connected set” (a series of adjacent cells that are all horizontally connected) and merging the sets together, or creating a new connected set when the two cells should not be merged.

The finish variable is used to change the behavior for the final row; it is false for the rest of the rows.

The second step looks at the available sets and randomly adds vertical connections:

1
2
3
4
5
6
7
8
9
10
verticals = []
next_state = state.next

unless finish
  state.each_set do |id, set|
    cells_to_connect = set.sort_by { rand }[0, 1 + rand(set.length-1)]
    verticals.concat(cells_to_connect)
    cells_to_connect.each { |cell| next_state.add(cell, id) }
  end
end

State#next just returns a new State object (that we’re using for the next row). Then, for each set of cells, we randomly pick some number of them and add them to the list of verticals we’re going to create. (The verticals are also added to the next row, in the same set.)

The algorithm itself then loops over these steps repeatedly, setting state to next_state at the end of each pass, until it is done. (In my case, I trapped the INT signal, so ctrl-C can be used to terminate the algorithm and gracefully finish the maze.)

For those of you not using IE (which will make a total mess of this), here are two demos you can play with to see the algorithm in action:

<link href=”http://jamisbuck.org/mazes/css/mazes.css” />

My complete implementation (in Ruby) is here:

<noscript>ellers.rb on gist.github.com</noscript>

I think Eller’s algorithm is harder to customize than the recursive backtracking algorithm, but it can be done. Consider it an exercise, if you want: how would you introduce horizontal or vertical bias into the maze? (I.e., how would you make the maze prefer longer corridors, either horizontally or vertically?) How would you implement weave mazes, where the passages move over or under other passages? Especially tricky: how would you introduce symmetry into the output, given that the algorithm itself doesn’t look at anything more than the single row?

If nothing else, though, please give Eller’s algorithm a shot. Please share your implementations (in any language!) in the comments (links to gist.github.com are preferred).

How to Install Ruby 1.9.2 and Rails 3.0 on Ubuntu 10.10

For better or worse, Ubuntu has become one of the most popular Linux variants on which to deploy Ruby and Rails apps. It was used for 84% of the 2010 Rails Rumble projects, for example. Back in 2009, I wrote a guide to installing a Ruby 1.8 stack on Ubuntu 8.10 so it was only a matter of time before I had to tackle 10.10 (Maverick Meerkat) and Ruby 1.9.

A few days ago I needed to produce a screencast on installing Ruby 1.9.2 on Ubuntu 10.10 for students in my forthcoming Ruby class to use. It was a pleasant surprise, then, to discover that Ryan Bigg had posted Ubuntu, Ruby, RVM, Rails and You on Christmas Day. It’s a written walkthrough of installing Ruby 1.9.2 and Rails 3.0 on Ubuntu 10.10. So if you want a written walkthrough, read his post.

I still needed a screencast though so I compared Ryan’s technique to mine, made some minor tweaks, and followed his general process when making the following screencast. So if you prefer screencasts to blog posts, you’re in luck. The video is available on YouTube as “How to Install Ruby 1.9.2 and Rails 3.0 on Ubuntu 10.10” or if you’re on the Web, you can see it embedded below:

Errata: 1) The aspect ratio for the video is wrong. I don’t tend to do HD uploads so I learned a lesson there. This doesn’t affect the content, though. 2) I call Ryan Bigg “Ryan Biggs” once or twice. Curiously this seems to be a common mistake on Ruby blogs..

A Scam I Say!

Today, I repeated myself in a particular way for the last time. In at least four places in Harmony, I had faux classes that responded to all, find, create, etc. and initialized attributes from a hash. As I went to write another, I realized I had a problem and it was time for a new open source project.

A Little History

Harmony has a Site model, which has a site mode. The site mode is either ‘development’ or ‘live’. Back in the day, I would have created a SiteMode model that was backed by the database and hooked up all the relationships between Site and SiteMode.

Over the years, I have realized that is a waste. The information in that database table rarely changes and if it does, it is usually accompanied by other code changes and a deploy. This type of information is perfect for just storing in memory. If you need to change it, do so, commit, and deploy.

When I was originally working on site mode’s a few years back, I created a fake model that looked similar to this:

class SiteMode
  cattr_accessor :modes
  @@modes = [
    {:id => 1, :name => 'live'},
    {:id => 2, :name => 'development'},
  ]
  
  def self.[](id)
    SiteMode.new(id)
  end
  
  def self.all
    @@modes.map { |m| SiteMode.new(m[:id]) }
  end

  attr_accessor :id, :name
  
  def initialize(id)
    self.class.modes.detect { |m| m[:id] == id }.each_pair do |attr, value|
      self.send("#{attr}=", value)
    end
  end
  
  def password_required?
    id == 2
  end
  
  def display_name
    name.titleize
  end
end

With just that code, Site could belong_to :site_mode and everything just worked. I got my same relationships and instead of querying a database (and having to keep that data in sync), everything was just stored in memory.

The Scam

Like I said, rather than create another fake model with all the same code and tests, I pulled out the shared pieces into a gem named scam. With scam in the mix, the SiteMode model now looks like this:

class SiteMode
  include Scam

  attr_accessor :name

  def password_required?
    id == 2
  end

  def item_cache?
    id == 1
  end

  def display_name
    name.titleize
  end
end

SiteMode.create({
  :id   => 1,
  :name => 'live'
})

SiteMode.create({
  :id   => 2,
  :name => 'development'
})

By just including Scam, we get all the enumerable functionality and such (see the specs for more). Now the SiteMode class deals specifically with site mode related code instead of how to initialize, create, and enumerate site modes. I switched the other classes that used the same idiom and was left with less code on the other side.

A Few Notes

This is nothing earth shattering, but it saves queries and wraps up an idiom I was using into a separate, well-tested piece of functionality that I can share not only across Harmony, but other applications as well.

One other thing worth noting is my choice of using id’s and having them be integers. The first advantage to using integers is size. The integer 2 is smaller to store than the string ‘development’. That might not seem like a big deal, but after working on the projects I have recently, every byte still counts.

The second reason is that integers are more flexible. Right now, 1 is live and 2 is development. If I decide I want another site mode to be the default instead of development, I can simply change development to a different id and create my new one as 2. The new one instantly becomes the new site mode for all sites with site_mode_id of 2.

If, on the other hand, I had used strings, I would have to map my new site mode to development and map development to something different. This would certainly lead to confusion in the code down the road. Strings have meaning because they are often words, whereas integers do not. Hope that makes sense.

At any rate, you can gem install scam if you have a need. If not, I hope some of the ideas in this post inspire you in some way.

The Heroku & IndexTank “Build A Search-Based Webapp” Holiday Challenge

Recently-minted Ruby webapp hosting gods Heroku and real-time “search as a service” provider IndexTank have teamed up to present the Heroku + IndexTank Application Contest. Prizes include an 11.6″ MacBook Air, a Lilliput Mini USB monitor, and an Electronic Rock Guitar shirt. It’s open to US developers only (boo!) and entries are due by January 6, 2011.

IndexTank is one of Heroku’s new add-on partners providing search-as-a-service to folks running apps on the Heroku platform. Essentially, it’s a giant “search in a box” service you can push data into and query in real-time. They worry about providing a search service just like Heroku worries about keeping your app running. They seem a little pricey to me but this is the sort of service any serious site can’t afford to compromise on.

The aim of the contest is to build a Ruby webapp that’s hosted on Heroku and uses IndexTank’s search service in an interesting way. To do this you get access to IndexTank’s $300/mo premium service for free for the duration of the contest (you can add it in Heroku now) and Heroku’s basic service is already free to use. IndexTank provides a IndexTank for Ruby tutorial as well as various ideas of what you could build but the contest is quite freeform. The rules are all here.

The judges of the contest are: Diego Basch, founder of IndexTank; James Lindenbaum, co-founder of Heroku; Michael Dearing, an angel investor; Othman Laraki, director of search at Twitter; and me, Peter Cooper, the Ruby Inside guy.

The Ruby Community’s Christmas Releases

bauble.pngChristmas is a special time for Rubyists and not only for those of us taking the opportunity to get drunk and eat a lot. December 25 has been a popular release date since Ruby 1.0 was released on December 25, 1996 and several developers were preparing releases of their libraries and Ruby implementations this year too. So what December 25, 2010 bring?

Ruby 1.8.7 patchlevel 330

At 9am GMT on Christmas Day, Urabe Shyouhei posted the release announcement for the latest patchlevel version of Ruby 1.8.7 to the ruby-talk mailing list:

If I were Takoyaki Mask I would have written a prose but sadly I am not, so let me tell in brief. Here you are the Ruby 1.8.7’s most recent update. Mainly bugfixes.

Urabe Shyouhei

Intriguingly, he also noted that Ruby has changed license recently and this is the first release since the switch:

It is worth noting that, though Ruby project experienced license change recently (from GPLv2 to 2-clause BSDL) and this is the first time since then to release something, that change do not reach to already-released versions like 1.8.7, matz said to us. So you do not have to worry about it. If you are already using 1.8.7, you can continue using it.

Urabe Shyouhei

Ruby 1.9.2 patchlevel 136

The latest version of the production-ready Ruby 1.9.2 was announced by Yuki (Yugui) Sonoda who notes it fixes “many bugs” found in 1.9.2-p0. The changelog shows a lot of small “niggly” bugs rather than any showstoppers so this release is just a spit and polish of an already fine implementation.

RubyInstaller Releases for Windows

RubyInstaller bills itself as “the easy way to install Ruby on Windows” and it’s certainly what I’d advise you use if you’re on that platform. It’s primarily maintained by Luis Lavena. Luis made the announcements of RubyInstaller 1.8.7-p330 and RubyInstaller 1.9.2-p136 packages two days after Christmas, but since they tie so directly to the previous items, it only seemed fair to give him two days to prepare them 😉

Separate to the general improvements in 1.8.7-p330 and 1.9.2-p136, Luis also notes that RubyInstaller is now based around an updated compiler toolchain (GCC 4.5.1) and there are now “friendlier Gem installation error messages” for those gems that require compilation.

posix_mq 0.6.0 – POSIX message queues with new Rubinius support

Eric Wong announced posix_mq 0.6.0, an update of a library that makes it easy to use POSIX-compliant message queues from your Ruby apps. The key news with this update is all new Rubinius support.

graph 2.0.0 – A super hash that outputs in Graphviz dot format

Ryan Davis (a.k.a. zenspider) announced graph 2.0.0 – a library to work with “graphs” which he describes as “a type of hash that outputs in graphviz’s dot format.” The big news for 2.0.0 is that it’s a total rewrite without backwards compatibility but that it’s now 147.3% more awesome and no longer a Hash sublcass.

OS X only tip: Want to give graph a quick try? gem install graph, brew install graphviz, copy and paste the example from GitHub, run it, then open simple_example.png. Tada – you’ve created a basic graph!

Hackety Hack 1.0 – _why’s Baby All Grown Up

Despite losing Why The Lucky Stiff over a year ago, Why’s Shoes and Hackety Hack projects have lived on under the care of Steve Klabnik (amongst others). I’m going to write a separate, deeper post on this but it deserves to be in the list since Steve pushed out Hackety Hack 1.0 on Christmas Day along with an all-new Hackety Hack homepage.

Sinatra 1.1.1

As noted by people in the comments, Sinatra also had an update on Christmas Day, to version 1.1.1. An incomplete but “rather stable” prerelease of Sinatra 1.2.0 also went out – it can be installed with gem install sinatra --pre

Maze Generation: Recursive Backtracking

I’ve said before that generating mazes is a great default project when experimenting with a new programming language. I’ve yet to find a better one (but I’d love to hear recommendations). However, before you can dive into generating a maze (especially in a syntax you are unfamiliar with), you had better have a solid grasp of how the process works.

With mazes, you can take your pick of a solid double-handful of algorithms: recursive backtracking, Prim’s, Kruskal’s, Eller’s, Aldous-Broder or Wilson’s algorithms, recursive division, hunt-and-kill, and more.

My favorite, and the one I implement by default, is recursive backtracking. It is fast, easy to understand, and straightforward to implement. You’ll need sufficient memory to store the entire maze in memory, though, and it requires stack space again proportional to the size of the maze, so for exceptionally large mazes it can be fairly inefficient. But for most mazes, it works a charm.

Here’s the mile-high view of recursive backtracking:

  1. Choose a starting point in the field.
  2. Randomly choose a wall at that point and carve a passage through to the adjacent cell, but only if the adjacent cell has not been visited yet. This becomes the new current cell.
  3. If all adjacent cells have been visited, back up to the last cell that has uncarved walls and repeat.
  4. The algorithm ends when the process has backed all the way up to the starting point.

I generally implement the field as a grid of bitfields (where the bits in each cell describe which direction passages have been carved). That’s probably just the C programmer in me asserting dominance, though; feel free to experiment with other representations.

In Ruby, I usually initialize the grid like so:

1
grid = Array.new(height) { Array.new(width, 0) }

Next, the algorithm itself is kicked off by calling the worker function:

1
2
3
4
5
def carve_passages_from(cx, cy, grid)
  # work, work, work
end

carve_passages_from(0, 0, grid)

This begins carving passages in the grid, starting at the upper-left corner, (0,0). And as you might have guessed from the algorithm’s name, this works recursively, as we’ll see next.

The carve_passages_from method first creates a list of directions that ought to be tried from the current cell:

1
directions = [N, S, E, W].sort_by{rand}

We sort the list in random order, so that the path will meander, rather than having a bias in any particular direction.

Then, the function iterates over each of those directions, determining the coordinates of the cell in that direction and deciding if the cell is valid or not. Note that a cell is valid only if it lies within the bounds of the maze, AND it has not previously been visited: we only want to carve passages into untouched cells, to avoid creating circular loops in the maze.

1
2
3
4
5
6
7
8
directions.each do |direction|
  nx, ny = cx + DX[direction], cy + DY[direction]

  if ny.between?(0, grid.length-1) && nx.between?(0, grid[ny].length-1) && grid[ny][nx] == 0
    # cell is valid!
    # ...
  end
end

Finally, if the cell is valid, we carve a passage out of the current cell and into the next cell. Then, we recursively call carve_passages_from on the new cell:

1
2
3
grid[cy][cx] |= direction
grid[ny][nx] |= OPPOSITE[direction]
carve_passages_from(nx, ny, grid)

And that’s really all there is to it!

For all of you not using IE (and I honestly hope no reader of my blog uses IE), here’s a Javascript demo you can play with to see the algorithm in action:

<link href=”http://jamisbuck.org/mazes/css/mazes.css” />

My complete Ruby implementation, including some lines to output the maze as ASCII, is here:

<noscript>[recursive-backtracker2.rb at gist.github.com]</noscript>

Start by writing your own implementation in a language you’re comfortable in, just to wrap your mind around the algorithm. Try replacing the recursion with iteration (always a fun exercise). Consider extending it to include weave mazes (where passages move over or under other passages), or braided mazes (mazes where deadends are removed to create loops in the maze), or symmetrical mazes, or wrapped mazes. Or even different cell tesselations. If you’re at all like me, you may find that your “toy” project has taken on a life of its own, and you’re suddenly researching new and exciting ways to build mazes!

Once you understand the algorithm, though, the real fun is trying it in a language you’re unfamiliar with. It’ll show you conditionals, bit manipulation (if you use the bitfield storage like I showed above), iteration, recursion, and console output (if you decide to render your maze, too). When you’ve finished, you’ll have a good idea of how the language looks and works in practice, and not just for trivial “hello world” apps.

Give it a try! Please share your implementations in the comments (links to gist.github.com are preferred).

#246 AJAX History State

The new pushState function in JavaScript allows us to change the full URL with AJAX. This means the back button, reloading, and bookmark support can be easily added.

#246 AJAX History State

The new pushState function in JavaScript allows us to change the full URL with AJAX. This means the back button, reloading, and bookmark support can be easily added.

Improving Your Methods

I am always looking for ways to improve my efficiency while coding. One of the things that has been bothering me lately is how I run tests. Back in the day, I used autotest. Of late, I have been using watchr. Finally, this week I worked out something that does exactly what I want.

Watchr

Watchr is great at running arbitrary code when files change, but it cannot read my mind. When working on libraries, such as Hunt, Joint, MongoMapper, etc., running tests/specs/features every time a file changes is fine. Heck, the whole suite MongoMapper runs in like 10 seconds.

You can take it a step further and make watchr even more responsive by making it run only related files. For example, whenever lib/mongo_mapper/plugins/accessible.rb is saved, most likely I want to run the test/functional/test_accessible.rb test (checkout MM’s specs.watchr file on Github).

The Problem

The issues I have had with watchr is more when working on big applications, such as Harmony or Words with Friends. Bigger applications have bigger test suites and the process of automatically determining which tests I want to run when a file changes is difficult to impossible and changes from moment to moment.

After about a month of the pain that is manually running specific test files, I finally decided to think out what I wanted. The conclusion I came to was that I do not want tests to automatically run when I save a file. Instead, I want it to be easier to run specific tests based on keywords.

The Solution

Since I was working on Harmony at the time and it uses test/unit, I decided to write a script that would grep through the tests to match arguments I pass in and run all of the matches. Below is the script I came up with:

#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'

if ARGV.empty?
  puts <<-EOH
Usage:
  script/test [test match string]

Example:
  script/test site
  script/test site_test
  script/test site_test account_test

  Above would run all tests matching site
  (ie: test/unit/site_test.rb, test/functional/admin/sites_controller.rb, etc.)

EOH
  exit
end

tests = Dir.glob(File.dirname(__FILE__) + '/../test/**/*').map do |file|
  # skip non test files
  next unless file.include?('_test')

  # check if any of the inputs match the file
  if ARGV.any? { |match| file =~ /#{match}/ }
    File.expand_path(file).gsub(Rails.root.join('test'), 'test')
  end
end.compact.join(' ')

test_loader = File.expand_path('../test_loader', __FILE__)

$stdout.sync = true
command = "bundle exec ruby -Itest #{test_loader} #{tests}"

puts(command)

IO.popen(command) { |com| com.each_char { |c| print(c) } }

Note that it uses this test_loader, stolen directly from rake.

#!/usr/bin/env ruby

ARGV.each { |f| load f unless f =~ /^-/  }

I dropped both of these files in the script/ directory of Harmony. The cool thing is with not very much code, I can now do the following:

# run all of harmony's tests for liquid filters
# filters are all in a folder test/unit/filters
script/test filters

# same thing but drops
script/test drops

# run all the item related tests (from controllers, models, filters and drops)
script/test item

# run unit tests for feed and feed template
script/test unit/feed

# run unit test for feed and feed drop test
script/test unit/feed feed_drop

Conclusion

In general I am working on a very specific thing. All I want is to be able to test that very specific thing, on the fly, and very easily. script/test allows me to do that. Oh, and of course I aliased script/test to st, as I hate typing more than 2 characters. 😉

Not sure that this is valuable to anyone else as it is so specific to how I work, but it is the thought that counts. Also, It could easily be adapted to rspec or cucumber. I can say that it has drastically helped me.

I think the lesson to grep from this post is always be conscious of how you work and when you see a chance for improvement, spend a few minutes to make it happen.

Faker 0.9.0 released

It’s been two-and-a-half years since the last release of the faker gem, so it’s about time for another one. 🙂 For a long time I’ve been kicking around the idea of using the I18n gem to make it easier to support different formats (think zip codes in the US vs. postal codes in the UK) without having to change the method signatures, so I sat down today to give it a whirl. I was pleasantly surprised with how quickly I was able to get it implemented, so kudos to the folks who have done all the hard work of making that gem work as well as it does.

In fact, my love for that gem has grown over the last several months as I’ve used it on a few different projects. I had one client who wanted everything localized in his Rails app, which helped me get familiar with it (and eventually enchanted with it). One thing that I found fun to do (which may indicate I’ve crossed the line into gem abuse) is to store starter data in the locale files. For example, if you wanted to give new users a sample list of groceries when they create an account in your grocery list tracker app, you could do something like this:

Then you can create a list of groceries in the user’s native tongue.

So check out the latest faker, and find crazy stuff like that and more! 🙂

Faker 0.9.0 released

It’s been two-and-a-half years since the last release of the faker gem, so it’s about time for another one. 🙂 For a long time I’ve been kicking around the idea of using the I18n gem to make it easier to support different formats (think zip codes in the US vs. postal codes in the UK) without having to change the method signatures, so I sat down today to give it a whirl. I was pleasantly surprised with how quickly I was able to get it implemented, so kudos to the folks who have done all the hard work of making that gem work as well as it does.

In fact, my love for that gem has grown over the last several months as I’ve used it on a few different projects. I had one client who wanted everything localized in his Rails app, which helped me get familiar with it (and eventually enchanted with it). One thing that I found fun to do (which may indicate I’ve crossed the line into gem abuse) is to store starter data in the locale files. For example, if you wanted to give new users a sample list of groceries when they create an account in your grocery list tracker app, you could do something like this:

Then you can create a list of groceries in the user’s native tongue.

So check out the latest faker, and find crazy stuff like that and more! 🙂

The 5 Most Popular Articles of 2010 on RubyLearning

(Based on Twitter Re-tweets)

The year 2010 saw RubyLearning’s awesome guest authors write and share their knowledge with others. If you missed any of these useful articles then here’s the roundup of the 5 most popular articles of 2010 on RubyLearning. Enjoy!

Do You Enjoy Your Code Quality?

James M. Schorr gives real-world tips on how to enjoy crafting software by improving the quality of your code and your development habits.

The goal of this article is to enable you to improve code quality and, thus, transform the mundane into the beautiful. No matter where you’re at on the spectrum, beginner to advanced, there is always room for improvement. As the code quality improves, your ability to delight in it and enjoy what you’re doing does as well.

Read the rest of the article: Do You Enjoy Your Code Quality?

14 Ways To Have Fun Coding Ruby

Jeff Schoolcraft shows you 14 different ways to have fun coding with Ruby.

From time to time you might need to spice up your romance with code to make it a bit more interesting and fun. Maybe you’ve been there? You’d rather do anything except find the extra closing tag in some view. Or maybe you’ve stumbled across some particularly horrible use of google + copy and paste. Or, you might just be looking to practice. You don’t necessarily want to get on the same treadmill day after day so you’re looking for something new.

Read the rest of the article: 14 Ways To Have Fun Coding Ruby.

The Ruby movement

Matt Aimonetti writes about the “Ruby movement”, a parallel between art movements and programming and what makes Ruby special.

The programming world is much closer to the art world than you might think. Painters, sculptors, architects, singers, writers, cinematographers and photographers are recognized as artists, while programmers/coders/hackers are not there yet. One could argue that programming is more of a craft than an art, but instead of getting into semantics, let’s look at “programming movements” the same way we look at “art movements”.

Read the rest of the article: The Ruby movement.

An introduction to eventmachine, and how to avoid callback spaghetti

Martyn Loughran gives an introduction to eventmachine, and how to avoid callback spaghetti.

Evented programming has become cool lately, largely due to the very elegant node.js project. However, we’ve been evented in the Ruby world for many years thanks to eventmachine, which adds evented IO to Ruby. Writing evented code is often viewed as ‘hard’ or ‘back to front’, but it can actually be very elegant. You just need a few tricks up your sleeve.

Read the rest of the article: An introduction to eventmachine, and how to avoid callback spaghetti.

Do YOU know Ruby’s ‘Chainsaw’ method?

Paolo Perrotta loves Ruby’s ‘Chainsaw’ method – method_missing(). Paolo shows you why.

The method_missing() method is a wonderful tool for every Ruby programmer. I love it. There, I said it! Some Rubyists are surprised when I declare my love for method_missing(). They do have a point. As far as tools go, method_missing() is a chainsaw: it’s powerful, but it’s also potentially dangerous.

Read the rest of the article: Do YOU know Ruby’s ‘Chainsaw’ method?

Your turn: Share the link to a roundup post you’ve written. If you’ve never written a roundup, try it this week. Be sure to share the link to your post here!

Technorati Tags: , ,

Rubinius 1.2 Released: A Blog, Better Documentation and More of a Good Thing

rubinius.pngThree months on from the 1.1 release, Rubinius 1.2 has hit the streets bringing together 242 commits from 10 developers. As well as the typical bugfixes and performance tweaks that come with any implementation update, 1.2 brings some underlying structural changes that set up the path to getting better Windows support, Ruby 1.9 support, and the removal of the much-loathed GIL (Global Interpreter Lock) in future releases.

Documentation A Priority

1.2 also sees some leaps forward in the documentation department, led by Brian Ford of Engine Yard. There’s now multilingual documentation available on the Web in English, German, Spanish, Japanese, and Russian (but with many holes – that’s where your help is needed). There have been lots of improvements to the Rubinius site in general, including a Rubinius blog (subscribe to this if Rubinius is your bag!) and a page listing all of the projects that use Rubinius’ VM technology to power things like JavaScript, Scheme and Lua interpreters.

Brian Ford is keen to get more people involved in the Rubinius documentation effort, especially people who speak non-English languages (Japanese in particular):

Rubinius is all about empowering Ruby developers and other language developers. Even though we write a ton of the system in Ruby, we want to make it even easier to understand how to implement Ruby. The documentation project is intended to explain the ins and outs of implementing Ruby and a dynamic language VM in general.

We also think it’s very important to make the knowledge available with as little barrier to understanding as possible. The effort to translate the documentation to other languages is our attempt to make Rubinius as useful and as empowering as possible. We need your help to make this a reality.

Brian Ford

If you’re keen to help out, check out #rubinius on irc.freenode.net or check out the first post on Rubinius’ new blog on this very topic.

Want to know more?

Read the release notes or get started with Rubinius 1.2.0 right now by using rvm install rbx or one of the installers available from the Rubinius home page. Rubinius has special binary installers for Mac OS X 10.5 and 10.6 and they’re the easiest way to get going on those systems.

[A new thing from Ruby Inside] Peter Cooper, editor of Ruby Inside, is running a 4 week “introduction to Ruby” course in association with CodeLesson.com. We’ll be going from bare basics so it’s ideal for non-Rubyists you need to train up or even ‘higher ups’ who want to get into the swing of Ruby. It costs $295 and there are discounts for multiple student signups. Oh, and there’ll be plenty of opportunities for live chats and help – check it out!

A New Approach to Errors

When your app is crashed, out of resources, or misbehaving in some other way, Heroku serves error pages to describe the problem. We also have a single page for platform errors, once known as the ouchie guy (pictured right).

While the approach of showing error information directly via the web has worked well enough, there was room for improvement on both developer visibility and professionalism of presentation to end users. With that in mind, we’ve reworked our approach on error handling, making improvements that should make it much easier for you, the developer, to diagnose and correct errors in your app.

This new approach can be broken down into four features:

  1. Consolidated error page
  2. Logging of error codes
  3. Custom Error Pages add-on
  4. Improved maintenance mode

These items are now available in public beta, activated by installing the Custom Error Pages add-on. Once the public beta period is over, items 1, 2, and 4 will become the default for all apps.

Let’s take a tour of this new world of error handling.

Consolidated Error Page

An error being served in a high-traffic app is more likely to be served to an end user than to the application developer; so showing information like “backlog too deep” or “request timed out” is useless and potentially confusing to the viewer. All error pages served over HTTP are now represented by a consolidated error page, and always serve with an HTTP status code of 503.

Logging of Error Codes

Because we’re no longer putting details of the error into the page served via HTTP, you’ll need to get at them some other way. In this new model, error details are now part of your application’s log. We’ve assigned a unique code to each type of error, and they’l be placed alongside the log line produced by the HTTP router. Like this:

18:05:10 heroku[router]: Error H12 (Request timeout) -> GET myapp.heroku.com/errortest dyno=web.1 queue=0 wait=0ms service=30000ms bytes=0

In this example, the dyno spent too long servicing the request and was timed out by the router. Each type of error gets its own error code, with all HTTP errors starting with the letter H. Here’s the complete list of error codes. In the future we’ll be adding codes for other kinds of errors that can occur (for example, errors during deployment).

Note that you’ll need the new logging add-on in order to see error code logging.

Custom Error Pages

The standard error page is unbranded, but you might wish to use an error page styled to match the rest of your site. You can’t do this directly, because these error pages are generated from outside your app. (When your app is broken or overloaded, it’s impossible for it to serve any page.)

The Custom Error Pages add-on gives you the ability to specify a URL to your own error page, which can have any text and style you want. Usage is simple:

$ heroku addons:add custom_error_pages
 Adding custom_error_pages to myapp...done.
 $ heroku config:add ERROR_PAGE_URL=http://s3.amazonaws.com/your_bucket/your_error_page.html

You can put your HTML anywhere, though we recommend S3 for its simplicity and reliability. Full docs for Custom Error Pages.

Improved Maintenance Mode

The final piece of the puzzle is a new maintenance mode, to address problems with our current maintenance mode implementation for apps with more than fifty dynos. This new implementation handles maintenance mode at the HTTP router, providing an instantaneous response for turning maintenance mode on or off regardless of the size of your app. It uses a standard page which serves with an HTTP 503 code. You can use the Custom Error Pages add-on to provide your own maintenance page to the router. (If for some reason you prefer the current maintenance mode, it’s available as Rack middleware.)

Conclusion

During the beta, adding the Custom Error Pages add-on will turn on the consolidated error page, logging of error codes, and the new maintenance mode. When the beta is over, everything other than the add-on itself will become standard for all apps. Custom Error Pages will become a premium-priced feature after the beta, reflecting its nature as useful primarily to production apps with significant investment in their brand image.

We believe these changes will significantly improve the experience of dealing with errors on Heroku. Give it a try and tell us if you agree!

Our Own Domain

In this episode, Peter and Jason go over Ruby in 2010, Rails testing, lies, and beer.

Self Awareness (an exploration of the JavaScript Module Pattern)

In starting a new JavaScript plugin, my coworker (Enrico Rubboli) and I began by looking at various design patterns to determine what would best suite our needs. After a bit of research we decided to go with the self executing module pattern. For example we have something like this (edited for brevity): var Journal = […]

Being Awesome with the MongoDB Ruby Driver

Being Awesome with the MongoDB Ruby Driver

This guest post is by Ethan Gunderson, who is a software developer living in Chicago. By day he is a developer at Obtiva, where he helps clients deliver projects and be more awesome. By night, he is part of the gathers.us team, a co-organizer of ChicagoDB, and contributes when he can to the MongoDB community. You can find him at ethangunderson.com, or on Twitter as @ethangunderson.

Ethan Gunderson MongoDB is fast becoming one of the more popular and widely used NoSQL databases, and rightfully so. Its flexible key/value store, powerful query language and sexy scaling options is enough to piqué any developers interest. While most Ruby developers may jump right into the warm embrace of the Active Record replacements Mongoid and MongoMapper, that often robs developers of a valuable learning experience.

The MongoDB Ruby driver is not only simple to use, but it will get you familiar with how queries look and how they operate. Armed with this knowledge, moving into an ORM becomes much easier. You’ll not only be able to understand what is abstracted away, but you’ll be able to spot bad and inefficient generated queries, making performance troubleshooting a snap. To help you hit the ground running, we’ll be building up some of common queries you would find in a common blog. Let’s get started!

Installation

Since the driver is just a gem, installation is simple:

(sudo) gem install mongo

There is one more piece to install, bson_ext. Essentially, this is a collection of C extensions used to increase the speed of serialization. While this is optional, I recommend that you install it.

(sudo) gem install bson_ext

Now that we have everything installed that we need, lets hop into some code and see what we can do.

Getting Started

First things first, we need a database connection. For the sake of simplicity, we’ll be using localhost with the default port.

require 'mongo'
include Mongo

db = Connection.new.db('ruby-learning')

The next thing we’ll need is a place to store all of our posts. Let’s go ahead and get a post collection started.

posts = db.collection('posts')

That’s it! If you notice, there are two things we *didn’t* do that are kind of cool: we didn’t create the database or the collection. In fact, neither still exist at the database level, and won’t until we insert some data.

Inserting & Updating Documents

Let’s get our blog rolling with a high quality post.

new_post = { :title => "RubyLearning.com, its awesome", :content => "This is a pretty sweet way to learn Ruby", :created_on => Time.now }
post_id = posts.insert(new_post)

So, what did we just do? MongoDB stores its data as key/value pairs, which maps nicely to Ruby’s Hash. After creating a hash with our data, we inserted it into the posts collection, and in return, we received the ObjectId for the post from MongoDB. Pretty simple, right? It’s just as simple to update that document as well.

post = Posts.find( :_id => post_id ).first
post[:author] = "Ethan Gunderson"
posts.update( { :_id => post_id }, post )

Using the ObjectId we got back from our insert query, we find that same document again. After changing the data as we see fit, we issue an update query. An update query takes two arguments, the first one is conditions used to find the document (just the ObjectId in our case), and the second is the data.

While this works, it’s kind of silly if you think about it. We query the database for our document, change a small amount of information, and insert the entire document again. There’s gotta be a better way! Luckily, there is. MongoDB has the concept of Query Operators. One of these operators is $set, which allows you to, as I’m sure you can guess, set the value of an attribute.

posts.update( { :_id => post_id }, '$set' => { :author => 'Ethan Gunderson' } )

Here, we supply our find conditions, similarly to our previous update, but instead of supplying the entire document, we just set the values we wish to change. Now, instead of having to issue two queries against the database, we can accomplish the same task in one query, and less code to boot.

Now let’s take care of the post index page next.

posts = Posts.find

If you run this, you’ll probably notice a problem. Most of the time, blogs list their posts in descending order. Let’s change our query to account for this.

posts.find.sort( [['_id', -1]] )

This query has a couple of interesting points. Firstly, note that we are sorting on id. Since MongoDB’s ObjectId’s contain a timestamp, we can accurately sort based on that. This effectively removes the need for a created_at timestamp as well! Secondly, the sort parameter must always take an array of array, even if there is only one field you are sorting on.

So, that wasn’t so hard, but pretty naïve. What happens when we have 1,000 posts? We don’t want to torture our visitors with a ridiculous page load time, so let’s trim that back.

posts.find.sort( [['_id', -1]] ).limit(5)

Again, this was pretty simple, and by now you should be noticing a pattern. Building up relatively complex queries is just a matter of chaining methods together. To further this example, here’s a query showing a theoretical pagination query.

posts.find.sort( [['_id', -1]] ).limit(5).skip(5)

Tags

Another common element to blogs is the concept of tags. To accomplish this in our example, we’ll be adding an array of tags to our blog post.

post = Posts.find( :_id => post_id ).first
post[:tags] = ['mongo', 'nosql', 'awesome']
posts.update( { :_id => post_id }, post )

Now lets find a post based on a specific tag.

posts.find( :tags => 'mongo' )

It really doesn’t get more simple than that, folks. Now that the basic implementation is out-of-the-way, how do we find posts that match more than one tag? To accomplish this, we’ll be using another Query Operator called $all. As you can imagine, the $all operator specifies that selected documents contain all the elements in the supplied array.

posts.find( :tags => { '$all' => ['mongo', 'awesome'] } )

To round out our tags feature, let’s build a query that will list all the unique tags in our system. There are a couple of ways to skin this particular cat, since we don’t need to do any aggregation, and it needs to be performant, we’ll be using distinct. Though, if we needed to also produce a count of tag occurrences, Map/Reduce may be a better option.

posts.distinct('tags')

Indexing

Now that our blog is starting to grow in complexity, we’ll need to start thinking about adding proper indexes. If you notice in our tags implementation, we’re now querying on an attribute that is not indexed. Let’s fix that.

posts.create_index('tags')

And there we have it. In a relatively short amount of time, we’ve built up a lot of the common queries you would see in a standard blog. While I’ve only touched the surface of what you can accomplish with the MongoDB Ruby driver, I hope that I’ve shown you it’s power. I’ve included some more learning material and references below to continue your learning. Of course, if you have any questions, feel free to ask questions and give feedback in the comments section of this post. Thanks!

References

Technorati Tags: , , ,