Ruby In-Memory Database

30 Mar 2013. comments

One of the things that Uncle Bob talks](https://www.youtube.com/watch?v=WpkDN78P884) about a lot is architectures that allow you to defer decisions about frameworks and other dependencies as long as possible. If you follow that principle then your architecture will be decoupled from any such dependencies and therefor allow you to change it easier down the road. Probably the most common application of this thinking is in the decision around database technology (sql, nosql, filesystem, etc etc). One way you can defer this decision is by developing your application against a uniform data store abstraction that can have any concrete persistence wired up to it and use an in-memory implementation with it as long as possible.

I’ve been doing a lot of Ruby lately. As an exercise I wanted to create a good in-memory data store that would be general enough to use for a variety of applications without creating a specific type of backing data store. I started from a plain old hash:

class MemoryDataStore
  def initialize
    @storage = {}
  end
end

Since I want to store many different types of entities in this data store, I envisioned a data structure like the following:

@storage = {
  'foo-entity' => {
    '123' => foo-entity-123
    '456' => foo-entity-456
  },
  'bar-entity' => {
    '123' => bar-entity-123
    '456' => bar-entity-456
  }
}

The keys in the top-level hash are the entity type names with a sub-hash for those entities id/value pairs. Here’s what the code implementation looks like as an in-memory implementation:

class InMemoryDataStore  

  def initialize  
    @storage = {}  
  end  

  def save(klass, entity)  
    @storage[klass] ||= {}  
    entity.id ||= SecureRandom.uuid  
    @storage[klass][entity.id] = entity  
  end  

  def find_by_id(klass, id)  
    entities = @storage[klass] || {}  
    entities[id]  
  end  

end

Alright so that works. But what if I want to look up an entity by some arbitrary attribute? For example, what if I want to look up a user by his name instead of just some database identifier? I decided to construct a query method that would locate an entity based on an attribute hash of options passed in:

def get(klass, options) do  
  ...  
end  

entity = get(:user, { :name => "Ben" })

The implementation would only return a user if all of the options passed in were found on an entity in the data store. Since the options passed in is an arbitrary hash of attribute names to values we need to check if the entity has an attribute with the name of each option key and that the value returned by that attribute matches up with what’s desired. Here’s what it ended up looking like:

def get(klass, options)  
  entities = @storage[klass] || {}  
  entities.values.find do |entity|  
    options.all? do |opkey, opval|  
      entity.respondto?(opkey) && entity.send(opkey) == opval  
    end  
  end  
end

Pete Higgins recently pointed out the Moneta project to me which accomplishes what I’ve done here in a more fully featured way, so if you’re looking for something like the above you should check that out.

comments

Tagged: ruby software craftsmanship architecture

2017 Ben Lakey

The words here do not reflect those of my employer.