out of time
mat brown on programming, politics, cooking, and assorted nerdiness
-
This is a good puppy.
Comments -
Sunspot 0.8 is out
CommentsOn 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:
Direct access to the Query API
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
Sunspot.new_search()andSearch#query()are exposed, and theSunspot::Queryclass 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: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 endThen in controller code, it’s as simple as:
def search @search = EventSearchBuilder.new(params).search @search.execute! endDynamic Fields
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
KeyValuePairs.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 Sunspot::Rails’s wrapper API here):
class Business < 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 => pair.value) end end end endThis 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
key_value_pairs, which is used to namespace the dynamic names that come out of the hash.Working with dynamic fields is a lot like working with regular ones, except in the query, calls are wrapped in a
dynamicblock:Business.search do dynamic :key_value_pairs do with(:cuisine, 'Sushi') facet(:atmosphere) end endNaturally, those field names (
:cuisine,:atmosphere) wouldn’t be hard-coded in a real application, since they would not be known at build time.Dirty Sessions
Sessions now track whether any operations have been performed since the last time a
commitwas issued. TheSession#dirty?method answers that question, and theSession#commit_if_dirtydoes 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.That’s all for now
Sunspot 0.9 is up next; the main goal for that version is to replace solr-ruby with RSolr as the low-level Solr interface, which will open the door to more features in future versions (query-based faceting, LocalSolr support, etc.), but probably won’t have much effect on the API for that version (other than supporting use of the faster Curb library for the HTTP communication with Solr).
-
Installing alternate Ruby versions as optional packages
CommentsAs 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.
Fortunately, the omniscient beings who created the Filesystem Hierarchy Standard anticipated this need of mine, and in their wisdom created the
/optdirectory for this purpose. Unlike a normal package installation, which installs files in various places across your file system -/usr/bin,/usr/lib,/etc,/var, and the like - optional package installations put everything into a single subdirectory of/opt, where they’re fairly isolated from the rest of the system.So, here’s how I installed YARV and JRuby as optional packages. This should work for anyone using Linux or Mac OS X1:
Download the packages
Find a nice directory for downloads.
$ wget ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.1-p129.tar.gz $ wget http://dist.codehaus.org/jruby/1.2.0/jruby-bin-1.2.0.tar.gzInstall YARV
$ 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 installInstall JRuby
$ sudo tar -C /opt -xzvf jruby-bin-1.2.0.tar.gz $ sudo rm -v /opt/jruby-1.2.0/bin/*.batYou can also remove most of the directories in the
/opt/jruby-1.2.0/lib/nativedirectory, except the one that corresponds to your architecture. If in doubt, leaving them all in won’t hurt.Installing Gems
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
gemscript using the various binaries, and it’ll work the way you want (installing the gems inside those optional package directories). For example:$ sudo /opt/ruby-1.9.1-p129/bin/ruby -S gem install rake $ sudo /opt/jruby-1.2.0/bin/jruby -S gem install rakeUsing the small
rubiesscript I covered in this post makes the process of installing gems (and doing anything else) in your various ruby versions considerably less painful.1If you use MacPorts, which you probably do, you’ve got a bunch of software installed in a standard hierarchy inside of the
/opt/localdirectory. That isn’t really the way it was intended to be used, but it won’t conflict with the installations covered in this post. -
A 15-line alternative to multiruby
CommentsToday’s mission was to get Sunspot 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 multiruby, 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.
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.
So, I went ahead and just installed YARV and JRuby as optional packages, 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:
#!/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 endThen I just set up my ~/.rubies file, containing full paths to my various Ruby binaries:
/usr/local/bin/ruby /opt/ruby-1.9.1-p129/bin/ruby /opt/jruby-1.2.0/bin/jrubyAnd a quick
rubies -S rakeruns 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. -
Git Tip of the Day: git cherry-tree
CommentsThe problem
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.
The Solution
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:
git config --global alias.cherry-tree \!sh\ -c\ \'git-format-patch\ --stdout\ \$0..\$1\ \|\ \git\ am\ -3\'How to use it
For the sake of this example, we’ll call our branches
qaandmaster. My goal is to branchqa, make a series of commits, and then apply the changes in those commits tomasteras well. The result should be equivalent to individually cherry-picking the commits that I’ve made, but easier and more reliable. Here we go: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-changesThat 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:
# open config/environment.rb and fix the conflict git add config/environment.rb git am -3 --resolvedNote that, unlike with a normal merge conflict, you don’t 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).
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:
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-changesAs 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.
-
ShellElf runs shell commands from a Starling queue
ShellElf is a small daemon that reads shell commands out of a Starling queue and runs them. Great if you need to do non-trivial processing tasks in the background. It’s lightweight but has a few neat features:
- Post back to a specified HTTP service on success/failure for each command batch
- Exit gracefully on TERM/INT - either finish the current batch, or reenqueue it, before exiting.
- Retry Starling connection indefinitely if unable to connect on the first try - never exit silently.
-
rake db:rollback_to_common
If you find yourself having to switch locally between different git branches that have different database schemata, (for instance, a master branch and a production branch), one of the biggest hassles is keeping the database in the right state. We wrote this rake task to make the process painless: just specify the branch you’re about to switch to, and it will automatically roll back to the last common database version between that and your current branch. Usage:
rake db:rollback_to_common branch=production git checkout -m productionIt will raise an exception if nothing needs to be done, which doesn’t really bother me, but if it bothers you, feel free to edit - it’s a gist.
Comments -
Using Git Submodules for Shared Rails Components
I’ve been thinking for a while about how nice it would be to share the model layer of our Rails app with other codebases that don’t have to load up all of Rails. In particular, this would neatly allow one to avoid using Workling (which essentially runs the entire Rails stack as a Daemon), instead building lightweight queue-reading daemon processes that still have access to the model layer (since this is, in pretty much any case I can think of, the only part of the Rails stack that a background-job processor would need access to).
Comments -
Where are the REST frameworks?
I’ve been thinking a lot about REST lately - what it means for Rails developers, and what it means in the broader scheme of things (the two are scarcely related). This article is an interesting addition to the discussion, and I’m especially intrigued by the treatment of hyperlinking and its relationship with the traditional Rails approach of polymorphic associations.
Comments -
Using Ruby's Autoload Method To Configure Your App Just-in-Time
Good primer on Ruby’s autoload keyword, which makes it easy to only load pieces of a library or application when you need them
Comments