require "active_support" require File.dirname(__FILE__) +'/uuid.rb' require File.dirname(__FILE__) +'/property_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 NotRelational class DomainModel @@items_to_commit=nil @@transaction_depth=0 attr_accessor :sdb_key def initialize(options={}) @attribute_values=HashWithIndifferentAccess.new copy_attributes(options) @accessor_cache={} end def copy_attributes(hash) self.class.attribute_descriptions.each do |attribute_name,description| if hash.has_key?(attribute_name) or hash.has_key?(attribute_name.intern) value=hash[attribute_name] value=value || hash[attribute_name.intern] if !description.is_collection && value.respond_to?(:flatten) && value.length==1 value=value[0] end @attribute_values[attribute_name]=value else @attribute_values[attribute_name]=nil unless @attribute_values.has_key?(attribute_name) end end 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(index_name,columns,options={}) @index_descriptions||={} return if self.index_descriptions[index_name] is_index_encrypted=@is_encrypted is_index_encrypted=options[:is_encrypted] if options.has_key?(:is_encrypted) $columns_xxx=columns class_eval <<-GETTERDONE unless @index_descriptions.has_key?(:#{index_name}) @index_names||=[] @index_names<<:#{index_name} def self.index_names @index_names end attribute_description=NotRelational::PropertyDescription.new(:#{index_name},:string,{}) @index_descriptions[:#{index_name}]=NotRelational::IndexDescription.new(:#{index_name},$columns_xxx,#{is_index_encrypted.to_s}) 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 #{index_name} return self.class.calculate_#{index_name}(#{getter_params.join(",")}) end def self.calculate_#{index_name}(#{params.join(",")}) index_description=index_descriptions[:#{index_name}] h={} #{finder_code} index_description.format_index_entry(@@attribute_descriptions,h) end def self.find_by_#{index_name}(#{params.join(",")},options={}) options[:params]={:#{index_name}=>self.calculate_#{index_name}(#{params.join(",")})} options[:index]=:#{index_name} options[:index_value]=self.calculate_#{index_name}(#{params.join(",")}) find(#{find_scope},options) end GETTERDONE end @index_descriptions ||={} def self.index_descriptions @index_descriptions || {} end def self.is_encrypted? return @is_encrypted end def self.encrypt_me class_eval <<-GETTERDONE @is_encrypted=true GETTERDONE end def self.property(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].is_a?(LazyLoadingText) return @attribute_values[attribute_name].is_dirty else return true end end GETTERDONE @is_encrypted||=false is_prop_encrypted=@is_encrypted is_prop_encrypted=options[:is_encrypted] if options.has_key?(:is_encrypted) return if self.attribute_descriptions.has_key?(name) attribute_description=PropertyDescription.new(name,type,is_prop_encrypted,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==:text 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 if options.has_key?(:enum) attribute_name=attribute_description.name.to_s x="" enum_val=1 options[:enum].each { |enum| x<<"#{enum.to_s.upcase} = #{enum_val}\n" enum_val+=1 class_eval <<-GETTERDONE def is_#{attribute_name}_#{enum.to_s.downcase}? #{attribute_name} == #{attribute_name.capitalize}::#{enum.to_s.upcase} end GETTERDONE } class_eval <<-GETTERDONE class #{attribute_name.capitalize} #{x} end GETTERDONE end 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.underscore.downcase if foreign_key_attribute==:without_prefix # #send all attributes and let the other class figure it out} class_eval <<-GETTERDONE def #{accesser_attribute_name} #{module_name+domain_class.to_s}.find(@attribute_values) end GETTERDONE elsif foreign_key_attribute==:with_prefix # #the column names that start with the name of the other class are the # fkeys fkeys=[] self.class.attribute_descriptions.each do |attribute_name,description| if attribute_name.index(domain_class.to_s.downcase)==0 fkeys<{:keys=>#{reflecting_array_code},:values=>DomainModel.arrayify(self.primary_key)}) result=[] through_results.each do |through_result| item= #{module_name+domain_class.to_s}.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=#{module_name+through_class.to_s}.new connector.set_map(#{fkey_array_code},DomainModel.arrayify(#{domain_class.to_s.downcase}.primary_key)) connector.set_map(#{reflecting_array_code},DomainModel.arrayify(self.primary_key)) connector.save end XXDONE end def DomainModel.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 DomainModel.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={}) if reflecting_key_attributes==:without_prefix reflecting_key_attributes = primary_key_attribute_names.map{|x|x.to_s} end 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.underscore.pluralize order="" if options[:order_by] order="{:order_by=>:#{options[:order_by]}" end if options[:order] if order.length>0 order << "," else order<<"{" end order<<":order=>:#{options[:order]}" end if order.length==0 order << "{}" else order<<"}" end if options[:dependent] class_eval <<-XXDONE on_destroy_blocks<<"#{accesser_attribute_name}.each{|item|item.destroy}" XXDONE end class_eval <<-XXDONE def create_child_#{domain_class.to_s.downcase}(options=nil) result=#{module_name+domain_class.to_s}.new(options) result.copy_attributes(self.primary_key_hash) result end XXDONE # 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} h={} pkey=DomainModel::arrayify(self.primary_key) #{reflecting_array_code}.each do |key| h[key]=pkey.shift end result= #{module_name+domain_class.to_s}.find_by_index(h,#{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_hash result={} self.class.primary_key_attribute_names.each do |key_part| result[key_part]=@attribute_values[key_part ] end return result end def primary_key=(value) key=DomainModel::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_text? || 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<desc.format_index_entry(self.attribute_descriptions,values)} options[:index]=desc.name options[:index_value]=desc.format_index_entry(self.attribute_descriptions,values) options.merge!(more_options) return find(:all,options) end end options={} options[:params]={} options.merge!(more_options) values.each{|key,value|options[:params][key]=value} return find_every(options) end def find(*arguments) scope = arguments.slice!(0) options = arguments.slice!(0) || {} case scope when :all then return find_every(options) when :first then return find_one(options) else return find_single(scope, options) end end def find_one(options={}) options[:limit]=1 find_every(options).first end def find_every(options={}) results=[] untyped_results=self.repository.query(self.table_name,attribute_descriptions,options) untyped_results.each do |item_attributes| results<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 def all(options={}) return find_every(options) end end def self.module_name result="" result=self.name.slice(0, self.name.rindex(":")+1) if self.name.rindex(":") result end end end