Dry dry dry

Hello everyone!
Ive been on rails for the last month or so and really enjoyed this
framework.
However its very hard to find documentation on how to do things right!

For example every beginner after a while realizes that having similar
CRUD actions in every controller and views for them is not DRY at all!

Few days ago I found http://dereksivers.com/rails-shared-controller.html
I quite like his generic controller, not Nub class though !
And I’ve adopted it for my project as:

controllers/basic_crud_controller.rb

class BasicCRUDController < ApplicationController
ITEMS_PER_PAGE = 15
before_filter :initialize_crud_controller
verify :method => :post, :only => [ :destroy, :create, :update ],
:redirect_to => { :action => :list }
def index
list
end
def list
@items = model_class.find :all, :limit => ITEMS_PER_PAGE,
:offset => ITEMS_PER_PAGE * (@sess[:page] -
1)
@pages = Paginator.new self, model_class.count, ITEMS_PER_PAGE,
@sess[:page]
render ‘shared/list’
end
def new
@item = model_class.new
#@editform = model_class.editform
render ‘shared/new’
end
def edit
@item = model_class.find(params[:id])
#@editform = model_class.editform
render ‘shared/edit’
end
def create
@item = model_class.new(params[:edit])
if @item.save
#flash[:notice] = ‘Region was successfully created.’
flash[:notice] = sprintf(_(:added),
(model_class.table_name.to_sym))
redirect_to :action => :list
else
#@editform = model_class.editform
render ‘shared/new’
end
end
def update
@item = model_class.find(params[:id])
if @item.update_attributes(params[:edit])
#flash[:notice] = ‘Region was successfully updated.’
flash[:notice] = sprintf(
(:updated),
(model_class.table_name.to_sym))
redirect_to :action => :list #:edit, :id => @region
else
#@editform = model_class.editform
render ‘shared/edit’
end
end
def destroy
model_class.find(params[:id]).destroy
flash[:notice] = sprintf(
(:deleted),
_(model_class.table_name.to_sym))
redirect_to :action => :list
end
private
def initialize_crud_controller
id = self.object_id
session[id] = { :page => 1 } unless session[id]
@sess = session[id]
@model_name = model_class.table_name.singularize
end
end

By the way, is this a good way to go?
Should something like this be generated when you generate scaffold?
I believe it should!

Then our custom controller looks like this:

controller/regions_controller.rb

class RegionsController < BasicCRUDController
def model_class
Region
end
end

Looks very DRY to me!

Now I would like to DRY the views as well
Its very easy with list:

views/shared/list.rhtml

Listing <%= @model_name.pluralize %>

<%= link_to 'New ' + @model_name, :action => :new %> <%= link_to 'Previous Page', { :page => @pages.current.previous } if @pages.current.previous %> <%= pagination_links(@pages) %> <%= link_to 'Next Page', { :page => @pages.current.next } if @pages.current.next %> <% for column in controller.model_class.content_columns %> <% end %>

<% for item in @items %>

<%= render :partial => 'list', :locals => { :item => item } %> <% end %>
<%= column.human_name %>
<%= link_to 'Edit', :action => :edit, :id => item %> <%= link_to 'Destroy', { :action => :destroy, :id => item }, :confirm => "Delete #{@model_name}: #{item.name} ?", :post => true %>
...

And the custom view for list item:

views/region/_list.rhtml

<%= item.name %> ...

Thats looks promising?
But there always will be but!
The new/edit templates seem not working so far

Consider this:

views/shared/new.rhtml

New <%= @model_name %>

<%= start_form_tag :action => :create %> <%= render :partial => 'shared/form' %> <%= submit_tag 'Create' %> <%= end_form_tag %> <%= link_to 'Back', :action => :list %> ...

views/shared/_form.rhtml

<%= error_messages_for @model_name.to_sym %>

<%= render :partial => ‘edit’ %>

views/region/_edit.rhtml

Name
<%= text_field :region, :name %>

...

This way of doing thigs seems to be the right one.
So if anyone will get this last bit (new/edit) to work please let me
know!

I would appreciate any help and suggestions!
Thanks!

Hi. A few thoughts…

It’s okay to “repeat yourself” across multiple controllers. In my mind,
controllers should be fairly short.

I’ve been where you are – a lot of controllers look similar, so it’s
tempting to combine actions with a little bit of meta magic. But I
advise against it. You never know when you’ll need to do something
specific in one or more actions.

With regards to being DRY… the principle’s core message is that every
piece of information in a system should have a single, authorative
source or representation. (A hash (dictionary) of real estate terms and
the database columns they map to, for example.)

The Pragmatic Programmers never intended for you to NEVER repeat
yourself. It’s okay to say something twice. Just make sure that when
it’s said, it’s acting under the influence of that single source.

Cheers,

  • Daniel

Hi Daniel

The king of design I was describing above is that what I need right now.
Just simple operations with DB and common design. And I believe many
other developers are doing the same thing. Besides if I would want
something different I always can owerride the DEFAULT behaviour. Isn’t
that one of the RoR main principle? Abstraction, metaprogramming, rails,
ruby, less code, easy?
Anyway its just my opinion.

And its not just controllers. The templates are big and cluttered with
all sorts of mess. And what if views should look same. Ok. You have 50
different controllers that should look exactly same. This is the main
thing!

So could you please explain what disadvantaget of this approach you see?

Thanks for your thoughts

There is nothing wrong with taking this approach Igor if it meets your
needs. It is more DRY that way. I think Daniel was just saying that
often you need to tweak this or that in the controller methods so you
end up overriding a fair amount losing many of the benefits. Also
another reason it is used less common is that it might be easier to
visualize what is going on in a controller when you see all the
methods in one place, much more difficult when split across files.
Might not be an issue for you especially if you use mostly the same
code everywhere.

Justin Ghetland and Stuart Halloway’s Streamlined generator takes this
approach currently, so you are not the only one thinking along these
lines.

I find it to be an interesting idea myself, one which I am going to
experiment around with too. It certainly would be more DRY if you
don’t have to override that often.

Blessings,

Jeff B.
MasterView Template Engine Developer
http://masterview.org/

Igor Su wrote:

And its not just controllers. The templates are big and cluttered with
all sorts of mess. And what if views should look same. Ok. You have 50
different controllers that should look exactly same. This is the main
thing!

If you feel combining controllers and views makes sense then do it.

Igor Su wrote:

So could you please explain what disadvantaget of this approach you see?

Like Jeff said, I think the primary disadvantage is a lack of
flexibility. I’ll also say it doesn’t scale well. I’m working with 5
major models, all of which have very similar functionality (setting
schedules, selling services) and tried dearly to get everything to work
in one set of controllers.

At the time it made sense. SchedulesController should handle schedules
for everything! Well, after about two weeks, I realized it wasn’t
working. There were too many idiosyncracies within each of the five
models that the controller was quickly becoming ugly and unwieldly.

I’m a fan of context over consistency. Code in a manner that makes sense
for your situation. (I might add, your situation right now. Don’t get
too caught up in designing for ages to come.)

  • Daniel