Earlier this year, I attended Avi Bryant’s Applied Web Heresies tutorial session at ETech. Starting from scratch, over about four hours, the other students and I each created a mini-Seaside framework in the language of their choice.
The details of the tuturial are worthy of a post, and Nick Sieger and I are working on one. For this post, I’ll just use his thesis from the talk as a starting point. Avi says,
“A lot of the design decisions that are unconsciously adopted by modern frameworks like Rails or Django were made 10 or 15 years ago. They were probably good decisions at the time, but we need to re-evaluate them now that we have new and better tools available to us.”
The “unconsciously adopted” part of his thesis has been bouncing around in my head since then, and showed up in an unexpected place that I want to explore – think out, really – in this post.
To get where I want to go with this post I’ll need to go a bit off-topic for a couple of paragraphs, but I’ll bring it back quickly, so bear with me.
About a month ago, a group of us – including fellow Rail Spikes contributors Luke and Jon – decided to teach ourselves functional programming. With the help of the Pragmatic Programmers’ Beta Book, I decided to tackle Erlang.
Erlang’s primary data structure unit is a “tuple.” Adapting an example from the book, here’s a tuple that represents a single contact in an address book:
1 2 3 4 5 |
{person,
{first_name, "Dan"},
{last_name, "Grigsby"}
}.
|
Even if you’ve never seen erlang before, it should be pretty clear what’s going on. And, of course, an address book would normally have more fields, but this simplified example will be good enough.
Like Ruby, Erlang uses square brackets to signify lists. A list of people in an address book would look like this:
1 2 3 4 5 6 |
AddressBook = [
{person, {first_name, "Dan"}, {last_name, "Grigsby"}},
{person, {first_name, "Kristy"}, {last_name, "Grigsby"}},
{person, {first_name, "Dan"}, {last_name, "Buettner"}}
].
|
Now that we’ve covered this little bit of Erlang, it’s time to bring this post back onto topic. Recalling Avi’s unconscious adoption thesis, I want to talk about about databases and ORMs.
As Rails developers, we spend a lot of time working to “round trip” Ruby objects into and out of the database. While ActiveRecord makes this translation considerably simpler than other ORMs, ultimately we still put in work to get what we want:
We want Ruby objects.
As an experiment, let’s think about working exclusively with lists of pure Ruby objects within the context of the CRUD (Create, Read, Update, Destroy) verbs, since that reflects pretty well what we do with ActiveRecord.
To keep this discussion anchored, let’s port our Erlang Person tuple/record to Ruby and work with that:
1 2 3 4 5 6 7 8 |
class Person attr_accessor :first_name, :last_name def initialize(first_name = nil, last_name = nil) @first_name, @last_name = first_name, last_name end end |
We want an Address book, which we’ll represent as a list of Person objects, so let’s add that:
1 2 3 4 5 |
address_book = [] address_book << Person.new('Dan', 'Grigsby') address_book << Person.new('Kristy', 'Grigsby') address_book << Person.new('Dan', 'Buettner') |
Before we go on, we should take a second to think about storage:
Obviously, Ruby objects can be stored in memory; keep them in a long running process and you’d be able to work with them through a series of http requests. Serialize them and you can store them on disk for when your long-running process dies or needs to be shut down.
Alright, back to CRUD verbs:
Let’s knock down the basics quickly:
Create is demonstrated above: use the Person.new method to create an object and then add it to the address_book list. To Read an object, reference it by its index: address_book[0]. Update follows suit: address_book[0].first_name = 'Bob'. To Destroy, use any of the delete/slice/shift/pop array methods.
You probably noticed that I cheated a bit. My Read example is the equivalent of an ActiveRecord find using an ID. If you happen to know the list-offset then you can retrieve your element. The question becomes: how can we selectively choose element like you could do in a WHERE part of a SQL clause or ActiveRecord :conditions argument?
To answer this quetion, I’ll select out a list of people whose first name is Dan. In SQL, this’d be SELECT * FROM PEOPLE WHERE FIRST_NAME = 'Dan'. In ActiveRecord, :conditions => ["asks.state = 'open'"] would do it. With an array of Person objects, in Ruby this would do it:
1 2 3 4 5 |
dans = address_book.inject([]) do |results, person| results << person if person.first_name == 'Dan' results end |
(Update: See Chris Carter’s comment below about using Enumerable’s .select for a better way to do this.)
Similarly, Ruby’s Array method “delete_if” could be used to selectively delete from the list.
Using nothing other than pure Ruby objects and standard, simple Array methods we’ve demonstrated most of the common uses of ActiveRecord. We never had to leave Ruby to do this.
There’s a simplicity and an odd-elegance to this that appeals to me.
If that sounds crazy to you, consider that Mnesia, Erlang’s wickedly fast, fault-tolerant DBMS operates in the mode I’ve described the process here: it stores lists of native Erlang records (in memory, to disk, or both) and you apply list functions to select items you want. Here’s the select statement demonstrated above in SQL, ActiveRecord and Ruby, but this time in (I’m sure very bad) Erlang:
1 2 |
[ {person, {first_name, "Dan"}, {last_name, X}} || {person, {first_name, "Dan"}, {last_name, X}} <- AddressBook ].
|
Erlang has Mnesia. Someone who wears “rose colored glasses” could be said to have selective amnesia. Maybe the world needs an adaptation of Mnesia to Ruby: Ruby Colored Glasses.
I’m certain that there are more or less complete Ruby variants of Mnesia. The purpose of this article isn’t to suggest that there should be another one – if you’ve used one, use the comments to tell me about it. Rather, this post examines we can learn by throwing out one of the things we’ve all “unconsciously adopted” with Rails. Stay tuned for my forthcoming – and aforementioned – article on the mini-Seaside I built in Avi’s tutorial where we’ll question other assumptions to interesting ends.

