<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"><channel><atom:link rel="hub" href="http://tumblr.superfeedr.com/" xmlns:atom="http://www.w3.org/2005/Atom"/><description>mat brown on programming</description><title>out of time (mat brown on programming)</title><generator>Tumblr (3.0; @0utoftime)</generator><link>http://outofti.me/</link><item><title>ActiveModelListener: Easy to use global ActiveRecord event listeners</title><description>&lt;a href="http://pivotallabs.com/users/jdean/blog/articles/1020-introducing-activemodellistener-easy-to-use-global-activerecord-event-listeners"&gt;ActiveModelListener: Easy to use global ActiveRecord event listeners&lt;/a&gt;</description><link>http://outofti.me/post/896277302</link><guid>http://outofti.me/post/896277302</guid><pubDate>Mon, 02 Aug 2010 23:54:27 -0400</pubDate></item><item><title>patmaddox's no-peeping-toms at master - GitHub</title><description>&lt;a href="http://github.com/patmaddox/no-peeping-toms"&gt;patmaddox's no-peeping-toms at master - GitHub&lt;/a&gt;</description><link>http://outofti.me/post/896277281</link><guid>http://outofti.me/post/896277281</guid><pubDate>Mon, 02 Aug 2010 23:54:27 -0400</pubDate></item><item><title>Raphaël—JavaScript Library</title><description>&lt;a href="http://raphaeljs.com/"&gt;Raphaël—JavaScript Library&lt;/a&gt;</description><link>http://outofti.me/post/876272573</link><guid>http://outofti.me/post/876272573</guid><pubDate>Thu, 29 Jul 2010 15:26:26 -0400</pubDate></item><item><title>Baja Fish Tacos</title><description>&lt;a href="http://www.epicurious.com/recipes/food/views/Baja-Fish-Tacos-352509"&gt;Baja Fish Tacos&lt;/a&gt;</description><link>http://outofti.me/post/861895705</link><guid>http://outofti.me/post/861895705</guid><pubDate>Mon, 26 Jul 2010 12:40:22 -0400</pubDate></item><item><title>Deviled Eggs</title><description>&lt;a href="http://www.epicurious.com/recipes/food/views/Deviled-Eggs-106562"&gt;Deviled Eggs&lt;/a&gt;</description><link>http://outofti.me/post/858472677</link><guid>http://outofti.me/post/858472677</guid><pubDate>Sun, 25 Jul 2010 18:23:15 -0400</pubDate></item><item><title>Menu -- Grilled Pancetta Wrapped Asparagus, Grilled Scallops &amp; Nectarines with Corn and Tomato Salad</title><description>&lt;a href="http://www.epicurious.com/recipes/menu/views/summersupperforsix"&gt;Menu -- Grilled Pancetta Wrapped Asparagus, Grilled Scallops &amp; Nectarines with Corn and Tomato Salad&lt;/a&gt;</description><link>http://outofti.me/post/858472694</link><guid>http://outofti.me/post/858472694</guid><pubDate>Sun, 25 Jul 2010 18:23:15 -0400</pubDate></item><item><title>Fluffy Pancakes</title><description>&lt;a href="http://allrecipes.com/Recipe/Fluffy-Pancakes-2/Detail.aspx"&gt;Fluffy Pancakes&lt;/a&gt;</description><link>http://outofti.me/post/858472648</link><guid>http://outofti.me/post/858472648</guid><pubDate>Sun, 25 Jul 2010 18:23:15 -0400</pubDate></item><item><title>The Road to Passenger 3: Technology Preview 2 – Stability, robustness, availability, self-healing – Phusion Corporate Blog</title><description>&lt;a href="http://blog.phusion.nl/2010/06/18/the-road-to-passenger-3-technology-preview-2-stability-robustness-availability-self-healing/"&gt;The Road to Passenger 3: Technology Preview 2 – Stability, robustness, availability, self-healing – Phusion Corporate Blog&lt;/a&gt;</description><link>http://outofti.me/post/759822418</link><guid>http://outofti.me/post/759822418</guid><pubDate>Thu, 01 Jul 2010 22:23:00 -0400</pubDate></item><item><title>The Road to Passenger 3: Technology Preview 3 – Closing the gap between development and production &amp; rethinking the word “easy” – Phusion Corporate Blog</title><description>&lt;a href="http://blog.phusion.nl/2010/07/01/the-road-to-passenger-3-technology-preview-3-closing-the-gap-between-development-and-production-rethinking-the-word-easy/"&gt;The Road to Passenger 3: Technology Preview 3 – Closing the gap between development and production &amp; rethinking the word “easy” – Phusion Corporate Blog&lt;/a&gt;</description><link>http://outofti.me/post/759822337</link><guid>http://outofti.me/post/759822337</guid><pubDate>Thu, 01 Jul 2010 22:23:00 -0400</pubDate></item><item><title>The Road to Passenger 3: Technology Preview 1 – Performance – Phusion Corporate Blog</title><description>&lt;a href="http://blog.phusion.nl/2010/06/10/the-road-to-passenger-3-technology-preview-1-performance-2/"&gt;The Road to Passenger 3: Technology Preview 1 – Performance – Phusion Corporate Blog&lt;/a&gt;</description><link>http://outofti.me/post/759822427</link><guid>http://outofti.me/post/759822427</guid><pubDate>Thu, 01 Jul 2010 22:23:00 -0400</pubDate></item><item><title>In Defense of The Memory Theater | Open Letters Monthly - an Arts and Literature Review</title><description>&lt;a href="http://www.openlettersmonthly.com/in-defense-of-the-memory-theater/"&gt;In Defense of The Memory Theater | Open Letters Monthly - an Arts and Literature Review&lt;/a&gt;</description><link>http://outofti.me/post/758899605</link><guid>http://outofti.me/post/758899605</guid><pubDate>Thu, 01 Jul 2010 17:05:00 -0400</pubDate></item><item><title>QDB: Quote #925050</title><description>&lt;a href="http://bash.org/?925050"&gt;QDB: Quote #925050&lt;/a&gt;</description><link>http://outofti.me/post/757924661</link><guid>http://outofti.me/post/757924661</guid><pubDate>Thu, 01 Jul 2010 11:29:22 -0400</pubDate></item><item><title>ruote</title><description>&lt;a href="http://openwferu.rubyforge.org/"&gt;ruote&lt;/a&gt;: &lt;p&gt;Workflow engine written in Ruby&lt;/p&gt;</description><link>http://outofti.me/post/136636735</link><guid>http://outofti.me/post/136636735</guid><pubDate>Mon, 06 Jul 2009 17:21:31 -0400</pubDate></item><item><title>redis is a key-value data store</title><description>&lt;a href="http://code.google.com/p/redis/"&gt;redis is a key-value data store&lt;/a&gt;</description><link>http://outofti.me/post/124126085</link><guid>http://outofti.me/post/124126085</guid><pubDate>Mon, 15 Jun 2009 15:50:13 -0400</pubDate></item><item><title>Sunspot 0.8 is out</title><description>&lt;p&gt;On Friday, I released the next milestone in Sunspot, version 0.8. This version doesn’t add to or change any of the basic functionality, but does add some advanced features which the app I work on for my day job happens to demand. Here’s a rundown:&lt;/p&gt;

