Skip to content
mo-betta the yaml/store.
Ruby
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
lib
README
Rakefile
ydb.gemspec

README

# 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
#         #=> {"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"}
#         #=> ]
#
#
#     ydb[:tablename].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.expand_path(__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)
      options = Map.options_for!(args)
      @path = ( args.shift || options[: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
You can’t perform that action at this time.