Skip to content

Instantly share code, notes, and snippets.

@cscotta
Created August 3, 2009 07:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cscotta/160408 to your computer and use it in GitHub Desktop.
Save cscotta/160408 to your computer and use it in GitHub Desktop.
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