Created
June 12, 2014 10:50
-
-
Save maetl/cfa709488fb3f8346f75 to your computer and use it in GitHub Desktop.
Proof of concept for a toy ORM that combines some aspects of the ActiveRecord API with an in-memory query model based on the Axiom relational algebra API.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'virtus' | |
require 'axiom-memory-adapter' | |
module Mementus | |
# Proof of concept for a toy ORM that combines some aspects of the | |
# ActiveRecord API with an in-memory query model based on the Axiom | |
# relational algebra API. | |
# | |
# The weirdest trick is that the data stored in the relational model | |
# is a read-only index that never gets re-materialised back into the | |
# model objects themselves (though Axiom does seem to be capable of | |
# doing this). | |
# | |
# Instead of returning mapped data from queries, the Ruby object_id | |
# is used as a reference to point to the existing instance in the | |
# runtime object space. | |
# | |
class Model | |
@@local_storage = nil | |
@@model_registry = {} | |
def self.name_to_sym | |
name_without_namespace = name.split("::").last | |
name_without_namespace.gsub(/([^\^])([A-Z])/,'\1_\2').downcase.to_sym | |
end | |
# TODO: this mapping could be based on declared indexes, | |
# rather than dumping the entire attribute schema here. | |
# | |
def schema_tuple | |
tuple = [[:__object_id, String]] | |
attribute_set.each do |attribute_type| | |
tuple << [attribute_type.name.to_sym, attribute_type.primitive] | |
end | |
tuple | |
end | |
# TODO: this mapping could be based on declared indexes, | |
# rather than dumping the entire attribute data set here. | |
# | |
def values_tuple | |
tuple = [object_id.to_s] | |
attributes.each do |_, attribute_value| | |
tuple << attribute_value | |
end | |
tuple | |
end | |
def self.inherited(concrete_model) | |
concrete_model.send(:include, Virtus.model) | |
end | |
# Create only writes to memory. It's required in order to | |
# index the object's attributes in the query model. | |
# | |
def create | |
ensure_registered(self) | |
local_storage[self.class.name_to_sym].insert([values_tuple]) | |
end | |
def ensure_registered(model) | |
register(model) unless registered?(model) | |
end | |
def registered?(model) | |
model_registry.has_key? model.class.name_to_sym | |
end | |
def register(model) | |
model_id = model.class.name_to_sym | |
model_registry[model_id] = true | |
local_storage[model_id] = Axiom::Relation.new(model.schema_tuple) | |
end | |
def model_registry | |
@@model_registry | |
end | |
def local_storage | |
unless @@local_storage | |
@@local_storage = Axiom::Adapter::Memory.new | |
end | |
@@local_storage | |
end | |
def self.collection | |
@@local_storage[name_to_sym] | |
end | |
# Stub implementation that demonstrates the ObjectSpace | |
# reference grabbing. | |
# | |
# Currently just splats to an array instead of handing back | |
# a chainable scope object. | |
# | |
def self.where(constraints) | |
self.collection.restrict(constraints).inject([]) do |list, relation| | |
list << ObjectSpace._id2ref(relation[:__object_id].to_i) | |
end | |
end | |
end | |
end | |
class Book < Mementus::Model | |
attribute :title, String | |
attribute :author, String | |
end | |
book1 = Book.new(:title => "Gravity's Rainbow", :author => "Thomas Pynchon") | |
book2 = Book.new(:title => "The Crying of Lot 49", :author => "Thomas Pynchon") | |
book3 = Book.new(:title => "Crash", :author => "J.G. Ballard") | |
book4 = Book.new(:title => "The Golden Notebook", :author => "Doris Lessing") | |
book5 = Book.new(:title => "Mao II", :author => "Don DeLillo") | |
book1.create | |
book2.create | |
book3.create | |
book4.create | |
book5.create | |
p Book.where(author: "Thomas Pynchon") | |
p Book.where(title: "Mao II") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment