require 'facet/pool' require 'facet/class/descendents' require 'og/entity' require 'og/store' module Og # A Manager defines the relations between entities, ie objects # managed by Og. class Manager def self.managers managers = [] ObjectSpace.each_object(self) { |o| managers << o } return managers end def self.managed?(klass) self.managers.any? { |m| m.managed? klass } end def self.managed_classes managed = self.managers.collect {|m| m.managed_classes} managed.flatten end # Information about an Entity class. class EntityInfo attr_accessor :klass attr_accessor :enchanted attr_accessor :options def initialize(klass = nil) @klass = klass @enchanted = false @options = {} end end # The configuration options. attr_accessor :options # The store used for persistence. This is a virtual field # when running in thread_safe mode. attr_accessor :store # The collection of Entities (managed classes) managed by # this manager. attr_accessor :entities # The managed object cache. This cache is optional. When # used it improves object lookups. attr_accessor :cache def initialize(options) @options = options @entities = {} @store_class = Store.for_name(options[:store]) @store_class.destroy(options) if options[:destroy] @store_class.destroy_tables(options) if options[:destroy_tables] init_store end def initialize_store if @pool close_store end if Og.thread_safe @pool = Pool.new (options[:connection_count] || 5).times do @pool << @store_class.new(@options) end else @store = @store_class.new(@options) end end alias :init_store :initialize_store # used when changing thread_safe mode def close_store if @pool.empty? @store.close else @pool.each { |s| s.close } @pool.clear end @pool = nil end # Get a store from the pool. def get_store if @pool thread = Thread.current unless st = thread[:og_store] and st.is_a?(@store_class) if 0 == @pool.size() initialize_store() end st = @pool.pop() thread[:og_store] = st end return st else return @store end end alias_method :store, :get_store alias_method :conn, :get_store # Return a store to the pool. def put_store if @pool thread = Thread.current if conn = thread[:og_store] thread[:og_store] = nil return @pool.push(conn) end end end # Resolve polymorphic relations. def resolve_polymorphic(klass) Relations.resolve_polymorphic(klass) end # Manage a class. Converts the class to an Entity. def manage(klass) return if managed?(klass) or klass.ancestors.include?(Unmanageable) info = EntityInfo.new(klass) # DON'T DO THIS!!! klass.module_eval %{ def ==(other) other.instance_of?(#{klass}) ? @#{klass.primary_key.symbol} == other.#{klass.primary_key.symbol} : false end } klass.class.send(:attr_accessor, :ogmanager) klass.instance_variable_set '@ogmanager', self Relation.enchant(klass) # ensure that the superclass is managed before the # subclass. manage(klass.superclass) if manageable?(klass.superclass) # FIXME: uggly! store.enchant(klass, self); put_store # Call special class enchanting code. klass.enchant if klass.respond_to?(:enchant) @entities[klass] = info end # Is this class manageable by Og? #-- # FIXME: investigate this (polymorphic/unmanageable). #++ def manageable?(klass) klass.respond_to?(:properties) and (!klass.properties.empty?) # and klass.ann.self.polymorphic.nil? end # Is the class managed by Og? def managed?(klass) @entities.include?(klass) end alias_method :entity?, :managed? # Returns an array containing all classes managed by this manager. def managed_classes @entities.map {|e| e[0]} end # Use Ruby's advanced reflection features to find # all manageable classes. Managable are all classes that # define Properties. def manageable_classes classes = [] ObjectSpace.each_object(Class) do |c| if manageable?(c) classes << c end end return classes end # Manage a collection of classes. def manage_classes(*classes) classes = manageable_classes.flatten # if classes.empty? FIXME! classes = classes.reject { |c| self.class.managed?(c) } classes.each { |c| Relation.resolve_targets(c) } classes.each { |c| Relation.resolve_polymorphic_markers(c) } classes.each { |c| Relation.resolve_polymorphic_relations(c) } # The polymorpic resolution step creates more manageable classes. classes = manageable_classes.flatten # if classes.empty? FIXME! classes = classes.reject { |c| self.class.managed?(c) } Logger.debug "Og manageable classes: #{classes.inspect}" if $DBG classes.each { |c| Relation.resolve_targets(c) } classes.each { |c| Relation.resolve_names(c) } classes.each { |c| manage(c) } end alias_method :manage_class, :manage_classes def unmanage_classes(*classes) classes = manageable_classes.flatten if classes.empty? for c in classes @entities.delete_if { |k, v| v.klass == c } end end alias_method :unmanage_class, :unmanage_classes # Allows functionality that requires a store is finalized # to be implemented. A vastly superior method of constructing # foreign key constraints is an example of functionality # this provides. Currently only used by the PostgreSQL store. # Another good use for this would be an alternate table # and field creation routine, which could be much faster, # something I intend to do to the PostgreSQL store if nobody # has reasons for objecting. def post_setup store.post_setup if store.respond_to?(:post_setup) end # Dump a nice name for this store. =begin def to_s if store = get_store store.to_s else 'Uninitialized' end end =end end end # * George Moschovitis # * Guillaume Pierronnet # * Rob Pitt