The Models
To implement, create the top level object as a model as normal (with generate scaffold works best for me), but include a column called "type" that is a string. You also need to include every column that all your subclasses will use (though you can, of course, add columns later, as normal).
For each subclass, create a model (again, I recommend generate scaffold). Modify the model so that the class inherits from the desired superclass, and delete the database migration. Migrate the database, and you are pretty much done.
See also:
http://wiki.rubyonrails.org/rails/pages/SingleTableInheritance
http://www.juixe.com/techknow/index.php/2006/06/03/rails-single-table-inheritance/
You can access all the objects in the class hierarchy through the top level class, or a specific class through other classes.
A < B < C < ActiveRecord::Base
C.find :all # Get all objects in the table, whatever the class
B.find :all # Get objects of type B only (not A)
Your superclass will need all the fields of all the subclasses - otherwise there will be no table column for field. Each subclass needs its own controller and map.resources entry in routes.rb.
Controllers and Views
So how does STI affect the controllers and the views?
Possibly not at all. You can have one controller for each model, all completely independant, all with their own set of views. However, there is a good chance that there will be some overlap. It is quite likely that you will want to use the same "show" view for each model, for example. Let us suppose SubClass is a subclass of SuperClass; the default show method is this:
def show
@sub_class = SubClass.find(params[:id])
end
It is very easy to point the render method to the SuperClass view (remembering to rename the variable to what super_class/show.html.erb is expecting):
def show
@super_class = SubClass.find(params[:id])
render :template => 'super_class/show'
end
We can go a step further. If we set up SubClassController to inherit from SuperClassController, we do not need a show method in SubClassController at all; it can all be handled in SuperClassController. This will work without any changes to SuperClassController, but your object will be of the SuperClass type, and you will lose all the benefits of subclassing. It is better to instantiate your object as the subclass:
def show
@super_class = eval("#{params[:controller].classify}.find(params[:id])")
render :template => 'super_class/show'
end
Other methods for the SuperClass (destroy can be used as is):
def index
@super_class = eval("#{params[:controller].classify}.find")
render :template => 'super_class/index'
end
def edit
@super_class = eval("#{params[:controller].classify}.find(params[:id])")
render :template => 'super_class/update'
end
def create
@super_class = eval("#{params[:controller].classify}.new(params[:#{params[:controller].singularize}])")
if @super_class.save
flash[:notice] = 'Record was successfully created.'
redirect_to(:controller => 'super_class' , :action => 'show')
else
render :template => 'super_class/new'
end
end
def update
@super_class = eval("#{params[:controller].classify}.find(params[:id])")
if @super_class.update_attributes(params[params[:controller].singularize.to_sym])
flash[:notice] = 'Record was successfully updated.'
redirect_to(:action => 'show')
else
render :action => 'edit'
end
end
One last point. In your helps, instead of naming a model for the links, use polymorphic, eg,
edit_polymorphic_path(super_class)
rather than edit_super_class_path(super_class)
. This will make Rails point to the right controller for your subclass, whatever it might be.Struggling with Ruby: Contents Page
2 comments:
great summary of STI and controllers and views! Thanks!
You write:
We can go a step further.... This will work without any changes to SuperClassController, ...
I think it is not possible without any changes in SperClassController. If you put no action definition in your SubClassController how it would know that it should use view from SuperClass? I tried, but it does not work.
Post a Comment