Welcome to Giant Robots Smashing Into Other Giant Robots — a weblog about development, business, design and technology — written by thoughtbot.
The Ideal of Agnosticism
I don’t share several of my co-workers’ fondness for SQL. Sure, it was designed precisely with relational databases in mind, and is therefore in theory the most powerful and flexible tool for getting the job done. Sure, that’s fine, if coding’s just a job to you. Like most everyone in my generation of Ruby programmers, I demand more from my code than a paycheck – I write Ruby primarily because doing so makes me happy, in a way that PHP/ASP never did, and I imagine Java or even Python couldn’t. Trying to explain exactly why this is would comprise another, much longer article. Or maybe the next Great American novel.
The point is, SQL doesn’t.
Less than anything, less than I could possibly say, does SQL makes me happy. In fact, it gives me Tourette’s, even-no-especially when properly formatted. “Hey guys how LEFT INNER JOIN are you SELECT WHERE doing INSERT SUM.” I know at least a few of you out there agree with me, because plenty of you are doing what you can to shield us sensitive code-aesthetes from the horror of this profane language (especially Ambition, which has lately taken the effort to a new level), and of course, thoughtbot has our own contribution.
But every once in a while, some odd requirement, and more inevitably, the need to optimize require confronting this beast. This is inevitable, but worse than actually seeing 10 lines of SQL dropped like a load of bricks onto my lovely Ruby method is the actual violation of ActiveRecord, one of whose most important jobs it is to not just shield us from SQL, but from database dependency. And many times, if you can’t do it with Rails’ finders, or even Squirrel and its ilk, then your implementation is going to end up depending on a particular database backing the app to even work. As I’m sure you’re all aware, this is bad for two reasons:
- The remote chance your client switches db technology in the future (a point on which you may or may not have sold him or her Rails)
- More pressingly (and annoyingly), the inability to use a different db technology for testing and developing, namely that wonder of size, speed and portability, SQLite
Well just this thing happened on a project I’m working on, twice actually, and both times I actually managed to solve the problem by delving deeper into an object you might not have worked with on day-to-day development: the connection object. The connection object is an instance of Rails’, or more specifically, ActiveRecord’s db adapter, which it has kindly tailored to your db in its automagical way that it has, making all your finders work properly. It can be found at ActiveRecord::Base.connection (unless you’re using db specific Models, but really) Sadly, you have betrayed this gift by slapping it in the face with your own clammy SQL, which it can’t help you with. Unless of course, you make amends by placing the SQL in the adapter itself, thereby increasing its power rather than insulting its heritage. You can do this in the same way you open any other class, by sticking a file in lib.
Here is an example where I reconciled MySQL and SQLite, once bitter enemies over the pointless dispute of random function syntax:
1 2 3 4 5 6 7 8 9 10 11 12 |
module SQLiteAdapterExt def rand(seed=Kernel.rand) "RANDOM(#{seed})" end end module MySQLAdapterExt def rand(seed=Kernel.rand) "RAND(#{seed})" end end ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:include, MySQLAdapterExt) ActiveRecord::ConnectionAdapters::SQLite3Adapter.send(:include, SQLiteAdapterExt) |
Now instead of forcing one or the other, you can just call ActiveRecord::Base.connection.rand(12345), and blam, you get database agnostic random result sorting, for example (You’ll of course need to extend every database you want to support).
But what if you need something even more obscure, say regular expression matching? Well, if you’re using MySQL, it’s happily baked right in. But your SQLite backed test? Nothing doing. From the SQLite3 docs:
The REGEXP operator is a special syntax for the regexp() user function. No regexp() user function is defined by default and so use of the REGEXP operator will normally result in an error message. If a user-defined function named “regexp” is added at run-time, that function will be called in order to implement the REGEXP operator.
In order to remedy this, you’ll need to make use of SQLite’s user-defined functions, which needless to say, aren’t well documented. sqlite3-ruby, the Ruby binding for SQLite3, provides an API for defining these: create_function, which is documented – unofficially – here: Luckily it’s relatively simple in code, once you learn to ignore the erroneous example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
if ActiveRecord::Base.connection.adapter_name.eql?("SQLite") db = ActiveRecord::Base.connection.instance_variable_get(:@connection) db.create_function("regexp", 2) do |func, expr, value| begin if value.to_s && value.to_s.match(Regexp.new(expr.to_s)) func.set_result 1 else func.set_result 0 end rescue => e puts "error: #{e}" end end end |
In brief, SQLite passes two parameters to REGEXP, the expression, and the value of the column, and includes it in the results if the function returns 1, and not if it returns 0. You may want to remove the puts in the rescue clause if you’re not using this in a development/test environment, but for some reason the documented set_error method seemed to break the whole thing for me. Kudos to those with suggestions for improvements!
update I had some trouble with reloading issues in the development environment and ended up having to move the UDF logic into the model. This isn’t the happiest solution since this logic is in no way necessarily tied to one specific model (although it may have, as in my case, been implemented for its sake), so if I find something better, I’ll post it.
About this entry
You're reading an entry on GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS, the company weblog of thoughtbot, inc.
- Author:
- Floyd Wright
- Published:
- October 9th 02:02 PM
- Updated:
- October 11th 10:51 AM
- Sections:
- Development
thoughtbot is hiring
We are hiring web developers and web designers in both Boston and New York, NY.
What are we up to?
We built Shoulda, an eclectic set of additions to Test::Unit; Paperclip to manage uploaded files without hassle; Jester, a REST/ActiveResource client library written in Javascript, and Squirrel, an enhancement for ActiveRecord's find syntax; — amongst some other projects.

Chad (President) and Jon (CTO) co-authored a technical book titled Pro Active Record: Databases with Ruby and Rails, which explores the ins and outs of the ActiveRecord ruby library. You can buy it today at Amazon.com.
About thoughtbot, inc.
We are a small web application development consulting business, with offices in Boston, MA and New York, NY. If you're looking to find a team for your next web development project or your new web application — get in touch.
7 comments
Jump to comment form