# code: # * George Moschovitis # # (c) 2004 Navel, all rights reserved. # $Id: meta.rb 185 2004-12-10 13:29:09Z gmosx $ require 'og/backend' require 'glue/inflector' module Og # = MetaUtils # # Some useful meta-language utilities. # module MetaUtils # :nodoc: all # Conver the klass to a string representation # The leading module if available is removed. # def self.expand(klass) return klass.name.gsub(/^.*::/, '').gsub(/::/, '_').downcase end end # = MetaLanguage # # Implements a meta-language for manipulating og-managed objects # and defining their relationships. The original idea comes # from the excellent ActiveRecord library. # # Many more useful relations will be available soon. # module MetaLanguage # Defines an SQL index. # def sql_index(index, options = {}) meta :sql_index, [index, options] end # Implements a 'belongs_to' relation. # Automatically enchants the calling class with helper methods. # # Example: # # class MyObject # belongs_to AnotherObject, :parent # end # # creates the code: # # prop_accessor Fixnum, :parent_oid # def parent; ... end # def parent=(obj_or_oid); ... end # def belongs_to(name, klass, options = {}) module_eval %{ prop_accessor Fixnum, :#{name}_oid def #{name} $og.load_by_oid(@#{name}_oid, #{klass}) end def #{name}=(obj_or_oid) @#{name}_oid = obj_or_oid.to_i end } end # Implements a 'has_one' relation. # Automatically enchants the calling class with helper methods. # # Example: # # class MyObject # has_one :children, AnotherObject # end # # creates the code: # # def children; ... end # def has_one(klass, name, options = {}) # linkback is the property of the child object that 'links back' # to this object. linkback = options[:linkback] || "#{MetaUtils.expand(self)}_oid" module_eval %{ @@og_descendants ||= {} @@og_descendants[#{klass}] = :#{linkback} unless defined?(og_descendants) def self.og_descendants @@og_descendants end end def #{name}(extrasql = nil) $og.select_one("SELECT * FROM #{Utils.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}", #{klass}) end } end # Implements a 'has_many' relation. # Automatically enchants the calling class with helper methods. # # Example: # # class MyObject # has_many :children, AnotherObject # end # # creates the code: # # def children; ... end # def has_many(name, klass, options = {}) # linkback is the property of the child object that 'links back' # to this object. linkback = options[:linkback] || "#{MetaUtils.expand(self)}_oid" module_eval %{ @@og_descendants ||= {} @@og_descendants[#{klass}] = :#{linkback} unless defined?(og_descendants) def self.og_descendants @@og_descendants end end def #{name}(extrasql = nil) $og.select("SELECT * FROM #{Utils.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}", #{klass}) end def #{name}_count(extrasql = nil) $og.count("SELECT COUNT(*) FROM #{Utils.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}") end } end # Implements a 'many_to_many' relation. # Two objects are associated using an intermediate join table. # Automatically enchants the calling class with helper methods. # # Example: # # class Article # many_to_many :children, Categories # end # # article.categories # article.del_category # article.add_category # article.clear_categories # # category.articles # ... # #-- # FIXME: make more compatible with other enchant methods. #++ def many_to_many(klass, options = {}) name1 = options[:name1] || G::Inflector.name(self) pname1 = options[:list_name1] || G::Inflector.plural_name(self) name2 = options[:name2] || G::Inflector.name(klass) pname2 = options[:list_name2] || G::Inflector.plural_name(klass) # exit if the class is allready indirectly 'enchanted' from the # other class of the many_to_many relation. return if self.respond_to?(name1) # Add some metadata to the class to allow for automatic join table # calculation. meta :sql_join, [klass, options] # gmosx, FIXME: should I update descendants here ? # enchant this class module_eval %{ def #{pname2}(extrasql = nil) $og.select("SELECT d.* FROM #{Utils.table(klass)} AS d, #{Utils.join_table(self, klass)} AS j WHERE j.key1=\#\@oid AND j.key2=d.oid \#\{extrasql\}", #{klass}) end def #{pname2}_count(extrasql = nil) $og.select("SELECT COUNT(*) FROM #{Utils.table(klass)} AS d, #{Utils.join_table(self, klass)} AS j WHERE j.key1=\#\@oid AND j.key2=d.oid \#\{extrasql\}", #{klass}) end def add_#{name2}(obj, extra = nil) $og.exec("INSERT INTO #{Utils.join_table(self, klass)} (key1, key2) VALUES (\#\@oid, \#\{obj.oid\})") end def del_#{name2}(obj_or_oid, extra = nil) $og.exec("DELETE FROM #{Utils.join_table(self, klass)} WHERE key2=\#\{obj_or_oid.to_i\}") end alias_method :delete_#{name2}, :del_#{name2} def clear_#{pname2} $og.exec("DELETE FROM #{Utils.join_table(self, klass)} WHERE key1=\#\@oid") end } # indirectly enchant the other class of the relation. klass.module_eval %{ def #{pname1}(extrasql = nil) $og.select("SELECT s.* FROM #{Utils.table(self)} AS s, #{Utils.join_table(self, klass)} AS j WHERE j.key2=\#\@oid AND j.key1=s.oid \#\{extrasql\}", #{self}) end def #{pname1}_count(extrasql = nil) $og.select("SELECT COUNT(*) FROM #{Utils.table(self)} AS s, #{Utils.join_table(self, klass)} AS j WHERE j.key2=\#\@oid AND j.key1=s.oid \#\{extrasql\}", #{self}) end def add_#{name1}(obj, extra = nil) $og.exec("INSERT INTO #{Utils.join_table(self, klass)} (key1, key2) VALUES (\#\{obj.oid\}, \#\@oid)") end def del_#{name1}(obj_or_oid, extra = nil) $og.exec("DELETE FROM #{Utils.join_table(self, klass)} WHERE key1=\#\{obj_or_oid.to_i\}") end alias_method :delete_#{name1}, :del_#{name1} def clear_#{pname1} $og.exec("DELETE FROM #{Utils.join_table(self, klass)} WHERE key2=\#\@oid") end } end alias :has_and_belongs_to_many :many_to_many end end # module # Include the meta-language extensions into Module. If the flag is # false the developer is responsible for including the MetaLanguage # module where needed. # if $og_include_meta_language class Module # :nodoc: all include Og::MetaLanguage end end