require "active_support" require 'uuid' require File.dirname(__FILE__) +'/domain_attribute_description.rb' require File.dirname(__FILE__) +"/reference.rb" require File.dirname(__FILE__) +"/index_description.rb" require File.dirname(__FILE__) +"/lazy_loading_text.rb" require File.dirname(__FILE__) +"/repository_factory.rb" module SdbDal class DomainObject @@items_to_commit=nil @@transaction_depth=0 attr_accessor :sdb_key def initialize(options={}) @attribute_values=HashWithIndifferentAccess.new self.class.attribute_descriptions.each do |attribute_name,description| value=options[attribute_name] value=value || options[attribute_name.intern] if !description.is_collection && value.respond_to?(:flatten) && value.length==1 value=value[0] end @attribute_values[attribute_name]=value end @accessor_cache={} @repository=options[:repository] end def self.primary_key_attribute_names return @primary_key_attribute_names end def id return @attribute_values[:id] if self.class.attribute_descriptions[:id] return base.id end def set_map(keys,values) (0..keys.length-1).each do |i| key=keys[i] value=values[i] @attribute_values[key]=value end end def self.index_names [] end def self.index(name,columns,options={}) return if self.index_descriptions[name] $columns_xxx=columns class_eval <<-GETTERDONE @@index_descriptions||={} unless @@index_descriptions.has_key?(:#{name}) @@index_names||=[] @@index_names<<:#{name} def self.index_names @@index_names end attribute_description=SdbDal::DomainAttributeDescription.new(:#{name},:string,{}) @@index_descriptions[:#{name}]=SdbDal::IndexDescription.new(:#{name},$columns_xxx) end GETTERDONE getter_params=[] finder_code="" params=[] columns.each do |column| if column.respond_to?(:transform) finder_code<<"h[:#{column.name}]=#{column.name.to_s.downcase}\n" getter_params<<"@attribute_values[:#{column.source_column}]" params<<"#{column.name.to_s.downcase}" else finder_code<<"h[:#{column}]=#{column.to_s.downcase}\n" getter_params<<"@attribute_values[:#{column}]" params<<"#{column.to_s.downcase}" end end find_scope=":all" if options[:unique] find_scope=":first" end class_eval <<-GETTERDONE def #{name} return self.class.calculate_#{name}(#{getter_params.join(",")}) end def self.calculate_#{name}(#{params.join(",")}) index_description=index_descriptions[:#{name}] h={} #{finder_code} index_description.format_index_entry(@@attribute_descriptions,h) end def self.find_by_#{name}(#{params.join(",")},options={}) options[:params]={:#{name}=>self.calculate_#{name}(#{params.join(",")})} options[:index]=:#{name} options[:index_value]=self.calculate_#{name}(#{params.join(",")}) find(#{find_scope},options) end GETTERDONE end @@index_descriptions ||={} def self.index_descriptions @@index_descriptions || {} end def self.data_attribute(name,type,options={}) class_eval <<-GETTERDONE @@attribute_descriptions||=HashWithIndifferentAccess.new @@non_clob_attribute_names||=[] @@clob_attribute_names||=[] @@on_destroy_blocks||=[] def self.on_destroy_blocks @@on_destroy_blocks || [] end def self.attribute_descriptions @@attribute_descriptions end def self.non_clob_attribute_names @@non_clob_attribute_names end def self.clob_attribute_names @@clob_attribute_names end def is_dirty(attribute_name) if @attribute_values[attribute_name]!= nil && @attribute_values[attribute_name].respond_to?(:value) return @attribute_values[attribute_name].is_dirty else return true end end GETTERDONE return if self.attribute_descriptions.has_key?(name) attribute_description=DomainAttributeDescription.new(name,type,options) self.attribute_descriptions[name] = attribute_description @primary_key_attribute_names||=[] if attribute_description.is_primary_key @primary_key_attribute_names< item ,:index=>index) end def remove_from_#{attribute_description.name}(item) @attribute_values[:#{attribute_description.name}] ||=[] @attribute_values.each do |reference| if reference.targets?(item) @attribute_values.remove(reference) return end end end GETTERDONE elsif type==:clob class_eval <<-GETTERDONE def #{attribute_description.name} if @attribute_values[:#{attribute_description.name}]!= nil && @attribute_values[:#{attribute_description.name}].respond_to?(:value) return @attribute_values[:#{attribute_description.name}].value else return @attribute_values[:#{attribute_description.name}] end end GETTERDONE else class_eval <<-GETTERDONE def #{attribute_description.name} return @attribute_values[:#{attribute_description.name}] end GETTERDONE end class_eval <<-GETTERDONE def #{attribute_description.name}=(xvalue) @attribute_values[:#{attribute_description.name}] =xvalue end def #{attribute_description.name}? return @attribute_values[:#{attribute_description.name}] end def self.find_by_#{attribute_description.name}(#{attribute_description.name.to_s.downcase},options={}) options[:params]={:#{attribute_description.name}=>#{attribute_description.name.to_s.downcase}} find(#{scope},options) end GETTERDONE end def self.belongs_to(domain_class,foreign_key_attribute=nil,accesser_attribute_name=nil,options={}) foreign_key_attribute ||= "#{domain_class.to_s.downcase}_id".to_sym accesser_attribute_name ||=domain_class.to_s.downcase class_eval <<-GETTERDONE def #{accesser_attribute_name} #{domain_class}.find(self.#{foreign_key_attribute}) end GETTERDONE end def self.many_to_many(domain_class, through_class, reflecting_key_attributes=nil, foriegn_key_attribute=nil, accesser_attribute_name=nil, options={}) reflecting_key_attributes ||= primary_key_attribute_names.map{|x|self.name+"_"+x.to_s} reflecting_key_attributes =arrayify(reflecting_key_attributes) reflecting_array_code="[:"+reflecting_key_attributes.join(",:")+"]" accesser_attribute_name ||=domain_class.to_s.downcase+'s' order="" if options[:order_by] if options[:order] order="result=DomainObject.sort_result(result,:#{options[:order_by]},:#{options[:order]})" else order="result=DomainObject.sort_result(result,:#{options[:order_by]},:ascending)" end end foriegn_key_attributes=options[:foriegn_key_attribute] || "#{domain_class.to_s.downcase}_id".to_sym foriegn_key_attributes=arrayify(foriegn_key_attributes) fkey_array_code="[:"+foriegn_key_attributes.join(",:")+"]" class_eval <<-XXDONE def #{accesser_attribute_name} #unless @accessor_cache.has_key? :#{accesser_attribute_name} through_results= #{through_class}.find(:all,:map=>{:keys=>#{reflecting_array_code},:values=>DomainObject.arrayify(self.primary_key)}) result=[] through_results.each do |through_result| item= #{domain_class}.find(through_result.#{foriegn_key_attribute}) result<< item if item end #{order} # @accessor_cache[:#{accesser_attribute_name}]=result return result #end #return @accessor_cache[:#{accesser_attribute_name}] end def connect_#{domain_class.to_s.downcase}(#{domain_class.to_s.downcase}) connector=#{through_class}.new connector.set_map(#{fkey_array_code},DomainObject.arrayify(#{domain_class.to_s.downcase}.primary_key)) connector.set_map(#{reflecting_array_code},DomainObject.arrayify(self.primary_key)) connector.save end XXDONE end def DomainObject.transaction @@items_to_commit||=[] @@transaction_depth+=1 begin yield rescue # catch all raise $! # rethrow ensure @@transaction_depth-=1 end if @@transaction_depth==0 @@items_to_commit.uniq.each do |item | item.save! end @@items_to_commit=[] end end def DomainObject.sort_result(results,sort_by,order=:ascending) non_null_results=[] null_results=[] results.each { |i| sorter_value=i.get_attribute(sort_by) if sorter_value non_null_results< b_val } if order!=:ascending sorted_results.reverse! end sorted_results.concat( null_results) sorted_results end def get_sortable_attribute(attr_name) a_val= self.get_attribute(attr_name) return 0 unless a_val if a_val.class== Time return a_val.to_f else return a_val end end def self.has_many(domain_class, reflecting_key_attributes=nil, accesser_attribute_name=nil, options={}) reflecting_key_attributes ||= primary_key_attribute_names.map{|x|self.name.downcase+"_"+x.to_s} reflecting_key_attributes =arrayify(reflecting_key_attributes) reflecting_array_code="[:"+reflecting_key_attributes.join(",:")+"]" accesser_attribute_name ||=domain_class.to_s.downcase+'s' order="" if options[:order_by] order=",:order_by=>:#{options[:order_by]}" end if options[:order] order<<",:order=>:#{options[:order]}" end if options[:dependent] class_eval <<-XXDONE on_destroy_blocks<<"#{accesser_attribute_name}.each{|item|item.destroy}" XXDONE end # if options[:tracking] # #add add_xxx method # #add to list of tracker attributes # # class_eval <<-XXDONE # # @@attribute_descriptions[ :#{accesser_attribute_name}_tracker]=TrackerDescription.new(:#{accesser_attribute_name}_tracker,:#{domain_class},:#{reflecting_key_attribute}) # # def #{accesser_attribute_name} # find_tracked_list(:#{accesser_attribute_name}_tracker,#{domain_class},:#{reflecting_key_attribute}) # # end # def add_to_#{accesser_attribute_name}(item) # # item.save! # add_to_tracked_list(:#{accesser_attribute_name}_tracker,#{domain_class.to_s},:#{reflecting_key_attribute},item.primary_key) # item.set_attribute(:#{reflecting_key_attribute},self.primary_key) # # # # end # XXDONE # else class_eval <<-XXDONE def #{accesser_attribute_name} if !@accessor_cache.has_key? :#{accesser_attribute_name} result= #{domain_class}.find(:all,:map=>{:keys=>#{reflecting_array_code},:values=>DomainObject::arrayify(self.primary_key)}#{order}) @accessor_cache[:#{accesser_attribute_name}]=result || [] end return @accessor_cache[:#{accesser_attribute_name}] end XXDONE # end end def self.AttributeDescription(name) return attribute_descriptions[name] end def self.ColumnDescription(name) return column_descriptions[name] end def primary_key result=[] self.class.primary_key_attribute_names.each do |key_part| result<<@attribute_values[key_part ] end return result[0] if result.length==1 return result end def primary_key=(value) key=DomainObject::arrayify(value) self.class.primary_key_attribute_names.each do |key_part| @attribute_values[ key_part]=key.shift end end def method_missing(method_symbol, *arguments) #:nodoc: method_name = method_symbol.to_s case method_name.last when "=" if @attribute_values.has_key?(method_name.first(-1)) @attribute_values[method_name.first(-1)] = arguments.first else super end when "?" @attribute_values[method_name.first(-1)] else @attribute_values.has_key?(method_name) ? @attribute_values[method_name] : super end end def index_values result={} self.class.index_names.each do |name| result[name]= self.send("#{name}") end result end def set_attribute(name,value) @attribute_values[name]=value end def get_attribute(name) @attribute_values[name] end def to_xml result= "<#{self.class.table_name}>" attributes.each do |key,value| if value.respond_to?(:flatten) result<<"<#{key}>" value.each do |sub_value| result<<"#{h sub_value.to_s}" end result<<"" else result<<"<#{key}>#{h value.to_s}" end end result<< "" result end def destroy(options={}) if self.class.on_destroy_blocks self.class.on_destroy_blocks.each do |block| instance_eval <<-XXDONE #{block} XXDONE end end if self.primary_key self.repository(options).destroy(self.table_name,self.primary_key) end end def attributes result={} # #todo refactor to avoid this copy self.class.attribute_descriptions.values.each do |description| if !description.is_clob || is_dirty(description.name) result[description.name]=@attribute_values[description.name] end end return result end def find_tracked_list(tracking_attribute,other_class,reflecting_attribute) # #if the attribute has no value do a query list=@attribute_values[tracking_attribute] if !list list=other_class.query_ids(:params=>{reflecting_attribute=>self.primary_key}) @attribute_values[tracking_attribute]=list self.save! end return other_class.find_list(list) end def add_to_tracked_list(tracking_attribute,other_class,reflecting_attribute,value) list=@attribute_values[tracking_attribute] if !list list=other_class.query_ids(:params=>{reflecting_attribute=>self.primary_key}) end list<0 # #we are in a transaction. wait to commit @@items_to_commit<0 @primary_key_attribute_names.each do |key_name| attributes[key_name]=id.shift end attributes[:repository]=self.repository(options) return istantiate(attributes,self.repository(options)) end return nil end def find_recent_with_exponential_expansion(time_column,how_many,options={}) found=[] tries=0 timespan=options[:timespan_hint] || 60*60*4 params = options[:params] || {} while found.lengthtime) found= find(:all,:limit=>how_many,:order=>:descending,:order_by => time_column, :params=>params) tries=tries+1 timespan=timespan*3 end return found end end end end