require 'mega/class_inherit' require 'nano/kernel/assign_with' require 'glue/property' require 'og/relation' module Og # Include this module to classes to make them managable by Og. module EntityMixin def save(options = nil) self.class.ogmanager.store.save(self, options) end alias_method :save!, :save alias_method :validate_and_save, :save def force_save!(options = nil) self.class.ogmanager.store.force_save(self, options) end def insert self.class.ogmanager.store.insert(self) return self end def update(options = nil) self.class.ogmanager.store.update(self, options) end def update_properties(*properties) self.class.ogmanager.store.update(self, :only => properties) end alias_method :update_property, :update_properties alias_method :pupdate, :update_properties def update_by_sql(set) self.class.ogmanager.store.update_by_sql(self, set) end alias_method :update_sql, :update_by_sql alias_method :supdate, :update_by_sql # Reload this entity instance from the store. def reload self.class.ogmanager.store.reload(self, self.pk) end alias_method :reload!, :reload # Delete this entity instance from the store. def delete(cascade = true) self.class.ogmanager.store.delete(self, self.class, cascade) end alias_method :delete!, :delete def transaction(&block) self.class.ogmanager.store.transaction(&block) end def saved? not @oid.nil? end alias_method :serialized?, :saved? def og_quote(obj) self.class.ogmanager.store.quote(obj) end def assign_properties(values, options = {}) Property.populate_object(self, values, options) return self end alias_method :assign, :assign_properties # Returns a symbol => value hash of the object's # properties. def properties_to_hash hash = {} for sym, prop in self.class.properties hash[sym] = instance_variable_get("@#{sym}") end end include RelationDSL class_inherit do def create(*args) obj = self.new(*args) yield(obj) if block_given? ogmanager.store.save(obj) return obj end # An alternative creation helper, only works # with objects that have an initialize method # tha works with no arguments. def create_with(hash) obj = self.new obj.assign_with(hash) ogmanager.store.save(obj) return obj end def assign_properties(values, options = {}) Property.populate_object(self.new, values, options) end alias_method :assign, :assign_properties # Load an instance of this Entity class using the primary # key. def load(pk) ogmanager.store.load(pk, self) end alias_method :[], :load alias_method :exist?, :load # Update the representation of this class in the # store. def update(set, options = nil) ogmanager.store.update_by_sql(self, set, options) end # Find a specific instance of this class according # to the given conditions. def find(options = {}) if find_options = self.ann.self[:find_options] options = find_options.dup.update(options) end options[:class] = self options[:type] = self if self.schema_inheritance_child? ogmanager.store.find(options) end alias_method :all, :find # Find a single instance of this class. def find_one(options = {}) options[:class] = self ogmanager.store.find_one(options) end alias_method :one, :find_one alias_method :first, :find_one def select(sql) ogmanager.store.select(sql, self) end def select_one(sql) ogmanager.store.select_one(sql, self) end # Perform a count query. def count(options = {}) options[:class] = self ogmanager.store.count(options) end # Delete an instance of this Entity class using the actual # instance or the primary key. def delete(obj_or_pk, cascade = true) ogmanager.store.delete(obj_or_pk, self, cascade) end alias_method :delete!, :delete # Delete all objects of this Entity class. #-- # TODO: add cascade option. #++ def delete_all ogmanager.store.delete_all(self) end def destroy ogmanager.store.send :destroy, self end def escape(str) ogmanager.store.escape(str) end def transaction(&block) ogmanager.store.transaction(&block) end # Return the store (connection) for this class. def ogstore ogmanager.store end # Returns the primary key for this class. def primary_key # gmosx: LEAVE as is, seems to be a workaround for a # nasty bug in the current facets version. pk = ann.self.primary_key if pk.nil? pk = Entity.resolve_primary_key(self) ann :self, :primary_key => pk end return pk end # Set the default find options for this entity. def set_find_options(options) ann self, :find_options => options end alias_method :find_options, :set_find_options # Enable schema inheritance for this Entity class. # The Single Table Inheritance pattern is used. def set_schema_inheritance include Og::SchemaInheritanceBase end alias_method :schema_inheritance, :set_schema_inheritance def schema_inheritance? ancestors.include? Og::SchemaInheritanceBase end def schema_inheritance_child? schema_inheritance? and superclass.respond_to?(:schema_inheritance?) end def schema_inheritance_root? schema_inheritance? and (!superclass.respond_to?(:schema_inheritance?)) end #-- # farms/rp: is there not another way to access the root class? #++ def schema_inheritance_root_class klass = self until !Og.manager.manageable?(klass) or klass.schema_inheritance_root? klass = klass.superclass end return klass end # Set the default order option for this entity. def set_order(order_str) unless ann.self.find_options.nil? ann.self.find_options[:order] = order_str else ann self, :find_options => { :order => order_str } end end alias_method :order, :set_order alias_method :order_by, :set_order # Set a custom table name. def set_sql_table(table) ann self, :sql_table => table.to_s end alias_method :set_table, :set_sql_table # Set the primary key. def set_primary_key(pk, pkclass = Fixnum) #ann self, :primary_key => Property.new(:symbol => pk, :klass => pkclass) ann self, :primary_key => properties[pk].dup end # Is this entity a polymorphic parent? def polymorphic_parent? self.to_s == self.ann.self.polymorphic.to_s end # Used internally to fix the forward reference problem. def const_missing(sym) # :nodoc: all return sym end # Returns an array of all relations formed by other og managed # classes with the class of this object. # # This is needed by the PostgreSQL foreign key constraints # system. def resolve_remote_relations klass = self manager = klass.ogmanager relations = Array.new manager.managed_classes.each do |managed_class| next if managed_class == klass managed_class.relations.each do |rel| relations << rel if rel.target_class == klass end end relations end # Define a scope for the following og method invocations # on this managed class. The scope options are stored # in a thread variable. # # At the moment the scope is only considered in find # queries. def set_scope(options) Thread.current["#{self}_scope"] = options end # Get the scope. def get_scope Thread.current["#{self}_scope"] end # Execute some Og methods in a scope. def with_scope(options) set_scope(options) yield set_scope(nil) end # Handles dynamic finders. Handles methods such as: # # class User # property :name, String # property :age, Fixnum # end # # User.find_by_name('tml') # User.find_by_name_and_age('tml', 3) # User.find_all_by_name_and_age('tml', 3) # User.find_all_by_name_and_age('tml', 3, :name_op => 'LIKE', :age_op => '>', :limit => 4) # User.find_or_create_by_name_and_age('tml', 3) #-- # TODO: refactor this method. #++ def method_missing(sym, *args) if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(sym.to_s) return finder(match, args) elsif match = /find_or_create_by_([_a-zA-Z]\w*)/.match(sym.to_s) obj = finder(match, args) unless obj attrs = match.captures.last.split('_and_') obj = self.create do |obj| attrs.zip(args).map do |name, value| obj.instance_variable_set "@#{name}", value end end yield(obj) if block_given? end return obj else super end end private # Helper method for dynamic finders. Finds the object dynamically parsed # method name is after. def finder(match, args) finder = (match.captures.first == 'all_by' ? :find : :find_one) attrs = match.captures.last.split('_and_') options = {} options = args.pop if args.last.is_a?(Hash) condition = attrs.zip(args).map do |name, value| %|#{name} #{options.delete("#{name}_op".to_sym) || '='} #{ogmanager.store.quote(value)}| end.join(' AND ') options.update( :class => self, :condition => condition ) return ogmanager.store.send(finder, options) end end end # An Og Managed class. Also contains helper # methods. class Entity include EntityMixin class << self def resolve_primary_key(klass) # Is the class annotated with a primary key? if pk = klass.ann.self[:primary_key] return pk end # Search the properties, try to find one annotated as primary_key. for p in klass.properties.values if p.primary_key return Property.new(:symbol => p.symbol, :klass => p.klass) end end # The default primary key is oid. return Property.new(:symbol => :oid, :klass => Fixnum) end # Converts a string into it's corresponding class. Added to support STI. # Ex: x = "Dave" becomes: (x.class.name == Dave) == true. # Returns nil if there's no such class. #-- # gmosx: investigate this patch! #++ def entity_from_string(str) res = nil Og.manager.managed_classes.each do |klass| if klass.name == str res = klass break end end res end end end end # * George Moschovitis # * Tom Sawyer