Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

STI, one controller

I'm new to rails and I'm kind of stuck with this design problem, that might be easy to solve, but I don't get anywhere: I have two different kinds of advertisements: highlights and bargains. Both of them have the same attributes: title, description and one image (with paperclip). They also have the same kind of actions to apply on them: index, new, edit, create, update and destroy.

I set a STI like this:

Ad Model: ad.rb

class Ad < ActiveRecord::Base
end

Bargain Model: bargain.rb

class Bargain < Ad
end

Highlight Model: highlight.rb

class Highlight < Ad
end

The problem is that I'd like to have only one controller (AdsController) that executes the actions I said on bargains or highlights depending on the URL, say www.foo.com/bargains[/...] or www.foo.com/highlights[/...].

For example:

  • GET www.foo.com/highlights => a list of all the ads that are highlights.
  • GET www.foo.com/highlights/new => form to create a new highlight etc...

How can i do that?

Thanks!

like image 703
Pizzicato Avatar asked Mar 09 '11 13:03

Pizzicato


3 Answers

First. Add some new routes:

resources :highlights, :controller => "ads", :type => "Highlight"
resources :bargains, :controller => "ads", :type => "Bargain"

And fix some actions in AdsController. For example:

def new
  @ad = Ad.new()
  @ad.type = params[:type]
end

For best approach for all this controller job look this comment

That's all. Now you can go to localhost:3000/highlights/new and new Highlight will be initialized.

Index action can look like this:

def index
  @ads = Ad.where(:type => params[:type])
end

Go to localhost:3000/highlights and list of highlights will appear.
Same way for bargains: localhost:3000/bargains

etc

URLS

<%= link_to 'index', :highlights %>
<%= link_to 'new', [:new, :highlight] %>
<%= link_to 'edit', [:edit, @ad] %>
<%= link_to 'destroy', @ad, :method => :delete %>

for being polymorphic :)

<%= link_to 'index', @ad.class %>
like image 78
fl00r Avatar answered Oct 31 '22 19:10

fl00r


fl00r has a good solution, however I would make one adjustment.

This may or may not be required in your case. It depends on what behavior is changing in your STI models, especially validations & lifecycle hooks.

Add a private method to your controller to convert your type param to the actual class constant you want to use:

def ad_type
  params[:type].constantize
end

The above is insecure, however. Add a whitelist of types:

def ad_types
  [MyType, MyType2]
end

def ad_type
  params[:type].constantize if params[:type].in? ad_types
end

More on the rails constantize method here: http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-constantize

Then in the controller actions you can do:

def new
  ad_type.new
end

def create
  ad_type.new(params)
  # ...
end

def index
  ad_type.all
end

And now you are using the actual class with the correct behavior instead of the parent class with the attribute type set.

like image 65
Alan Peabody Avatar answered Oct 31 '22 20:10

Alan Peabody


I just wanted to include this link because there are a number of interesting tricks all related to this topic.

Alex Reisner - Single Table Inheritance in Rails

like image 12
Andrew Lank Avatar answered Oct 31 '22 21:10

Andrew Lank