I’ve been working with some ruby and rails programming for a while and am still ramping up on a lot of it (the framework is pretty huge and the shift to incorporating functional programming into regular work is harder to do than saying “closures are to functional programming as object methods are to OO”). Anyway - yesterday I encountered a pretty straightforward data issue that needed fixing and was encouraged to use Ruby to solve it and it offered a glimpse of some of the coolness involved.
The problem could be described pretty simply. Say you have a database layout that looks something like this…
users
| id |
name |
| 1 |
Jack |
| 2 |
Jane |
| 3 |
Brad |
| 4 |
Matt |
user_biographies
| id |
user_id |
biography |
| 1 |
1 |
Ask not! |
| 2 |
2 |
Where’s tarzan? |
| 3 |
3 |
NULL |
For the sake of the example, let’s say the architecture was thought of with the DB implementation in mind in an effort to minimize page fetches when looking for users and that the biographies are rarely needed, so they were factored into a separate table.
The problem here is that we don’t have a biography for user 4. There should be one, even if it’s only NULL, but it’s missing. So we could fix this without too much trouble with a SQL statement - something like the following would work:
INSERT INTO user_biographies user_id, biography
VALUES (
SELECT users.id, null
FROM users
LEFT JOIN user_biographies ON users.id = user_biographies.user_id
WHERE user_biographies.user_id IS NULL
)
And then we’ll have records in the user_biographies table for all records from users and give them an empty (er, null) biography.
Or we could do it in ruby. The ruby way really isn’t that different or any harder - it would go something like this:
for users.find_all() do |u|
user_biography.create(:user_id => u.id, :biography => nil) unless u.user_biography
end
(turning it into a .find() with the right SQL constraints to say “find me the users who are missing biographies” rather than looping through ALL users and skipping the biography creation if it already exists is left as an exercise to the reader)
Functionally, this might be identical to the straight SQL call. The advantage is that it’s abstracted away from the DB implementation, it’s a lot clearer what you’re trying to accomplish, and it hooks into your framework’s infrastructure and any before_ and after_ create hooks on the user_biographies would be invoked. Maybe creating user biographies was broken on a website for a while and a ton of users sent you biographies in email and you want to bulk import them into the database rather than setting the bios to NULL. You could do that directly in SQL, but maybe you have a profanity filter or some other text scraping magic hooked up in the framework. If you just do inserts on the DB, that (probably) won’t be invoked, but if you did it the ruby way, it does.
It’s pretty awesome.
But still in the back of my mind, I can’t help feeling all this isolation from the DB is likely to create a really poorly performing system. The advocacy for the ruby way is that it lets the developer focus more on development style tasks and not worry so much about the underlying database framework (e.g. the rails adapter takes care of the specific SQL syntax for your underlying DB). My verdict’s still out on that, but the prospect that you could rely on the framework for so many implementation details is interesting.