From the beginning, I resisted the idea of inherited classes being used for single-table inheritance, because it broke my hope for an abstract model, similar to the abstract controller you can now use.
The idea, is you make a parent class ‘AbstractModel’ that inherits from ActiveRecord::Base, then your models inherit from AbstractModel. It is a nice place to put extra model functionality you want to add.
The basic AbstractModel
Create /app/models/abstract_model.rb
require 'active_record' require 'yaml' class AbstractModel < ActiveRecord::Base # add anything you want here # these hacks are necessary to fix the default assumption for single-table inheritance def self.descents_from_active_record? # :nodoc: #superclass ActiveRecord::Base superclass AbstractModel enddef self.class_name_of_active_record_descendant(klass) if klass.superclass == AbstractModel #puts “returning class name as ’#{klass.name}’” return klass.name elsif klass.superclass.nil? raise ActiveRecordError, ”#{name} doesn’t belong in a hierarchy descending from ActiveRecord” else class_name_of_active_record_descendant(klass.superclass) end end
end
My modifications, in case you want to see what is possible, or want to use what I find handy, are here:
def self.init() YAML::load(File.open(File.dirname(__FILE__) + "/../../config/database.yml")) end def init() self.init end # can I do this? def sql(statement) <a href="http://wiki.rubyonrails.org/rails/pages/ActiveRecord" class="existingWikiWord">ActiveRecord</a>::Base.connection.execute(statement) end #each of my models has a REF constant defined: #REF = %w( * ).map {|a| "articles.#{a}"}.join ', ' # this lets me do things like: #instead of Article.find(1), if you want it joined with the user table, use Article.ufind(1). def self.ufind(item_id, args = {}) args['add'] = 'user' args['limit'] = 1 return find_by_sql( "SELECT #{table_name}.*,#{User::REF} FROM #{table_name} left join users ON users.id = #{table_name}.user_id " + " WHERE #{table_name}.id = #{item_id}" ) end def self.list_with_user(limit = 20, page = 0, order = "#{table_name}.id DESC", where = '', us = nil) where = " WHERE #{where} " if where && !where.length.zero? us ||= self.const_get 'REF' limit ||= 20; page ||= 0; order ||= "#{table_name}.id DESC" # in case nil is passed in. ##{self.const_get 'REF'} return find_by_sql( "SELECT #{us},#{User::REF} FROM #{table_name} left join users ON users.id = #{table_name}.user_id " + " #{where} ORDER BY #{order} LIMIT #{page*limit}, #{limit}" ) end def self.sql_join(args) b = args[:base].downcase.gsub(/s$/,'') || table_name.downcase.gsub(/s$/,'') a = args[:add].downcase.gsub(/s$/,'') args[:limit] ||= self.const_get 'LIM' args[:id] = "#{a}_id" unless args[:id] args[:limit] = "#{args[:page]*args[:limit]}, #{args[:limit]}" if args[:page] res = "SELECT \#{#{b.capitalize}::REF},\#{#{a.capitalize}::REF} FROM #{b}s left join #{a}s ON #{a}s.id = #{b}s.#{a}_id " res << "WHERE #{args[:where]} " if args[:where] res << "ORDER BY #{args[:order_by]} " if args[:order] res << "GROUP BY #{args[:group_by]} " if args[:group] res << "LIMIT #{args[:limit]} " if args[:limit] $stderr.puts "made sql_list_with: #{res}" res end #like has_many, only uses the REF and LIM constants that must be defined. Also allows :add => 'table_to_join_with' def self.has(collection_id, options = {}) validate_options([ :foreign_key, :class_name, :dependent, :conditions, :order, :what, :limit, :finder_sql, :add], options.keys) name = collection_id.to_s cl = name.gsub(/s$/,'').capitalize tbl = table_name.gsub(/s$/,'') #forward immediately to regular one if we can't find the REF constant in our collection package? #return has_many(collection_id, options) unless Object.const_get( cl ).const_get 'REF' options[:order] ||= 'id DESC' options[:limit] ||= '#{LIM}' options[:what] ||= "\#{#{cl}::REF}" #c = Object.const_get( cl ) #allow 'add' options[:finder_sql] ||= sql_join(:base => collection_id.to_s, :add => options[:add], :where => "#{tbl}_id = \#{id}") if options[:add] options[:finder_sql] ||= "SELECT #{options[:what]} FROM #{name} WHERE #{tbl}_id = \#{id} " + "ORDER BY #{options[:order]} LIMIT #{options[:limit]}"# unless self.const_get 'LIM' # if c and c.const_get 'REF' #$stderr.puts "finder_sql is #{options[:finder_sql]}" options.delete :order options.delete :what options.delete :limit options.delete :add has_many(collection_id, options) end
Instead of making a subclass for this, you can take advantage of Ruby’s dynamicness and just add the methods to ActiveRecord::Base:
class ActiveRecord::Base
def method_i_want_to_add(args)
puts "This method is available to all my models."
end
end
It’s a little less hackish.
category:Howto