&lt;h3&gt;Direct access to the Query API&lt;/h3&gt;

&lt;p&gt;Users of Sunspot will doubless be familiar with Sunspot’s search DSL, which gives an English-like interface for constructing search parameters. In some cases, however, such a DSL is actually counterproductive, particularly when searches are being built by an intermediate object, and thus not necessarily all in one place. So, the new methods &lt;code&gt;Sunspot.new_search()&lt;/code&gt; and &lt;code&gt;Search#query()&lt;/code&gt; are exposed, and the &lt;code&gt;Sunspot::Query&lt;/code&gt; class itself is now part of the public API. What I have in mind in particular here is an application of the Go4 Builder pattern, along with ActiveRecord’s hash-initializer pattern, to elegantly translate web query parameters into a Sunspot search. Here’s a stripped-down example of what I think the code will look like to do that:&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;class EventSearchBuilder
  attr_reader :search


  def initialize(options = {})
    @search = Sunspot.new_search(Event)
    options.each_pair do |attr, value|
      if respond_to?("#{attr}=")
        send("#{attr}=", value)
      end
    end
  end


  def when=(day_string)
    case day_string
    when 'future'
      @search.query.add_restriction(:start_time, :greater_than, Time.now)
    when 'past'
      @search.query.add_restriction(:start_time, :less_than, Time.now)
    else
      date_time = Date.parse(day_string).to_time
      @search.query.add_restriction(:start_time, :between, date_time..(date_time + 1.day))
    end
  end


  def page=(page)
    @search.query.paginate(page)
  end


  def sort=(field)
    @search.query.order_by(field)
  end
