Reputation: 6946
Say there is a product controller which you want to have an index (list products) action. Easy. Now say you have an admin and store parts in your project. Both need to list products, but in a slightly different manner (store's one shouldn't have this edit product link for instance). They also use different layouts.
So far my idea is to have two product controllers under different namespaces - app/controllers/admin/products_controller.rb
and app/controllers/store/products_controller.rb
- each then having its own views and layouts. But I suspect this may lead to WET code. Or to references to other controller views (which imo breaks modularity and hence should be avoided).
So, the actual question: is there a more DRY (or in fact proper) way to achieve the above?
I'm not sure the title actually reflects the question. But, on the other hand, if it were, I could probably google the answer.
EDIT As of 3.1, Rails supports template inheritance.
Upvotes: 2
Views: 3937
Reputation: 6946
If only there were some kind of view inheritance... So that one can subclass controller without need to supply all its views. Good thing is that there is this patch. Bad thing is that it can't make it to the core for quite a while.
Having applied it to my rails 2.2, I managed to have the following answer to the original question.
Subclassing controller
ProductController has been blessed with the twins:
class Products::AdminController < ProductsController
layout 'admin'
before_filter :authenticate
end
and
class Products::StoreController < ProductsController
layout 'store'
before_filter :find_cart
end
This itself looks quite nice since each of them as well carries its own initialization part.
Changing routes
map.resources :products, :controller => 'products/admin', :path_prefix => 'admin',
:name_prefix => 'admin_'
map.resources :products, :controller => 'products/store', :path_prefix => 'store',
:only => [:show, :index], :name_prefix => 'store_'
Not an easy route, defo. But, hey, after this point everything just works (assuming you fixed path helpers) with ProductController views and partials.
Shared views changes
Each subclass controller has its own version of index.html.erb. Everything else is shared in a base class.
Speaking about path helpers in shared templates. What used to be
<% form_for @product ... %>
becomes
<% form_for [controller_name, @product] ... %>
and thins like
<%= link_to products_path %>
turn into
<%= link_to send("#{controller_name}_products_path") %>
I don't know if it is all worth it, but that is a way. Anyone knows why if there are plans to include this patch in rails soon?
Upvotes: 0
Reputation: 39335
If the way you're displaying products between the admin section and the store section is constant except for the admin links (Create, Edit, Destroy), then I think it would be easiest to create a partial for your product. I assume you have a way of telling whether the user is an admin or not (I'll just use admin? for simplicity below). Inside your partial you do something like this...
<div class="product">
<div class="productheader">
<%=h product.title %>
</div>
<div class="productdescription>
<%=h product.description %>
</div>
<% if admin? %>
<div class="productadmin">
<%= link_to "Delete", destroy_product_url %>
<%= link_to "Edit", edit_product_url %>
</div>
<% end %>
</div>
Be sure to name this partial _product.html.erb (the underscore tells rails that the template is a partial). Create a folder in the app/views directory of your application called shared and store the partial there.
To render this partial in your other views, simply call the render method and pass the partial parameter.
A single product:
<%= render(:partial => "shared/product", :object => @a_product) %>
Multiple products:
<%= render(:partial => "shared/product", :collection => @products) %>
Layouts can be applied to partials by adding the layout parameter. Partial layouts must be prefixed with the underscore but stored in the app/views directory associated with the controller.
<%= render(:partial => "shared/product", :object => @a_product, :layout => "somelayout" %>
Upvotes: 2
Reputation: 532545
The approach that I take is to have a single controller for products and add code to it to detect the role that the user plays and conditionally set view data based on that role. This includes both actual model data and data used only by the view to determine which bits of the interface to display. The view itself, then, contains some small amount of code that is able to act on the role-based data and render only those bits that are relevant to the particular role. One might argue that this is injecting either some small bit of business logic into the view or some small bit of display logic into the controller -- and those arguments have some validity. However, I find that it's really more of a balancing act between principles and I prefer value DRY over MVC-purity.
Upvotes: 1
Reputation: 35151
You're describing the Model-View-Controller Pattern, in which models views and controllers can vary orthogonally (or more or less orthogonally, depending on how its implemented).
Very basically, you have one View that allows edits and one that doesn't. Again, depending on implementation, the editable view may derived form the uneditable view. In either case, either the controller or some higher-level code will conditionally chose the right view.
Upvotes: 0