Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Single Controller, multiple (inherited) classes (rails 3)

I have a base class inherited by 2 others via Single Table Inheritance. I want all subclasses to share the same controller/views for various reasons-the only real difference is in the model's functionality.

However, when I try to use link_to "stuff", instance_of_child I get complaints about being unable to find the correct page.

I've tried messing with match '/subclass' => redirect('/parent') but that yields weird links that make no sense. Any suggestions? I'm pretty new at rails, and I admit that my understanding of routes.rb is still limited-however, I'm not entirely sure that is even where I should be looking.

like image 855
user632657 Avatar asked Feb 24 '11 16:02

user632657


1 Answers

From http://www.alexreisner.com/code/single-table-inheritance-in-rails:

If you’ve ever tried to add STI to an existing Rails application you probably know that many of your link_to and form_for methods throw errors when you add a parent class. This is because ActionPack looks at the class of an object to determine its path and URL, and you haven’t mapped routes for your new subclasses. You can add the routes like so, though I don’t recommend it:

# NOT recommended:
map.resources :cars,        :as => :vehicles, :controller => :vehicles
map.resources :trucks,      :as => :vehicles, :controller => :vehicles
map.resources :motorcycles, :as => :vehicles, :controller => :vehicles

This only alleviates a particular symptom. If we use form_for, our form fields will still not have the names we expect (eg: params[:car][:color] instead of params[:vehicle][:color]). Instead, we should attack the root of the problem by implementing the model_name method in our parent class. I haven’t seen any documentation for this technique, so this is very unofficial, but it makes sense and it works perfectly for me in Rails 2.3 and 3:

def self.inherited(child)  
  child.instance_eval do
    def model_name
      Vehicle.model_name
    end
  end
  super 
end

This probably looks confusing, so let me explain:

When you call a URL-generating method (eg: link_to("car", car)), ActionPack calls model_name on the class of the given object (here car). This returns a special type of string that determines what the object is called in URLs. All we’re doing here is overriding the model_name method for subclasses of Vehicle so ActionPack will see Car, Truck, and Motorcycle subclasses as belonging to the parent class (Vehicle), and thus use the parent class’s named routes (VehiclesController) wherever URLs are generated. This is all assuming you’re using Rails resource-style (RESTful) URLs. (If you’re not, please do.)

To investigate the model_name invocation yourself, see the Rails source code for the ActionController::RecordIdentifier#model_name_from_record_or_class method. In Rails 2.3 the special string is an instance of ActiveSupport::ModelName, in Rails 3 it’s an ActiveModel::Name

like image 194
Michelle Tilley Avatar answered Sep 30 '22 01:09

Michelle Tilley