end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then in controller code, it’s as simple as:&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;def search
  @search = EventSearchBuilder.new(params).search
  @search.execute!
end&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Dynamic Fields&lt;/h3&gt;

&lt;p&gt;I wouldn’t be surprised if I’m the only person who ever uses this feature of Sunspot, but just in case, let’s look at a real-world example. Let’s say part of my data model uses free-form key-value pairs, which use a constrained (but user-definable) set of keys and free-form values. I’ll call my model &lt;code&gt;KeyValuePairs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The trick I would like to pull here is that I would like to treat each key as a separate field in search, so that I can constrain, order, facet, etc. on the values for one key without them being affected by other keys. Since the keys are user-defined, I can’t just set up normal fields at build time; they need to be defined at index time. Enter Sunspot’s dynamic fields (we’ll use &lt;a href="http://github.com/outoftime/sunspot_rails" target="_blank"&gt;Sunspot::Rails&lt;/a&gt;’s wrapper API here):&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;class Business &lt; ActiveRecord::Base
  has_many :key_value_pairs


  searchable do
    dynamic_string :key_value_pairs do
      key_value_pairs.inject({}) do |hash, pair|
        hash.merge(pair.key.to_sym =&gt; pair.value)
      end
    end
  end
end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This sets up a dynamic field which is populated using the given block. What’s important there is that the field is populated using a hash - the keys of the hash become individual dynamic fields, and the values populate those fields in the index. The “base name” of the field is &lt;code&gt;key_value_pairs&lt;/code&gt;, which is used to namespace the dynamic names that come out of the hash.&lt;/p&gt;

&lt;p&gt;Working with dynamic fields is a lot like working with regular ones, except in the query, calls are wrapped in a &lt;code&gt;dynamic&lt;/code&gt; block:&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;Business.search do
  dynamic :key_value_pairs do
    with(:cuisine, 'Sushi')
    facet(:atmosphere)
  end
end&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Naturally, those field names (&lt;code&gt;:cuisine&lt;/code&gt;, &lt;code&gt;:atmosphere&lt;/code&gt;) wouldn’t be hard-coded in a real application, since they would not be known at build time.&lt;/p&gt;

&lt;h3&gt;Dirty Sessions&lt;/h3&gt;

&lt;p&gt;Sessions now track whether any operations have been performed since the last time a &lt;code&gt;commit&lt;/code&gt; was issued. The &lt;code&gt;Session#dirty?&lt;/code&gt; method answers that question, and the &lt;code&gt;Session#commit_if_dirty&lt;/code&gt; does exactly what it sounds like. Useful methods if you want to keep your commits to a minimum (you do) but you may have various parts of the code issuing Sunspot operations without any central knowledge on the part of your application.&lt;/p&gt;

&lt;h3&gt;That’s all for now&lt;/h3&gt;

