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!
Solution 1:
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 %>
Solution 2:
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.
Solution 3:
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