class ActiveRecord::Base class_inheritable_array :default_eager_loading class << self alias_method :has_one_without_cti, :has_one def find_with_default_eager_loading(*params) if default_eager_loading if params.last.is_a?(Hash) opts = params.last else opts = {} params.push(opts) end opts[:include] ||= [] opts[:include] = [opts[:include]] unless opts[:include].is_a?(Array) opts[:include] += default_eager_loading end find_without_default_eager_loading(*params) end alias_method_chain :find, :default_eager_loading def class_table_inheritance(options = {}) table_name = options[:subclass_table] || name.demodulize.tableize primary_key_name = options[:subclass_foreign_key] || "#{superclass.name.demodulize.underscore}_id" has_extra_columns_from( :table => table_name, :foreign_key => primary_key_name, :proxy_class_name => 'ExtraColumns', :proxy_class_belongs_to => self.name, :delegate_new_associations => true, :save_before_superclass_callbacks => true ) end def has_extra_columns_from(options, &block) table_name = options[:table] primary_key_name = options[:foreign_key] || "#{name.demodulize.underscore}_id" proxy_class_name = options[:proxy_class_name] || "ExtraColumnsFor#{table_name.camelize}" proxy_class_namespace = (options[:proxy_class_namespace] || name).constantize proxy_symbol = "extra_columns_for_#{table_name}".to_sym proxy_class = proxy_class_namespace.const_get(proxy_class_name) rescue nil unless proxy_class proxy_class = proxy_class_namespace.const_set(proxy_class_name, Class.new(ActiveRecord::Base)) proxy_class.class_eval do set_table_name table_name set_primary_key primary_key_name def self.reloadable?; false; end end proxy_class.class_eval(&block) if block_given? end has_one_without_cti proxy_symbol, :class_name => proxy_class.name, :foreign_key => primary_key_name, :dependent => true if options[:save_before_superclass_callbacks] || true # We need the after_save filter for this association to run /before/ any other after_save's already registered on a superclass, # and before any after_creates or after_updates. This calls for some hackery: proxy_save_callback = @inheritable_attributes[:after_save].pop @inheritable_attributes[:after_create] ||= [] @inheritable_attributes[:after_create].unshift(proxy_save_callback) @inheritable_attributes[:after_update] ||= [] @inheritable_attributes[:after_update].unshift(proxy_save_callback) end # adds this to the class-inheritable array of default eager loads: self.default_eager_loading = [proxy_symbol] unless options[:default_eager_loading] == false class_eval <<-EOV alias_method :#{proxy_symbol}_old, :#{proxy_symbol} def #{proxy_symbol} #{proxy_symbol}_old or self.#{proxy_symbol} = #{proxy_class.name}.new end def save(*args) self.#{proxy_symbol} ||= #{proxy_class.name}.new super end # this doesn't happen automatically on update, so we'll make it: after_update {|record| record.#{proxy_symbol}.save } alias_method :write_attribute_super, :write_attribute def write_attribute(attr_name, attr_value) if self.class.columns.find {|c| c.name == attr_name} write_attribute_super(attr_name, attr_value) else self.#{proxy_symbol}.write_attribute(attr_name, attr_value) end end alias_method :old_attributes, :attributes def attributes all_attributes = old_attributes.update(self.#{proxy_symbol}.attributes) all_attributes.delete(#{proxy_class_name}.primary_key) if self.class.primary_key != #{proxy_class_name}.primary_key all_attributes end class << self alias_method :method_missing_super, :method_missing def method_missing(method_symbol, *parameters) if method_symbol.to_s =~ /^find_by_(.+)/ attributes = $1.split('_and_') super_attributes = attributes & columns.map {|c| c.name} sub_attributes = attributes - super_attributes if super_attributes.size > 0 # TODO (uwe): Only use super attributes first # then filter by sub_attributes method_missing_super(method_symbol, *parameters) else result = #{proxy_class_name}.method_missing(method_symbol, parameters) if result if result.is_a? Array result.map {|entry| entry.base} else result = result.base end else result end end else method_missing_super(method_symbol, *parameters) end end delegate :foreign_keys, :validates_uniqueness_of, {:to => #{proxy_class_name}} end EOV delegate_methods = proxy_class.column_names delegate_methods += proxy_class.column_names.map {|name| "#{name}=".to_sym } delegate_methods += proxy_class.column_names.map {|name| "#{name}?".to_sym } proxy_class.reflect_on_all_associations.each do |reflection| delegate_methods += case reflection.macro when :has_many, :has_and_belongs_to_many [reflection.name, "#{reflection.name}=".to_sym, "#{reflection.name.to_s.singularize}_ids=".to_sym] when :has_one [reflection.name, "#{reflection.name}=".to_sym, "build_#{reflection.name}".to_sym, "create_#{reflection.name}".to_sym] when :belongs_to [reflection.name, "#{reflection.name}=".to_sym, "#{reflection.name}?".to_sym, "build_#{reflection.name}".to_sym, "create_#{reflection.name}".to_sym] end end delegate *(delegate_methods << {:to => proxy_symbol}) proxy_class.belongs_to(:base, :class_name => options[:proxy_class_belongs_to], :foreign_key => primary_key_name) if options[:proxy_class_belongs_to] if options[:delegate_new_associations] class_eval <<-EOV # associations on this subclass get added to the proxy class, and then the relevant methods delegated to the proxy object def self.belongs_to(name, *params) ExtraColumns.belongs_to(name, *params) delegate name, "\#{name}=".to_sym, "\#{name}?".to_sym, "build_\#{name}".to_sym, "create_\#{name}".to_sym, :to => :#{proxy_symbol} end def self.has_one(name, *params) ExtraColumns.has_one(name, *params) delegate name, "\#{name}=".to_sym, "build_\#{name}".to_sym, "create_\#{name}".to_sym, :to => :#{proxy_symbol} end def self.has_many(name, *params) ExtraColumns.has_many(name, *params) delegate name, "\#{name}=".to_sym, "\#{name.to_s.singularize}_ids=".to_sym, :to => :#{proxy_symbol} end def self.has_and_belongs_to_many(name, *params) ExtraColumns.has_and_belongs_to_many(name, *params) delegate name, "\#{name}=".to_sym, "\#{name.to_s.singularize}_ids=".to_sym, :to => :#{proxy_symbol} end EOV end end end end