&lt;p&gt;Sunspot 0.9 is up next; the main goal for that version is to replace solr-ruby with &lt;a href="http://github.com/mwmitchell/rsolr/tree/master" target="_blank"&gt;RSolr&lt;/a&gt; as the low-level Solr interface, which will open the door to more features in future versions (query-based faceting, &lt;a href="http://www.gissearch.com/localsolr" target="_blank"&gt;LocalSolr&lt;/a&gt; support, etc.), but probably won’t have much effect on the API for that version (other than supporting use of the faster &lt;a href="http://curb.rubyforge.org/" target="_blank"&gt;Curb&lt;/a&gt; library for the HTTP communication with Solr).&lt;/p&gt;</description><link>http://outofti.me/post/113429517</link><guid>http://outofti.me/post/113429517</guid><pubDate>Tue, 26 May 2009 17:58:00 -0400</pubDate><category>sunspot</category></item><item><title>Installing alternate Ruby versions as optional packages</title><description>&lt;p&gt;As a developer of Ruby libraries and applications, I’d like to make sure my code works in all of the major ruby implementations, but I’ve also got my “main” Ruby, the one that has been with me through thick and thin and happens to be the version installed on our production servers. The other Rubies need a place on my machine, but I’d like that place to be out of the way and have no chance of conflicting with my main Ruby installation or anything else I’ve got installed.&lt;/p&gt;

&lt;p&gt;Fortunately, the omniscient beings who created the &lt;a href="http://www.pathname.com/fhs/pub/fhs-2.3.html" target="_blank"&gt;Filesystem Hierarchy Standard&lt;/a&gt; anticipated this need of mine, and in their wisdom created the &lt;code&gt;/opt&lt;/code&gt; directory for this purpose. Unlike a normal package installation, which installs files in various places across your file system - &lt;code&gt;/usr/bin&lt;/code&gt;, &lt;code&gt;/usr/lib&lt;/code&gt;, &lt;code&gt;/etc&lt;/code&gt;, &lt;code&gt;/var&lt;/code&gt;, and the like - optional package installations put everything into a single subdirectory of &lt;code&gt;/opt&lt;/code&gt;, where they’re fairly isolated from the rest of the system.&lt;/p&gt;

&lt;p&gt;So, here’s how I installed YARV and JRuby as optional packages. This should work for anyone using Linux or Mac OS X&lt;sup&gt;1&lt;/sup&gt;:&lt;/p&gt;

&lt;h3&gt;Download the packages&lt;/h3&gt;

