# = Database Managed Objects # # Base Mixin for Managed Objects (Entities) # # === Design # # code: # George Moschovitis # # (c) 2004 Navel, all rights reserved. # $Id: managed.rb 71 2004-10-18 10:50:22Z gmosx $ # Extend the base Module class # # Implements the meta-language used to create/fine-tune # the generated sql. # class Module # Create an sql index. Wrapper arrount meta :sql_index. # This creates ONE sql index. # The input parameter is a String (for many symbols) or # a Symvol # def sql_index(symbols, pre_sql = nil, post_sql = nil) # gmosx, TODO: meta[:sql_index] should store the full sql clause # to allow for custom indices (for example partial indices, # hash indices and more) meta(:sql_index, [symbols.to_s, pre_sql, post_sql]) end # Wrap prop/meta declarations. # def manage(&block) inherit_meta(superclass) if superclass yield N::Managed.eval_dbseq(self) N::Managed.eval_dbtable(self) # gmosx: this is called from DbConnection # N::Managed.eval_db_read_row(self) N::Managed.eval_db_insert(self) N::Managed.eval_db_update(self) end end module N; require "n/properties" require "n/utils/array" require "n/utils/time" # The database interfaces automatically manages classes that # include this interface. # module Managed # Use this hash to store sql join query fields. # DONT create this hash unless needed! attr_accessor :join_fields # # def initialize(*args) end # A list of potential ancestor classes # Returns an array of classes. # def __ancestors_classes return nil end # A list of potential descendants classes # Typically used in cascading deletes. # Returns an array of classes. # def __descendants_classes return nil end # -------------------------------------------------------------------- # DB Hooks # Define the primary key as a string. # def __db_pk return nil end # Add code to be executed before the managed object is inserted. # Typically creates the primary key. # def __db_pre_insert(conn) # nop end # Add code to be executed before the managed object is deleted. # Typically used for cascading deletes, executed in a transaction, # do NOT error-check here! # def __db_pre_delete(conn) # nop end # This method is called to deserialize a resultset from the # database. This definition is ovveriden by the evaluated code from # eval_db_read_row. # def __db_insert(conn) # nop end # This method is called to insert a managed object in the database. # This definition is ovveriden by the evaluated code from # eval_db_update. # def __db_insert(conn) # nop end # This method is called to update a managed object in the database. # This definition is ovveriden by the evaluated code from # eval_db_update. # def __db_update(conn) raise "No primary key defined, cannot update" end # -------------------------------------------------------------------- # Evaluate the DBSEQ constant for the given class. # Classes that include the N::Sequenced marker have # a custom sequence. # # INVESTIGATE: how to implement this for MySQL? # def self.eval_dbseq(klass) if klass.include?(N::Sequenced) klass.module_eval %{ DBSEQ = "#{N::DbUtils.sql_table(klass)}_oids_seq" } else klass.module_eval %{ DBSEQ = "oids_seq" } end end # Evaluate the DBTABLE constant for the given class. # def self.eval_dbtable(klass) klass.module_eval %{ DBTABLE = "#{N::DbUtils.sql_table(klass)}" } end # Precompile the code to read objects of the given class # from the backend. # # In order to allow for changing field/attribute orders we # have to use a field mapping hash. # def self.eval_db_read_row(klass) props = klass.__props code = [] props.each { |p| if idx = $db.fields[klass][p.name] # more fault tolerant if a new field is added and it # doesnt exist in the database. code << "@#{p.name} = #{N::DbUtils.read_prop(p, idx)}" end } klass.module_eval %{ def __db_read_row(rows, tuple) #{code.join('; ')} end } end # Precompile the insert code for the given class. # # gmosx: SET the oid when inserting! # def self.eval_db_insert(klass) props = klass.__props values = props.collect { |p| N::DbUtils.write_prop(p) } sql = "INSERT INTO #{N::DbUtils.sql_table(klass)} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values.join(',')})" klass.module_eval %{ def __db_insert(conn) __db_pre_insert(conn) conn.retry_query("#{sql}", #{klass}) end } end # Precompile the update code for the given class. # # gmosx: ignore the oid when updating! # def self.eval_db_update(klass) props = klass.__props.reject { |p| :oid == p.symbol } updates = props.collect { |p| "#{p.name}=#{N::DbUtils.write_prop(p)}" } # the primary key if pk = klass.new.__db_pk # only evaluate the update code if the object # defines a primary key. sql = "UPDATE #{klass::DBTABLE} SET #{updates.join(', ')} WHERE #{pk}=#\{@#{pk}\}" klass.module_eval %{ def __db_update(conn) conn.retry_query("#{sql}", #{klass}) end } end end end end # module