all repos — h3rald @ 30e0d83d11af833c3305d4995573bc244c6280f6

The sources of https://h3rald.com

content/articles/simply-on-rails-3-shared-controller.textile

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
----- 
permalink: simply-on-rails-3-shared-controller
filters_pre: 
- erb
- redcloth
title: "Simply on Rails - Part 3: LiteController"
comments: 
- :date: 2007-09-08 04:26:55 +02:00
  :author: Aleksandr
  :url: http://www.ajaxrussia.com
  :id: 58
  :body: Why aren't you using REST?
- :date: 2007-09-08 04:39:37 +02:00
  :author: Fabio Cevasco
  :url: http://www.h3rald.com
  :id: 59
  :body: |-
    @Aleksandr: 
    
    Good question!
    
    The answer is that being it my first project in Rails, I was still a bit unsure about REST.
    
    <spoiler>
    On a side note, I'm currently considering using "ActiveScaffold":http://activescaffold.com/ for things like this now... Will blog about it soon-ish.
    </spoiler>
date: 2007-07-22 06:03:00 +02:00
tags: 
- rails
type: article
toc: true
-----
Enough with concepts, ideas and diagrams: it's time to start coding something. Everyone knows what's the first step when creating a Rails applications, but anyhow, here it is:

<% highlight :ruby do %>
rails italysimply
<% end %>

Then I create a new development database, load it up with the schema I "previously":/blog/simply-on-rails-2-database-design prepared and modify the @config/database.yml@ to be able to connect to it. Nothing new here.
I actually had to modify the schema a little bit:

* I changed all the names for the foreign keys to something more evocative than "has_many" or "has_one"
* I added a _level_ column to the _states_, _availabilities_ and _conditions_ table
* I removed the _description_ column from the categories table

Great, but... hang on: now some of the database tables look awfully similar with each other:

* statuses
* states
* roles
* types
* tags
* conditions
* availabilities
* categories

They all have a name column, some of them have a name column as well, they'll hold only a relative small number of records which will hardly ever be deleted. In fact, I was tempted to use Enums for some of those things...
Anyhow, I'll still have to add and modify data in those tables, so it looks like I kinda need to create 8 controllers, 8 models and about four views for each one of them. No way. Fair enough for the controllers and models, but I'm not going to create 32 views which all look exactly the same. Rails should be smarter than that!And it is, luckily. Derek Sivers & C. came out with an interesting "Shared Controller":http://dereksivers.com/rails-shared-controller.html concept, which could be just what I'm looking for in this case. Actually I need something really simple in this case:

* Put all the CRUD logic into one controller
* Create only one set of views

Here's the controller:

%{color:red}*app/controllers/admin/lite_controller.rb*%
<% highlight :ruby do %>
class Admin::LiteController < ApplicationController

  layout 'admin'

  before_filter :prepare
  
  def prepare
    @item_name = model.to_s
  end
  
  def index
    list
  end

  verify :method => :post, :only => [ :destroy, :create, :update ],
         :redirect_to => { :action => :list }

  def list
    ordering = model.column_names.include?('level') ? 'level ASC' : 'name ASC'
    @items = model.find(:all, :order => ordering)  
    render('lite/list')
  end

  def show
    @item = model.find(params[:id])
    render('lite/show')
  end

  def new
    @item = model.new
    render('lite/new')
  end

  def create
    @item = model.new(params[:"#{@item_name.downcase}"])
    if @item.save
      flash[:notice] = @item_name+' was successfully created.'
      redirect_to :action => 'list'
    else
      render('lite/new')
    end
  end

  def edit
    @item = model.find(params[:id])
    render('lite/edit')
  end

  def update
     @item = model.find(params[:id])
    if @item.update_attributes(params[:"#{@item_name.downcase}"])
      flash[:notice] = @item_name+' was successfully updated.'
      redirect_to :action => 'list'
    else
      render('lite/edit')
    end
  end
end
<% end %>

Then all I need to do is create eight controllers with just a few lines of code in each:

%{color:red}*app/controllers/admin/statuses_controller.rb*%
<% highlight :ruby do %>
class Admin::StatusesController < Admin::LiteController
  def model
    Status
  end
end
<% end %>

Basically, I just need to specify which model the specific controller takes care of, Ruby's inheritance does the rest. The model name will be passed to the views like this:

%{color:red}*app/controllers/admin/lite_controller.rb*%
<% highlight :ruby do %>
def prepare
	@item_name = model.to_s
end
<% end %>

And each method uses the @model@ method to access the model, like this:

%{color:red}*app/controllers/admin/lite_controller.rb*%
<% highlight :ruby do %>
def create
	@item = model.new(params[:"#{@item_name.downcase}"])
	if @item.save
		flash[:notice] = @item_name+' was successfully created.'
		redirect_to :action => 'list'
	else
		render('lite/new')
	end
end
<% end %>

Note how the params are collected:

<% highlight :ruby do %>
@item = model.new(params[:"#{@item_name.downcase}"])
<% end %>

@params[:"#{@item_name.downcase}"]@ at runtime becomes @params[:status]@ or @params[:role]@ etc. etc., depending on which controller is called. Sweet.

The views? Modified accordingly:

%{color:red}*app/views/lite/edit.rb*%
<% highlight :ruby do %>
<h1>Editing &lt;%= @item_name %&gt;</h1>

&lt;% form_tag :action => 'update', :id => @item do %&gt;
  &lt;%= render :partial => 'lite/form' %&gt;
  &lt;%= submit_tag 'Edit' %&gt;
&lt;% end %&gt;

&lt;%= link_to 'Show', :action => 'show', :id => @item %&gt; |
&lt;%= link_to 'Back', :action => 'list' %&gt;
<% end %>

%{color:red}*app/views/lite/_form.rb*%
<% highlight :ruby do %>
&lt;%= error_messages_for 'item' %&gt;
<!--[form:lite]-->
<p><label for="&lt;%= @item_name.downcase %&gt;_name">Name: </label>
&lt;%= text_field @item_name.downcase, 'name',  {:value => @item.name} %&gt;</p>
&lt;% if @item.methods.include?('level') then %&gt; 
  <p><label for="&lt;%= @item_name.downcase %&gt;_level">Level: </label>
  &lt;%= text_field @item_name.downcase, 'level',  {:value => @item.level} %&gt;</p>
&lt;% end %&gt;
<!--[eoform:lite]-->
<% end %>