begin require 'vendor/kirbybase' rescue Object => ex Logger.error 'KirbyBase is not installed!' Logger.error ex end require 'fileutils' require 'og/store/sql' module Og # A Store that persists objects into a KirbyBase database. # To read documentation about the methods, consult the documentation # for SqlStore and Store. class KirbyStore < SqlStore # Override if needed. def self.base_dir(options) options[:base_dir] || './kirbydb' end def self.destroy(options) begin FileUtils.rm_rf(base_dir(options)) super rescue Object Logger.info 'Cannot drop database!' end end def initialize(options) super mode = options[:mode] || :local if mode == :client # Use a client/server configuration. @conn = KirbyBase.new(:client, options[:address], options[:port]) else # Use an in-process configuration. base_dir = self.class.base_dir(options) FileUtils.mkdir_p(base_dir) unless File.exist?(base_dir) @conn = KirbyBase.new( :local, nil, nil, base_dir, options[:ext] || '.tbl' ) end end def close # Nothing to do. super end def enchant(klass, manager) klass.send :attr_accessor, :recno klass.send :alias_method, :oid, :recno klass.send :alias_method, :oid=, :recno= symbols = klass.properties.keys klass.module_eval %{ def self.kb_create(recno, #{symbols.join(', ')}) obj = self.allocate obj.recno = recno #{ symbols.map { |s| "obj.#{s} = #{s}; "} } return obj end } super end def get_table(klass) @conn.get_table(klass.table.to_sym) end # :section: Lifecycle methods. def load(pk, klass) get_table(klass)[pk.to_i] end alias_method :exist?, :load def reload(obj, pk) raise 'Cannot reload unmanaged object' unless obj.saved? new_obj = load(pk, obj.class) obj.clone(new_obj) end def find(options) query(options) end def find_one(options) query(options).first end #-- # FIXME: optimize me! #++ def count(options) find(options).size end def query(options) Logger.debug "Querying with #{options.inspect}." if $DBG klass = options[:class] table = get_table(klass) objects = [] if condition = options[:condition] || options[:where] condition.gsub!(/=/, '==') condition.gsub!(/LIKE '(.*?)'/, '=~ /\1/') condition.gsub!(/\%/, '') condition.gsub!(/(\w*?)\s?=(.)/, 'o.\1 =\2') objects = eval("table.select { |o| #{condition} }") else objects = table.select end if order = options[:order] desc = (order =~ /DESC/) order = order.gsub(/DESC/, '').gsub(/ASC/, '') eval "objects.sort { |x, y| x.#{order} <=> y.#{order} }" objects.reverse! if desc end return objects end def start # nop end # Commit a transaction. def commit # nop, not supported? end # Rollback a transaction. def rollback # nop, not supported? end def sql_update(sql) # nop, not supported. end private def create_table(klass) fields = fields_for_class(klass) begin table = @conn.create_table(klass.table.to_sym, *fields) do |t| t.record_class = klass end rescue Object => ex # gmosx: any idea how to better test this? if ex.to_s =~ /already exists/i Logger.debug "Table for '#{klass}' already exists!" return else raise end end end def drop_table(klass) @conn.drop_table(klass.table) if @conn.table_exists?(klass.table) end def fields_for_class(klass) fields = [] klass.properties.values.each do |p| klass.index(p.symbol) if p.index fields << p.symbol type = p.klass.name.to_sym type = :Integer if type == :Fixnum fields << type end return fields end def eval_og_insert(klass) pk = klass.primary_key.symbol if klass.schema_inheritance? props << Property.new(:symbol => :ogtype, :klass => String) values << ", '#{klass}'" end klass.class_eval %{ def og_insert(store) #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)} Logger.debug "Inserting \#{self}." if $DBG @#{pk} = store.get_table(#{klass}).insert(self) #{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)} end } end # Compile the og_update method for the class. def eval_og_update(klass) pk = klass.pk_symbol updates = klass.properties.keys.collect { |p| ":#{p} => @#{p}" } klass.module_eval %{ def og_update(store, options = nil) #{Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)} store.get_table(#{klass}).update { |r| r.recno == #{pk} }.set(#{updates.join(', ')}) #{Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)} end } end def eval_og_read(klass) klass.module_eval %{ def og_read(res, row = 0, offset = 0) #{Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)} #{Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)} end } end def eval_og_delete(klass) klass.module_eval %{ def og_delete(store, pk, cascade = true) pk ||= self.pk pk = pk.to_i #{Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)} table = store.get_table(self.class) transaction do |tx| table.delete { |r| r.recno == pk } if cascade and #{klass}.ann.this[:descendants] #{klass}.ann.this.descendants.each do |dclass, foreign_key| dtable = store.get_table(dclass) dtable.delete { |r| foreign_key == pk } end end end #{Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)} end } end end end # * George Moschovitis