diff --git a/.travis.yml b/.travis.yml
index 2328d03275b8109b1756831e06a756df2b409ea4..dddf7601273e6555cbd6ec99274c48a76b644e6d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -60,13 +60,26 @@ matrix:
     - compiler: clang
       language: dotnet
       env: BINDINGS=dotnet CC=clang
+    - compiler: gcc
+      language: ruby
+      rvm:
+        - ruby-1.9.3-p484
+        - ruby-2.0.0-p353
+        - ruby-2.1.0
+      env: BINDINGS=ruby
+    - compiler: clang
+      language: ruby
+      rvm:
+        - ruby-1.9.3-p484
+        - ruby-2.0.0-p353
+        - ruby-2.1.0
+      env: BINDINGS=ruby CC=clang
 before_install:
   - sudo apt-get update -qq
   - if [ "$BINDINGS" != "none" ]; then sudo apt-get install -qq swig; fi
   - if [ "$BINDINGS" == "perl" ]; then sudo add-apt-repository ppa:dns/irc -y; sudo apt-get update -qq; sudo apt-get install -qq swig=2.0.8-1irc1~12.04; fi
   - if [ "$BINDINGS" == "python" ]; then sudo apt-get install -qq python-dev; fi
   - if [ "$BINDINGS" == "dotnet" ]; then sudo add-apt-repository ppa:directhex/monoxide -y; sudo apt-get update -qq; sudo apt-get install -qq mono-devel mono-mcs nunit nunit-console; mozroots --import --sync; fi
-
 install: true
 before_script:
   - if [ "$BINDINGS" == "php" ]; then phpenv config-add src/bindings/php/hammer.ini; fi
diff --git a/SConstruct b/SConstruct
index 17b1009e0f2121a07de9bc94da8d889e17d91e6f..fe1c78ef381cbe1b92d9c382299fe8fc65f7071b 100644
--- a/SConstruct
+++ b/SConstruct
@@ -7,7 +7,7 @@ import sys
 vars = Variables(None, ARGUMENTS)
 vars.Add(PathVariable('DESTDIR', "Root directory to install in (useful for packaging scripts)", None, PathVariable.PathIsDirCreate))
 vars.Add(PathVariable('prefix', "Where to install in the FHS", "/usr/local", PathVariable.PathAccept))
-vars.Add(ListVariable('bindings', 'Language bindings to build', 'none', ['dotnet', 'perl', 'php', 'python']))
+vars.Add(ListVariable('bindings', 'Language bindings to build', 'none', ['dotnet', 'perl', 'php', 'python', 'ruby']))
 
 env = Environment(ENV = {'PATH' : os.environ['PATH']},
                   variables = vars,
diff --git a/src/bindings/ruby/.gitignore b/src/bindings/ruby/.gitignore
index 66f8ed35a47c740ac93a12aa7a32528721bdc127..4ceda1bade5fb0612afed8357cd41af0e2b07f2b 100644
--- a/src/bindings/ruby/.gitignore
+++ b/src/bindings/ruby/.gitignore
@@ -1 +1,2 @@
 /Gemfile.lock
+.bundle
diff --git a/src/bindings/ruby/SConscript b/src/bindings/ruby/SConscript
new file mode 100644
index 0000000000000000000000000000000000000000..290eb168e4f91a3eb55f4c40ced87dc73f4d5496
--- /dev/null
+++ b/src/bindings/ruby/SConscript
@@ -0,0 +1,29 @@
+# -*- python -*-
+import os.path
+Import("env libhammer_shared testruns targets")
+
+rubysources = [
+    Glob("test/*.rb"),
+    Glob("lib/hammer.rb"),
+    Glob("lib/*/*.rb"),
+    "hammer-parser.gemspec",
+    "Rakefile",
+    "Gemfile",
+    "Gemfile.lock",
+    "README.md",
+]
+
+rubyenv = env.Clone()
+rubyenv['ENV']['LD_LIBRARY_PATH'] = os.path.dirname(str(libhammer_shared[0]))
+rubyenv['RBDIR'] = os.path.dirname(str(rubyenv.File("Gemfile").path))
+
+setup = rubyenv.Command(Dir(".bundle"), rubysources, "cd $RBDIR && bundle install")
+AlwaysBuild(setup)
+
+rubytestexec = rubyenv.Command(None, [setup] + rubysources, "cd $RBDIR && bundle exec rake test")
+
+rubytest = Alias("testruby", [rubytestexec], rubytestexec)
+AlwaysBuild(rubytestexec)
+testruns.append(rubytest)
+
+# No need for an install target; everybody just uses gems for that.