Created
August 3, 2009 07:21
-
-
Save cscotta/160408 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 'tokyocabinet' | |
require 'dm-core' | |
include TokyoCabinet | |
# Hacky overriding for destroy because the original method was constantly | |
# reporting a new record and failing to pass a valid query to the delete method. | |
module DataMapper | |
module Resource | |
def destroy | |
return false unless repository.delete({:model => model, :id => id}) | |
@new_record = true | |
repository.identity_map(model).delete(key) | |
original_values.clear | |
properties.each do |property| | |
# We'll set the original value to nil as if we had a new record | |
original_values[property.name] = nil if attribute_loaded?(property.name) | |
end | |
true | |
end | |
# Hacky overriding for save because the original method was constantly reporting a new record. | |
def save(context = :default) | |
associations_saved = false | |
child_associations.each { |a| associations_saved |= a.save } | |
saved = update | |
original_values.clear if saved | |
parent_associations.each { |a| associations_saved |= a.save } | |
(saved | associations_saved) == true | |
end | |
end | |
end | |
module DataMapper | |
module Adapters | |
class TokyoCabinetAdapter < AbstractAdapter | |
# Opens a database connection and deletes an object identified by the primary key supplied. | |
def delete(query) | |
item_id = tokyo(query[:model]) { |connection| connection.out(query[:id]) } | |
true | |
end | |
# Method for updating the Tokyo TDB datastore when Model.save is called (for create and update). | |
def update(attributes, query) | |
id = attributes.keys.detect { |k| k.name == :id } | |
# Creates a new object if the content being saved does not yet contain an ID property. | |
unless id | |
item_id = tokyo(attributes.keys.first.model) do |connection| | |
primary_key = connection.genuid | |
tokyo_object = {} | |
attributes.each_pair { |k, v| tokyo_object.merge!(k.name.to_s => v.to_s) } | |
connection.put(primary_key, tokyo_object) | |
end | |
# Updates an existing object in the datastore by opening a connection, stringifying the | |
# key/value pairs, and updating it in the database by saving it using the same primary key. | |
else | |
item_id = tokyo(attributes.keys.first.model) do |connection| | |
tokyo_object = {} | |
attributes.each_pair { |k, v| tokyo_object.merge!(k.name.to_s => v.to_s) } | |
tokyo_object.delete(id) | |
connection.put(attributes[id], tokyo_object) | |
end | |
end | |
end | |
# Fetches a single object from the datastore. | |
def read_one(query) | |
read(query, query.model, false) | |
end | |
# Fetches many objects from the datastore. | |
def read_many(query) | |
read(query, query.model, true) | |
end | |
private | |
# The meat of this adapter. | |
def read(query, set, many = true) | |
model = query.model | |
conditions = query.conditions | |
results = [] | |
# If we're just fetching an object by ID, retrieve that single object and return it straightaway. | |
if conditions.size == 1 and conditions.first[0] == :eql and conditions.first[1].name == :id | |
op, prop, val = conditions.first | |
results << fetch_by_id(query, val) | |
else | |
# Open a database connection and initiate a new query | |
tokyo(model) do |connection| | |
tokyo_query = TDBQRY::new(connection) | |
conditions.all? do |tuple| | |
# Initialize the parameters for the query (including a shim for Ruby 1.9.x compatibility) | |
operator, property, bind_value = *tuple | |
bind_value = bind_value.flatten.join('') if RUBY_VERSION > '1.9.0' and bind_value.class == Array | |
# Translate Datamapper's conditions into Tokyo TDBQRY conditions and attach them to the query object. | |
unless bind_value.nil? or bind_value.empty? | |
case operator | |
when :eql, :in then tokyo_query.addcond(property.name.to_s, TDBQRY::QCSTREQ, bind_value.to_s) | |
when :not then tokyo_query.addcond(property.name.to_s, TDBQRY::QCSTREQ|TDBQRY::QCNEGATE, bind_value.to_s) | |
when :like then tokyo_query.addcond(property.name.to_s, TDBQRY::QCSTRINC, bind_value.to_s) | |
when :gt then tokyo_query.addcond(property.name.to_s, TDBQRY::QCNUMGT, bind_value.to_s) | |
when :gte then tokyo_query.addcond(property.name.to_s, TDBQRY::QCNUMGE, bind_value.to_s) | |
when :lt then tokyo_query.addcond(property.name.to_s, TDBQRY::QCNUMLT, bind_value.to_s) | |
when :lte then tokyo_query.addcond(property.name.to_s, TDBQRY::QCNUMLE, bind_value.to_s) | |
else raise "Invalid query operator: #{operator.inspect} (parameterized SQL not yet supported)." | |
end | |
end | |
end | |
# Perform the query and retrieve the results. | |
dataset = tokyo_query.search | |
# Build an array of objects with the properties we found in the datastore and return them. | |
if dataset.first | |
dataset.each do |data| | |
properties = connection.get(data) | |
properties.merge!({:id => data}) | |
results << model.new(properties) | |
end | |
end | |
end | |
end | |
many ? results : results.first | |
end | |
# Fetch a single object by ID by opening a connection, retrieving an object identified | |
# by the primary key supplied, and return an object populated with the properties attached. | |
def fetch_by_id(query, id, result = nil) | |
tokyo(query.model) do |connection| | |
dataset = connection.get(id) | |
result = query.model.new(dataset.merge!({:id => id})) unless dataset.nil? | |
end | |
result | |
end | |
# The database conncetion method. | |
def tokyo(model, property = nil, &block) | |
connection = TDB::new | |
attribute = property.to_s.capitalize if property | |
connection.open(data_path + "#{model}#{attribute}.tct", TDB::OWRITER | TDB::OCREAT) | |
result = yield(connection) | |
connection.close | |
result | |
end | |
# Sugar for the connection method. | |
# Defines the path to be used for storage based on the Datamapper.setup method used to initialize the ORM. | |
def data_path | |
data_path = DataMapper.repository.adapter.uri[:data_path].to_s + "/" | |
end | |
end # TokyoCabinetAdapter | |
end # Adapters | |
end # Datamapper |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment