# a little wrapper on yaml/store to give collection and record access to a # transaction yaml store. # # sample usage # # require ‘ydb’ # # ydb = YDB.new # # collection = ydb.collection(:posts) # # 2.times do # id = collection.create(:k => :v, :array => [0,1,2], :time => Time.now.to_f) # record = collection.find(id) # # p record # #=> “time”=>1315493211.86451, “id”=>“1”, “array”=>[0, 1, 2] # #=> “time”=>1315493211.88372, “id”=>“2”, “array”=>[0, 1, 2] # end # # p collection.all # #=> [“time”=>1315493211.86451, “array”=>[0, 1, 2], “id”=>“1” # #=> , “time”=>1315493211.88372, “array”=>[0, 1, 2], “id”=>“2” # #=> ] # # # ydb.create(:foo => :bar) # # puts IO.read(ydb.path) # #=> — # #=> tablename: # #=> “1”: # #=> foo: :bar # #=> id: “1” # #=> posts: # #=> “1”: # #=> k: :v # #=> time: 1315493211.86451 # #=> id: “1” # #=> array: # #=> - 0 # #=> - 1 # #=> - 2 # #=> “2”: # #=> k: :v # #=> time: 1315493211.88372 # #=> id: “2” # #=> array: # #=> - 0 # #=> - 1 # #=> - 2
require 'yaml/store'
require 'fileutils'
class YDB
Version = '0.0.1' unless defined?(Version)
def YDB.version
YDB::Version
end
def YDB.dependencies
{
'map' => [ 'map' , '~> 4.4.0' ],
}
end
def YDB.libdir(*args, &block)
@libdir ||= File.(__FILE__).sub(/\.rb$/,'')
args.empty? ? @libdir : File.join(@libdir, *args)
ensure
if block
begin
$LOAD_PATH.unshift(@libdir)
block.call()
ensure
$LOAD_PATH.shift()
end
end
end
def YDB.load(*libs)
libs = libs.join(' ').scan(/[^\s+]+/)
YDB.libdir{ libs.each{|lib| Kernel.load(lib) } }
end
# gems
#
begin
require 'rubygems'
rescue LoadError
nil
end
if defined?(gem)
YDB.dependencies.each do |lib, dependency|
gem(*dependency) if defined?(gem)
require(lib)
end
end
attr_accessor :path
def initialize(*args)
= Map.(args)
@path = ( args.shift || [:path] || YDB.default_path ).to_s
FileUtils.mkdir_p(File.dirname(@path)) rescue nil
end
def rm_f
FileUtils.rm_f(@path) rescue nil
end
def rm_rf
FileUtils.rm_rf(@path) rescue nil
end
def truncate
rm_f
end
def ydb
self
end
def ystore
@ystore ||= YAML::Store.new(path)
end
class Collection
def initialize(name, ydb)
@name = name.to_s
@ydb = ydb
end
def save(data = {})
@ydb.save(@name, data)
end
alias_method(:create, :save)
alias_method(:update, :save)
def find(id = :all)
@ydb.find(@name, id)
end
def all
find(:all)
end
def [](id)
find(id)
end
def []=(id, data = {})
data.delete(:id)
data.delete('id')
data[:id] = id
save(data)
end
def delete(id)
@ydb.delete(@name, id)
id
end
alias_method('destroy', 'delete')
def to_hash
transaction{|y| y[@name]}
end
def size
to_hash.size
end
alias_method('count', 'size')
def to_yaml(*args, &block)
Hash.new.update(to_hash).to_yaml(*args, &block)
end
def transaction(*args, &block)
@ydb.ystore.transaction(*args, &block)
end
end
def collection(name)
Collection.new(name, ydb)
end
alias_method('[]', 'collection')
def method_missing(method, *args, &block)
if args.empty? and block.nil?
return self.collection(method)
end
super
end
def transaction(*args, &block)
ystore.transaction(*args, &block)
end
def save(collection, data)
data = data_for(data)
ystore.transaction do |y|
collection = (y[collection.to_s] ||= {})
id = next_id_for(collection, data)
collection[id] = data
record = collection[id]
id
end
end
def data_for(data)
data ? Map.for(data) : nil
end
alias_method(:create, :save)
def find(collection, id = :all, &block)
ystore.transaction do |y|
collection = (y[collection.to_s] ||= {})
if id.nil? or id == :all
list = collection.values.map{|data| data_for(data)}
if block
collection[:all] = list.map{|record| data_for(block.call(record))}
else
list
end
else
key = String(id)
record = data_for(collection[key])
if block
collection[key] = data_for(block.call(record))
else
record
end
end
end
end
def update(collection, id = :all, updates = {})
data = data_for(data)
find(collection, id) do |record|
record.update(updates)
end
end
def delete(collection, id = :all)
ystore.transaction do |y|
collection = (y[collection.to_s] ||= {})
if id.nil? or id == :all
collection.clear()
else
deleted = collection.delete(String(id))
data_for(deleted) if deleted
end
end
end
alias_method('destroy', 'delete')
def next_id_for(collection, data)
data = data_for(data)
begin
id = id_for(data)
raise if id.strip.empty?
id
rescue
data['id'] = String(collection.size + 1)
id_for(data)
end
end
def id_for(data)
data = data_for(data)
%w( id _id ).each{|key| return String(data[key]) if data.has_key?(key)}
raise("no id discoverable for #{ data.inspect }")
end
def to_hash
ystore.transaction do |y|
y.roots.inject(Hash.new){|h,k| h.update(k => y[k])}
end
end
def to_yaml(*args, &block)
to_hash.to_yaml(*args, &block)
end
class << YDB
attr_writer :root
attr_writer :instance
def default_root()
defined?(Rails.root) && Rails.root ? File.join(Rails.root.to_s, 'db') : '.'
end
def default_path()
File.join(default_root, 'ydb.yml')
end
def method_missing(method, *args, &block)
super unless instance.respond_to?(method)
instance.send(method, *args, &block)
end
def instance
@instance ||= YDB.new(YDB.default_path)
end
def root
@root ||= default_root
end
def tmp(&block)
require 'tempfile' unless defined?(Tempfile)
tempfile = Tempfile.new("#{ Process.pid }-#{ Process.ppid }-#{ Time.now.to_f }-#{ rand }")
path = tempfile.path
ydb = new(:path => path)
if block
begin
block.call(ydb)
ensure
ydb.rm_rf
end
else
ydb
end
end
end
end
Ydb = YDb = YDB