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
end
def 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