Skip to content

Instantly share code, notes, and snippets.

@ahoward
Created September 8, 2011 14:50
Show Gist options
  • Save ahoward/1203590 to your computer and use it in GitHub Desktop.
Save ahoward/1203590 to your computer and use it in GitHub Desktop.
# a little wrapper on yaml/store to give collection and record access to a
# transaction yaml store.
#
# sample usage
#
# require 'ydb'
#
# db = Db.new
#
# collection = db.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
# #=> {"k"=>:v, "time"=>1315493211.86451, "id"=>"1", "array"=>[0, 1, 2]}
# #=> {"k"=>:v, "time"=>1315493211.88372, "id"=>"2", "array"=>[0, 1, 2]}
# end
#
# p collection.all
# #=> [{"k"=>:v, "time"=>1315493211.86451, "array"=>[0, 1, 2], "id"=>"1"}
# #=> , {"k"=>:v, "time"=>1315493211.88372, "array"=>[0, 1, 2], "id"=>"2"}
# #=> ]
#
#
# db[:tablename].create(:foo => :bar)
#
# puts IO.read(db.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'
require 'rubygems'
require 'map'
class Db
attr_accessor :path
def initialize(*args)
options = Map.options_for!(args)
@path = ( args.shift || options[:path] || Db.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 db
self
end
def ystore
@ystore ||= YAML::Store.new(path)
end
class Collection
def initialize(name, db)
@name = name.to_s
@db = db
end
def save(data = {})
@db.save(@name, data)
end
alias_method(:create, :save)
alias_method(:update, :save)
def find(id = :all)
@db.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)
@db.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)
@db.ystore.transaction(*args, &block)
end
end
def collection(name)
Collection.new(name, db)
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 << Db
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, 'db.yml')
end
def method_missing(method, *args, &block)
super unless instance.respond_to?(method)
instance.send(method, *args, &block)
end
def instance
@instance ||= Db.new(Db.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
db = new(:path => path)
if block
begin
block.call(db)
ensure
db.rm_rf
end
else
db
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment