lib/og/store/kirby.rb in og-0.20.0 vs lib/og/store/kirby.rb in og-0.21.0

- old
+ new

@@ -1,10 +1,10 @@ begin - require 'lib/og/store/kirby/kirbybase' + require 'lib/og/store/kirby/kirbybase' rescue Object => ex - Logger.error 'KirbyBase is not installed!' - Logger.error ex + Logger.error 'KirbyBase is not installed!' + Logger.error ex end require 'og/store/sql' module Og @@ -14,267 +14,272 @@ # To read documentation about the methods, consult the # documentation for SqlStore and Store. class KirbyStore < SqlStore - def self.db_dir(options) - "#{options[:name]}_db" - end - - def self.destroy(options) - begin - FileUtils.rm_rf(db_dir(options)) - super - rescue Object - Logger.info "Cannot drop '#{options[:name]}'!" - end - end + def self.db_dir(options) + "#{options[:name]}_db" + end + + def self.destroy(options) + begin + FileUtils.rm_rf(db_dir(options)) + super + rescue Object + Logger.info "Cannot drop '#{options[:name]}'!" + end + end - def initialize(options) - super - - if options[:embedded] - name = self.class.db_dir(options) - FileUtils.mkdir_p(name) - @conn = KirbyBase.new(:local, nil, nil, name) - else - # TODO - end - end + def initialize(options) + super + + if options[:embedded] + name = self.class.db_dir(options) + FileUtils.mkdir_p(name) + @conn = KirbyBase.new(:local, nil, nil, name) + else + # TODO + end + end - def close - super - end - - def enchant(klass, manager) - klass.property :oid, Fixnum, :sql => 'integer PRIMARY KEY' - super - end + def close + super + end + + def enchant(klass, manager) + klass.property :oid, Fixnum, :sql => 'integer PRIMARY KEY' + super + end - def query(sql) - Logger.debug sql if $DBG - return @conn.query(sql) - rescue => ex - handle_sql_exception(ex, sql) - end + def query(sql) + Logger.debug sql if $DBG + return @conn.query(sql) + rescue => ex + handle_sql_exception(ex, sql) + end - def exec(sql) - Logger.debug sql if $DBG - @conn.query(sql).close - rescue => ex - handle_sql_exception(ex, sql) - end + def exec(sql) + Logger.debug sql if $DBG + @conn.query(sql).close + rescue => ex + handle_sql_exception(ex, sql) + end - def start - # nop - end - - def commit - # nop - end - - def rollback - # nop - end + def start + # nop + end + + def commit + # nop + end + + def rollback + # nop + end private - def create_table(klass) - fields = fields_for_class(klass) + def create_table(klass) + fields = fields_for_class(klass) - table = @conn.create_table(klass::OGTABLE, *fields) { |obj| obj.encrypt = false } -=begin - # Create join tables if needed. Join tables are used in - # 'many_to_many' relations. - - if klass.__meta and join_tables = klass.__meta[:join_tables] - for join_table in join_tables - begin - @conn.query("CREATE TABLE #{join_table} (key1 integer NOT NULL, key2 integer NOT NULL)").close - @conn.query("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)").close - @conn.query("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)").close - rescue Object => ex - # gmosx: any idea how to better test this? - if ex.to_s =~ /table .* already exists/i - Logger.debug 'Join table already exists' if $DBG - else - raise - end - end - end - end -=end - end + table = @conn.create_table(klass::OGTABLE, *fields) { |obj| obj.encrypt = false } - def drop_table(klass) - @conn.drop_table(klass.table) if @conn.table_exists?(klass.table) - end + # Create join tables if needed. Join tables are used in + # 'many_to_many' relations. + + if klass.__meta and join_tables = klass.__meta[:join_tables] + for join_table in join_tables + begin + @conn.create_table(join_table[:table], + join_table[:first_key], :Integer, + join_table[:second_key], :Integer) + # KirbyBase doesn't support indices. + rescue RuntimeError => error + # Unfortunately, KirbyBase just throws RuntimeErrors + # with no extra information, so we just have to look + # for the error message it uses. + if error.message =~ /table #{join_table[:table]} already exists/i + Logger.debug 'Join table already exists' if $DBG + else + raise + end + end + end + end + end - def fields_for_class(klass) - fields = [] - - klass.properties.each do |p| - klass.index(p.symbol) if p.meta[:index] - - fields << p.symbol - - type = p.klass.name.intern - type = :Integer if type == :Fixnum - - fields << type - end + def drop_table(klass) + @conn.drop_table(klass.table) if @conn.table_exists?(klass.table) + end - return fields - end + def fields_for_class(klass) + fields = [] + + klass.properties.each do |p| + klass.index(p.symbol) if p.meta[:index] + + fields << p.symbol + + type = p.klass.name.intern + type = :Integer if type == :Fixnum + + fields << type + end - def create_field_map(klass) - map = {} - fields = @conn.get_table(klass.table).field_names + return fields + end - fields.size.times do |i| - map[fields[i]] = i - end - - return map - end + def create_field_map(klass) + map = {} + fields = @conn.get_table(klass.table).field_names - # Return an sql string evaluator for the property. - # No need to optimize this, used only to precalculate code. - # YAML is used to store general Ruby objects to be more - # portable. - #-- - # FIXME: add extra handling for float. - #++ - - def write_prop(p) - if p.klass.ancestors.include?(Integer) - return "@#{p.symbol} || nil" - elsif p.klass.ancestors.include?(Float) - return "@#{p.symbol} || nil" - elsif p.klass.ancestors.include?(String) - return %|@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : nil| - elsif p.klass.ancestors.include?(Time) - return %|@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : nil| - elsif p.klass.ancestors.include?(Date) - return %|@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : nil| - elsif p.klass.ancestors.include?(TrueClass) - return "@#{p.symbol} ? \"'t'\" : nil" - else - # gmosx: keep the '' for nil symbols. - return %|@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"| - end - end + fields.size.times do |i| + map[fields[i]] = i + end + + return map + end - # Return an evaluator for reading the property. - # No need to optimize this, used only to precalculate code. - - def read_prop(p, col) - if p.klass.ancestors.include?(Integer) - return "#{self.class}.parse_int(res[#{col} + offset])" - elsif p.klass.ancestors.include?(Float) - return "#{self.class}.parse_float(res[#{col} + offset])" - elsif p.klass.ancestors.include?(String) - return "res[#{col} + offset]" - elsif p.klass.ancestors.include?(Time) - return "#{self.class}.parse_timestamp(res[#{col} + offset])" - elsif p.klass.ancestors.include?(Date) - return "#{self.class}.parse_date(res[#{col} + offset])" - elsif p.klass.ancestors.include?(TrueClass) - return "('0' != res[#{col} + offset])" - else - return "YAML::load(res[#{col} + offset])" - end - end + # Return an sql string evaluator for the property. + # No need to optimize this, used only to precalculate code. + # YAML is used to store general Ruby objects to be more + # portable. + #-- + # FIXME: add extra handling for float. + #++ + + def write_prop(p) + if p.klass.ancestors.include?(Integer) + return "@#{p.symbol} || nil" + elsif p.klass.ancestors.include?(Float) + return "@#{p.symbol} || nil" + elsif p.klass.ancestors.include?(String) + return %|@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : nil| + elsif p.klass.ancestors.include?(Time) + return %|@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : nil| + elsif p.klass.ancestors.include?(Date) + return %|@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : nil| + elsif p.klass.ancestors.include?(TrueClass) + return "@#{p.symbol} ? \"'t'\" : nil" + else + # gmosx: keep the '' for nil symbols. + return %|@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"| + end + end - # :section: Lifecycle method compilers. - - # Compile the og_update method for the class. + # Return an evaluator for reading the property. + # No need to optimize this, used only to precalculate code. + + def read_prop(p, col) + if p.klass.ancestors.include?(Integer) + return "#{self.class}.parse_int(res[#{col} + offset])" + elsif p.klass.ancestors.include?(Float) + return "#{self.class}.parse_float(res[#{col} + offset])" + elsif p.klass.ancestors.include?(String) + return "res[#{col} + offset]" + elsif p.klass.ancestors.include?(Time) + return "#{self.class}.parse_timestamp(res[#{col} + offset])" + elsif p.klass.ancestors.include?(Date) + return "#{self.class}.parse_date(res[#{col} + offset])" + elsif p.klass.ancestors.include?(TrueClass) + return "('0' != res[#{col} + offset])" + else + return "YAML::load(res[#{col} + offset])" + end + end - def eval_og_insert(klass) - pk = klass.pk_symbol - props = klass.properties - - data = props.collect {|p| ":#{p.symbol} => #{write_prop(p)}"}.join(', ') -# data.gsub!(/#|\{|\}/, '') + # :section: Lifecycle method compilers. + + # Compile the og_update method for the class. - klass.module_eval %{ - def og_insert(store) - #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)} - store.conn.get_table('#{klass.table}').insert(#{data}) - #{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)} - end - } - end + def eval_og_insert(klass) + pk = klass.pk_symbol + props = klass.properties + + data = props.collect {|p| ":#{p.symbol} => #{write_prop(p)}"}.join(', ') +# data.gsub!(/#|\{|\}/, '') - # Compile the og_update method for the class. + klass.module_eval %{ + def og_insert(store) + #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)} + store.conn.get_table('#{klass.table}').insert(#{data}) + #{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)} + end + } + end - def eval_og_update(klass) - pk = klass.pk_symbol - props = klass.properties.reject { |p| pk == p.symbol } - - updates = props.collect { |p| - "#{p.symbol}=#{write_prop(p)}" - } + # Compile the og_update method for the class. - sql = "UPDATE #{klass::OGTABLE} SET #{updates.join(', ')} WHERE #{pk}=#\{@#{pk}\}" + def eval_og_update(klass) + pk = klass.pk_symbol + props = klass.properties.reject { |p| pk == p.symbol } + + updates = props.collect { |p| + "#{p.symbol}=#{write_prop(p)}" + } - klass.module_eval %{ - def og_update(store) - #{Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)} - store.exec "#{sql}" - #{Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)} - end - } - end + sql = "UPDATE #{klass::OGTABLE} SET #{updates.join(', ')} WHERE #{pk}=#\{@#{pk}\}" - # Compile the og_read method for the class. This method is - # used to read (deserialize) the given class from the store. - # In order to allow for changing field/attribute orders a - # field mapping hash is used. - - def eval_og_read(klass) - code = [] - props = klass.properties - field_map = create_field_map(klass) + klass.module_eval %{ + def og_update(store) + #{Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)} + store.exec "#{sql}" + #{Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)} + end + } + end - props.each do |p| - if col = field_map[p.symbol] - code << "@#{p.symbol} = #{read_prop(p, col)}" - end - end - - code = code.join('; ') + # Compile the og_read method for the class. This method is + # used to read (deserialize) the given class from the store. + # In order to allow for changing field/attribute orders a + # field mapping hash is used. + + def eval_og_read(klass) + code = [] + props = klass.properties + field_map = create_field_map(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)} - #{code} - #{Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)} - end - } - end + props.each do |p| + if col = field_map[p.symbol] + code << "@#{p.symbol} = #{read_prop(p, col)}" + end + end + + code = code.join('; ') - #-- - # FIXME: is pk needed as parameter? - #++ - - def eval_og_delete(klass) - klass.module_eval %{ - def og_delete(store, pk, cascade = true) - #{Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)} - pk ||= @#{klass.pk_symbol} - transaction do |tx| - tx.exec "DELETE FROM #{klass.table} WHERE #{klass.pk_symbol}=\#{pk}" - if cascade and #{klass}.__meta[:descendants] - #{klass}.__meta[:descendants].each do |dclass, foreign_key| - tx.exec "DELETE FROM \#{dclass::OGTABLE} WHERE \#{foreign_key}=\#{pk}" - end - end - end - #{Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)} - end - } - end + 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)} + #{code} + #{Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)} + end + } + end + #-- + # FIXME: is pk needed as parameter? + #++ + + def eval_og_delete(klass) + klass.module_eval %{ + def og_delete(store, pk, cascade = true) + #{Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)} + pk ||= @#{klass.pk_symbol} + transaction do |tx| + tx.exec "DELETE FROM #{klass.table} WHERE #{klass.pk_symbol}=\#{pk}" + if cascade and #{klass}.__meta[:descendants] + #{klass}.__meta[:descendants].each do |dclass, foreign_key| + tx.exec "DELETE FROM \#{dclass::OGTABLE} WHERE \#{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 <gm@navel.gr> +# * Ysabel <deb@isabel.org>