&lt;p&gt;Find a nice directory for downloads.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ wget &lt;a href="ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.1-p129.tar.gz" target="_blank"&gt;ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.1-p129.tar.gz&lt;/a&gt;
$ wget &lt;a href="http://dist.codehaus.org/jruby/1.2.0/jruby-bin-1.2.0.tar.gz" target="_blank"&gt;http://dist.codehaus.org/jruby/1.2.0/jruby-bin-1.2.0.tar.gz&lt;/a&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Install YARV&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ sudo mkdir -pv /opt/ruby-1.9.1-p129
$ tar xzvf ruby-1.9.1-p129.tar.gz
$ cd ruby-1.9.1-p129
$ ./configure --prefix=/opt/ruby-1.9.1-p129
$ make
$ sudo make install
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Install JRuby&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ sudo tar -C /opt -xzvf jruby-bin-1.2.0.tar.gz
$ sudo rm -v /opt/jruby-1.2.0/bin/*.bat
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can also remove most of the directories in the &lt;code&gt;/opt/jruby-1.2.0/lib/native&lt;/code&gt; directory, except the one that corresponds to your architecture. If in doubt, leaving them all in won’t hurt.&lt;/p&gt;

&lt;h3&gt;Installing Gems&lt;/h3&gt;

&lt;p&gt;Assuming you’ve got RubyGems installed in your main Ruby installation, you don’t need to install it for your other installations - you can simply run the existing &lt;code&gt;gem&lt;/code&gt; script using the various binaries, and it’ll work the way you want (installing the gems inside those optional package directories). For example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ sudo /opt/ruby-1.9.1-p129/bin/ruby -S gem install rake
$ sudo /opt/jruby-1.2.0/bin/jruby -S gem install rake
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Using the small &lt;code&gt;rubies&lt;/code&gt; script I covered in &lt;a href="http://outofti.me/post/111117383/a-15-line-alternative-to-multiruby" target="_blank"&gt;this post&lt;/a&gt; makes the process of installing gems (and doing anything else) in your various ruby versions considerably less painful.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;sup&gt;1&lt;/sup&gt;If you use MacPorts, which you probably do, you’ve got a bunch of software installed in a standard hierarchy inside of the &lt;code&gt;/opt/local&lt;/code&gt; directory. That isn’t really the &lt;a href="http://www.pathname.com/fhs/pub/fhs-2.3.html#OPTADDONAPPLICATIONSOFTWAREPACKAGES" target="_blank"&gt;way it was intended to be used&lt;/a&gt;, but it won’t conflict with the installations covered in this post.&lt;/small&gt;&lt;/p&gt;</description><link>http://outofti.me/post/111132650</link><guid>http://outofti.me/post/111132650</guid><pubDate>Thu, 21 May 2009 17:09:00 -0400</pubDate></item><item><title>A 15-line alternative to multiruby</title><description>&lt;p&gt;Today’s mission was to get &lt;a href="http://outoftime.github.com/sunspot" target="_blank"&gt;Sunspot&lt;/a&gt; working in all the major Ruby implementations (MRI, YARV, JRuby). I personally use MRI 1.8.6p114, and hadn’t had a need to install any other Ruby implementations, so I first tried out &lt;a href="http://www.zenspider.com/ZSS/Products/ZenTest/index.html" target="_blank"&gt;multiruby&lt;/a&gt;, which handles all of the installing and running of different Ruby versions. Alas, it didn’t work very well - after all, package management is a nontrivial problem, and multiruby does attempt to perform that function in a sense. The packages didn’t install.&lt;/p&gt;

&lt;p&gt;I also wasn’t a big fan of installing a whole filesystem hierarchy inside a hidden directory in my home directory (some people like this — follow your own path, young jedi); and it required I install the entire ZenTest gem, which I otherwise have not found much use for.&lt;/p&gt;

&lt;p&gt;So, I went ahead and just &lt;a href="http://outofti.me/post/111132650/installing-alternate-ruby-versions-as-optional-packages" target="_blank"&gt;installed YARV and JRuby as optional packages&lt;/a&gt;, installed gems for them, ran the spec suite under each version, and fixed the bugs that came up. Great! But I did notice that this involved a lot of typing out full paths to the various Ruby binaries, particularly since I will want to be running the specs under all the versions from now on. This is not a difficult problem to solve, I thought to myself. Enter quick 15-line script:&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;#!/usr/bin/env ruby
require 'rubygems'
gem 'escape'
require 'escape'

File.open(File.join(ENV['HOME'], '.rubies')) do |file|
  file.each_line do |bin|
    bin.sub!(/\n$/, '')
    STDERR.puts("Executing in #{`#{Escape.shell_command([bin, '-v'])}`}")
    fork do
      exec(Escape.shell_command([bin].concat(ARGV)))
    end
    Process.wait
  end
end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then I just set up my ~/.rubies file, containing full paths to my various Ruby binaries:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;/usr/local/bin/ruby
/opt/ruby-1.9.1-p129/bin/ruby
/opt/jruby-1.2.0/bin/jruby
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And a quick &lt;code&gt;rubies -S rake&lt;/code&gt; runs my suites in all the relevant ruby versions. Genius? No, but it gives me all the useful functionality of multiruby with the control over installation that I crave. Hopefully it’ll help you too.&lt;/p&gt;</description><link>http://outofti.me/post/111117383</link><guid>http://outofti.me/post/111117383</guid><pubDate>Thu, 21 May 2009 16:27:00 -0400</pubDate></item><item><title>Sunspot 0.7 is out</title><description>&lt;p&gt;Momentous news: Yesterday I released the 0.7 version of &lt;a href="http://github.com/outoftime/sunspot" target="_blank"&gt;Sunspot&lt;/a&gt;, my library for awesome Solr interaction in pure Ruby. This is the first release that I consider basically feature complete, meaning there aren’t any gaping holes in the feature set - not that there aren’t more features in the pipeline! Read on for all the new goodies.&lt;/p&gt;

&lt;h3&gt;Documentation!&lt;/h3&gt;

&lt;p&gt;Sunspot is now fully documented. In order to distinguish the public and private APIs, I made liberal use of :nodoc: on classes and methods that are not part of the public API. So, everything in the RDoc is fair game; if you find yourself needing to call methods that aren’t in the RDoc, let me know so I can expose what you need in the public API.&lt;/p&gt;

&lt;p&gt;API-private methods are still documented in the code.&lt;/p&gt;

&lt;h3&gt;Less magic in the search DSL&lt;/h3&gt;

&lt;p&gt;In earlier versions, the search DSL looked like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;Sunspot.search(Post) do
  with.blog_id 1
  with.average_rating.less_than 4
end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now it looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;Sunspot.search(Post) do
  with :blog_id, 1
  with(:average_rating).less_than 4
end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I find the new syntax to be more intuitive, and my colleagues whom I polled unanimously agreed. Sometimes Ruby developers get perhaps a bit too excited about how cleverly one can construct English-like DSLs in the language, and I would plead guilty to that charge where the earlier version is concerned.&lt;/p&gt;

&lt;h3&gt;Negative scoping&lt;/h3&gt;

&lt;p&gt;The search DSL now provides &lt;code&gt;without&lt;/code&gt;, which is a counterpart to the &lt;code&gt;with&lt;/code&gt; method that negates the restriction. So, you can do the following:&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;Sunspot.search(Post) do
  without :blog_id, 2
end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That would exclude all posts whose blog_id is 2.&lt;/p&gt;

&lt;h4&gt;Exclusion by identity&lt;/h4&gt;

&lt;p&gt;A special use of the &lt;code&gt;without&lt;/code&gt; method is excluding specific objects from the search results. This is done by just passing the objects you want to exclude to &lt;code&gt;without&lt;/code&gt;. For example, if you have an instance &lt;code&gt;current_post&lt;/code&gt; that you don’t want in the search results:&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;Sunspot.search(Post) do
  without current_post
end&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Restrict by empty values&lt;/h3&gt;

&lt;p&gt;You can now pass &lt;code&gt;nil&lt;/code&gt; to an equality restriction, which will restrict the results to documents for which the given field has no value. For example:&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;Sunspot.search(Post) do
  with :category_id, nil
end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The above would return only documents that do not have a category_id. &lt;code&gt;without&lt;/code&gt; works as expected, returning only documents that do have a value for the field in question. Passing nil to other restriction types is not allowed.&lt;/p&gt;

&lt;h3&gt;Faceting&lt;/h3&gt;

&lt;p&gt;One of Solr’s most powerful features is faceting, which returns all of the values stored for a given field, and the number of documents that have each value. It’s perfect for building drill-down search interfaces. Sunspot now supports it:&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;search = Sunspot.search(Post) do
  with :blog_id, 2
  facet :category_ids
end

category_ids_facet = search.facet(:category_ids)
category_ids_facet.rows.map { |row| [row.value, row.count] }
  #=&gt; [[25, 3], [13, 1]]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The above results indicate that there are 3 documents with blog_id 2 and category_id 25, and 1 document with blog_id 2 and category_id 13. Note that facet results are for the total number of documents that match the search conditions, not just the current result set (which is paginated). Note also that Sunspot casts facets into the appropriate Ruby object for their type - thus a time field’s facet values will be Time objects, etc.&lt;/p&gt;

&lt;p&gt;Solr also provides even more powerful query-based faceting (to facet by ranges of values, for instance), which I plan to tackle in a future version of Sunspot.&lt;/p&gt;

&lt;h3&gt;Explicit commits&lt;/h3&gt;

&lt;p&gt;Changing data in Solr is a two-step process - first, data is added, updated, or removed; then the changes are committed. When a commit is called, all pending changes are written to disk, and Solr instantiates a new searcher object with the updated index. In order for changes to appear in search, they must be committed, but the commit is a fairly expensive operation; thus, if you are making multiple updates as part of one operation, it’s highly advisable to commit once, after making all of the changes. Earlier versions of Sunspot automatically committed after each change; now a &lt;code&gt;commit&lt;/code&gt; method is exposed, as well as bang!-versions of the update methods, which perform a commit immediately. So:&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;Sunspot.index(my_document)
Sunspot.commit
# does the same thing as
Sunspot.index!(my_document)&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Boolean field type&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;boolean&lt;/code&gt; is now an available field type. It works pretty much as you’d expect. Note that in order for a &lt;code&gt;false&lt;/code&gt; value to be indexed, it has to explicitly be &lt;code&gt;false&lt;/code&gt; — &lt;code&gt;nil&lt;/code&gt; will not be indexed at all. Anything else is indexed as &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;Attribute field flexibility&lt;/h3&gt;

&lt;p&gt;You can now tell an attribute field to pull data from an attribute other than the one named by the field. For example:&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;Sunspot.setup(Post) do
  float :average_rating, :using =&gt; :ratings_average
end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This would index a field called &lt;code&gt;average_rating&lt;/code&gt;, pulling data from the &lt;code&gt;ratings_average&lt;/code&gt; method.&lt;/p&gt;

&lt;h3&gt;Virtual field evaluation flexibility&lt;/h3&gt;

&lt;p&gt;If the block specified for a virtual field takes an argument, the block will be passed the instance, rather than being evaluated in its context:&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;Sunspot.setup(Post) do
  string :sort_title do
    title.downcase
  end
  # is the same as
  string :sort_title do |post|
    post.title.downcase
  end
end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Use whichever one feels more natural for the given object - for instance, I would use the first form for a model I had written, but the second form for File objects.&lt;/p&gt;

&lt;h3&gt;Order by multiple fields&lt;/h3&gt;

&lt;p&gt;You can now call &lt;code&gt;order&lt;/code&gt; inside the search DSL more than once. Earlier calls get higher precedence.&lt;/p&gt;

&lt;h3&gt;New adapter API&lt;/h3&gt;

&lt;p&gt;Sunspot is intended to be flexible in what it can index and search; to that end, it provides a pluggable adapter architecture. Sunspot 0.7 makes several changes to the adapter API; this should be the final API that goes into the 1.0 release.&lt;/p&gt;

&lt;p&gt;Instead of building a single adapter module that contains two classes with preset names, the new API allows (and indeed requires) the two classes to be registered seperately. An adapter should consist of two classes: a subclass of &lt;code&gt;Sunspot::Adapters::InstanceAdapter&lt;/code&gt;, and a subclass of &lt;code&gt;Sunspot::Adapters::DataAccessor&lt;/code&gt;. Check out the Rdoc for information on what methods each should and can implement. Here’s an example of how one might build an adapter for File objects:&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;class FileInstanceAdapter &lt; Sunspot::Adapters::InstanceAdapter
  def id
    File.expand_path(@instance.path)
  end
end

class FileDataAccessor &lt; Sunspot::Adapters::DataAccessor
  def load(id)
    @clazz.open(id)
  end
end

Sunspot::Adapters::InstanceAdapter.register(FileInstanceAdapter, File)
Sunspot::Adapters::DataAccessor.register(FileDataAccessor, File)&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Goodbye Builder API&lt;/h3&gt;

&lt;p&gt;The last release of Sunspot attempted to provide a framework for using the Builder pattern to convert external parameters into searches. Upon further reflection, I found the API and implementation rather awkward, and it wasn’t really a core part of what Sunspot was trying to do. The &lt;code&gt;search&lt;/code&gt; method still can accept a hash of parameters, but you really shouldn’t use that because the DSL is way better.&lt;/p&gt;

&lt;p&gt;This also means that, for the moment, Search objects don’t provide access to the parameters passed in. That’s probably not a good thing, so I’ll try and find a clean, intuitive way to provide that access in a new version.&lt;/p&gt;

&lt;h3&gt;Goodbye extlib&lt;/h3&gt;

&lt;p&gt;Extlib is cool, but Sunspot was only using three methods from it, and it was causing some sort of weird error when I tried to run the tests on the installed gem. So I implemented the three methods myself and got rid of the extlib dependency.&lt;/p&gt;

&lt;h3&gt;That’s all, folks&lt;/h3&gt;

&lt;p&gt;That should just about cover all of the external-facing changes in the new version. There’s also lots of internal refactoring and simplification that should make the code leaner, faster, and more maintainable. This is the first version that I would feel comfortable putting into a production environment - if you’re using it, &lt;a href="mailto:mat@patch.com" target="_blank"&gt;I’d love to know&lt;/a&gt;.&lt;/p&gt;</description><link>http://outofti.me/post/101499111</link><guid>http://outofti.me/post/101499111</guid><pubDate>Wed, 29 Apr 2009 12:10:00 -0400</pubDate><category>Sunspot</category></item><item><title>RabbitMQ and Ruby</title><description>&lt;a href="http://www.rubyinside.com/rabbitmq-a-fast-reliable-queuing-option-for-rubyists-1681.html"&gt;RabbitMQ and Ruby&lt;/a&gt;: &lt;p&gt;The day when we ditch Starling for RabbitMQ will be a good day.&lt;/p&gt;</description><link>http://outofti.me/post/94595155</link><guid>http://outofti.me/post/94595155</guid><pubDate>Thu, 09 Apr 2009 14:58:10 -0400</pubDate></item><item><title>Git Tip of the Day: git cherry-tree</title><description>&lt;h3&gt;The problem&lt;/h3&gt;

&lt;p&gt;Recently I came across a situation in which I needed to make some fairly major changes to our codebase, and those changes needed to apply to two different branches - both a version branch that’s currently in QA, and the master branch. The changes involved reverting a few commits as well as making new changes that were too big to comfortably fit into one commit. One option would be to simply make all of the commits in one branch, and then cherry-pick them by hand into the other, but that seemed awfully manual for such a powerful tool as git.&lt;/p&gt;

&lt;h3&gt;The Solution&lt;/h3&gt;

&lt;p&gt;Enter git cherry-tree, a little alias I came up with that basically creates a series of patches based on the commit diff between two branches, and pipes the result directly into a third branch. Before I explain how to use it, here’s how to add the alias to your config:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;git config --global alias.cherry-tree \!sh\ -c\ \'git-format-patch\ --stdout\ \$0..\$1\ \|\ \git\ am\ -3\'
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;How to use it&lt;/h3&gt;

&lt;p&gt;For the sake of this example, we’ll call our branches &lt;code&gt;qa&lt;/code&gt; and &lt;code&gt;master&lt;/code&gt;. My goal is to branch &lt;code&gt;qa&lt;/code&gt;, make a series of commits, and then apply the changes in those commits to &lt;code&gt;master&lt;/code&gt; as well. The result should be equivalent to individually cherry-picking the commits that I’ve made, but easier and more reliable. Here we go:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;git checkout qa
git checkout -b qa-big-changes
# make and commit the changes
git checkout master
git checkout -b master-big-changes
git cherry-tree qa qa-big-changes
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That last command says, essentially, “Sequentially apply each commit that is in qa-big-changes, but not qa, to the current branch.” Unless you’re a naturally lucky person, you’ll probably have conflicts - when this happens, the process will stop midstream, telling you which files are in conflict. Let’s say config/environment.rb is in conflict:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# open config/environment.rb and fix the conflict
git add config/environment.rb
git am -3 --resolved
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note that, unlike with a normal merge conflict, you &lt;em&gt;don’t&lt;/em&gt; want to commit after fixing the conflicts - just add the conflicted files to the index. The last command just says, “OK, problem solved, pick up where you left off.” Note also that, since each commit from your other branch is applied individually, this can happen more than once (of course, with different conflicts, in different commits).&lt;/p&gt;

&lt;p&gt;Once the entire patch has run cleanly, master-big-changes will have a series of commits equivalent to the commits that you made to qa-big-changes (they won’t be the same commits, though - just like a cherry-pick). Then you can merge your changes into the respective branches:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;git checkout qa
git merge qa-big-changes
git branch -d qa-big-changes
git checkout master
git merge master-big-changes
git branch -d master-big-changes
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As a final note, the use of master-big-changes isn’t strictly necessary - you can just do it directly in master. I just feel safer doing this process to a separate branch and then merging it in. However, making your original set of changes in qa-big-changes, rather than directly in qa, is required for this process.&lt;/p&gt;</description><link>http://outofti.me/post/94537694</link><guid>http://outofti.me/post/94537694</guid><pubDate>Thu, 09 Apr 2009 11:20:31 -0400</pubDate></item></channel></rss>
