Simply on Rails - Part 3: LiteController

Published on

By Fabio Cevasco

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:

1rails italysimply

Then I create a new development database, load it up with the schema I previously 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 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:

app/controllers/admin/lite_controller.rb

 1class Admin::LiteController < ApplicationController
 2
 3  layout 'admin'
 4
 5  before_filter :prepare
 6  
 7  def prepare
 8    @item_name = model.to_s
 9  end
10  
11  def index
12    list
13  end
14
15  verify :method => :post, :only => [ :destroy, :create, :update ],
16         :redirect_to => { :action => :list }
17
18  def list
19    ordering = model.column_names.include?('level') ? 'level ASC' : 'name ASC'
20    @items = model.find(:all, :order => ordering)  
21    render('lite/list')
22  end
23
24  def show
25    @item = model.find(params[:id])
26    render('lite/show')
27  end
28
29  def new
30    @item = model.new
31    render('lite/new')
32  end
33
34  def create
35    @item = model.new(params[:"#{@item_name.downcase}"])
36    if @item.save
37      flash[:notice] = @item_name+' was successfully created.'
38      redirect_to :action => 'list'
39    else
40      render('lite/new')
41    end
42  end
43
44  def edit
45    @item = model.find(params[:id])
46    render('lite/edit')
47  end
48
49  def update
50     @item = model.find(params[:id])
51    if @item.update_attributes(params[:"#{@item_name.downcase}"])
52      flash[:notice] = @item_name+' was successfully updated.'
53      redirect_to :action => 'list'
54    else
55      render('lite/edit')
56    end
57  end
58end


 1class Admin::LiteController < ApplicationController
2
3 layout admin
4
5 before_filter :prepare
6
7 def prepare
8 item_name</span> = model.to_s <span class="line-numbers"> <a href="#n9" name="n9">9</a></span> <span class="keyword">end</span> <span class="line-numbers"><strong><a href="#n10" name="n10">10</a></strong></span> <span class="line-numbers"><a href="#n11" name="n11">11</a></span> <span class="keyword">def</span> <span class="function">index</span> <span class="line-numbers"><a href="#n12" name="n12">12</a></span> list <span class="line-numbers"><a href="#n13" name="n13">13</a></span> <span class="keyword">end</span> <span class="line-numbers"><a href="#n14" name="n14">14</a></span> <span class="line-numbers"><a href="#n15" name="n15">15</a></span> verify <span class="symbol">:method</span> =&gt; <span class="symbol">:post</span>, <span class="symbol">:only</span> =&gt; [ <span class="symbol">:destroy</span>, <span class="symbol">:create</span>, <span class="symbol">:update</span> ], <span class="line-numbers"><a href="#n16" name="n16">16</a></span> <span class="symbol">:redirect_to</span> =&gt; { <span class="symbol">:action</span> =&gt; <span class="symbol">:list</span> } <span class="line-numbers"><a href="#n17" name="n17">17</a></span> <span class="line-numbers"><a href="#n18" name="n18">18</a></span> <span class="keyword">def</span> <span class="function">list</span> <span class="line-numbers"><a href="#n19" name="n19">19</a></span> ordering = model.column_names.include?(<span class="string"><span class="delimiter">'</span><span class="content">level</span><span class="delimiter">'</span></span>) ? <span class="string"><span class="delimiter">'</span><span class="content">level ASC</span><span class="delimiter">'</span></span> : <span class="string"><span class="delimiter">'</span><span class="content">name ASC</span><span class="delimiter">'</span></span> <span class="line-numbers"><strong><a href="#n20" name="n20">20</a></strong></span> <span class="instance-variable">items = model.find(:all, :order => ordering)
21 render(lite/list)
22 end
23
24 def show
25 item</span> = model.find(params[<span class="symbol">:id</span>]) <span class="line-numbers"><a href="#n26" name="n26">26</a></span> render(<span class="string"><span class="delimiter">'</span><span class="content">lite/show</span><span class="delimiter">'</span></span>) <span class="line-numbers"><a href="#n27" name="n27">27</a></span> <span class="keyword">end</span> <span class="line-numbers"><a href="#n28" name="n28">28</a></span> <span class="line-numbers"><a href="#n29" name="n29">29</a></span> <span class="keyword">def</span> <span class="function">new</span> <span class="line-numbers"><strong><a href="#n30" name="n30">30</a></strong></span> <span class="instance-variable">item = model.new
31 render(lite/new)
32 end
33
34 def create
35 item</span> = model.new(params[<span class="symbol"><span class="symbol">:</span><span class="delimiter">&quot;</span><span class="inline"><span class="inline-delimiter">#{</span><span class="instance-variable">item_name.downcase}"])
36 if item</span>.save <span class="line-numbers"><a href="#n37" name="n37">37</a></span> flash[<span class="symbol">:notice</span>] = <span class="instance-variable">item_name was successfully created.
38 redirect_to :action => list
39 else
40 render(lite/new)
41 end
42 end
43
44 def edit
45 item</span> = model.find(params[<span class="symbol">:id</span>]) <span class="line-numbers"><a href="#n46" name="n46">46</a></span> render(<span class="string"><span class="delimiter">'</span><span class="content">lite/edit</span><span class="delimiter">'</span></span>) <span class="line-numbers"><a href="#n47" name="n47">47</a></span> <span class="keyword">end</span> <span class="line-numbers"><a href="#n48" name="n48">48</a></span> <span class="line-numbers"><a href="#n49" name="n49">49</a></span> <span class="keyword">def</span> <span class="function">update</span> <span class="line-numbers"><strong><a href="#n50" name="n50">50</a></strong></span> <span class="instance-variable">item = model.find(params[:id])
51 if item</span>.update_attributes(params[<span class="symbol"><span class="symbol">:</span><span class="delimiter">&quot;</span><span class="inline"><span class="inline-delimiter">#{</span><span class="instance-variable">item_name.downcase}"])
52 flash[:notice] = @item_name
was successfully updated.
53 redirect_to :action => list
54 else
55 render(lite/edit)
56 end
57 end
58end

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

app/controllers/admin/statuses_controller.rb

1class Admin::StatusesController < Admin::LiteController
2  def model
3    Status
4  end
5end


1class Admin::StatusesController < Admin::LiteController
2 def model
3 Status
4 end
5end

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:

app/controllers/admin/lite_controller.rb

1def prepare
2  @item_name = model.to_s
3end


1def prepare
2 @item_name = model.to_s
3end

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

app/controllers/admin/lite_controller.rb

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


 1def create
2 item</span> = model.new(params[<span class="symbol"><span class="symbol">:</span><span class="delimiter">&quot;</span><span class="inline"><span class="inline-delimiter">#{</span><span class="instance-variable">item_name.downcase}"])
3 if item</span>.save <span class="line-numbers"> <a href="#n4" name="n4">4</a></span> flash[<span class="symbol">:notice</span>] = <span class="instance-variable">item_name+ was successfully created.
5 redirect_to :action => list
6 else
7 render(lite/new)
8 end
9end

Note how the params are collected:

1@item = model.new(params[:"#{@item_name.downcase}"])

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:

app/views/lite/edit.rb

 1<h1>Editing <%= @item_name %></h1>
 2
 3<% form_tag :action => 'update', :id => @item do %>
 4  <%= render :partial => 'lite/form' %>
 5  <%= submit_tag 'Edit' %>
 6<% end %>
 7
 8<%= link_to 'Show', :action => 'show', :id => @item %> |
 9<%= link_to 'Back', :action => 'list' %>


 1<h1>Editing <%= item_name %&gt;&lt;/h1&gt;</span></span>
<span class="line-numbers"> <a href="#n2" name="n2">2</a></span><span class="string"><span class="content"></span></span>
<span class="line-numbers"> <a href="#n3" name="n3">3</a></span><span class="string"><span class="content">&lt;% form_tag :action </span><span class="delimiter">=</span></span>&gt; <span class="string"><span class="delimiter">'</span><span class="content">update</span><span class="delimiter">'</span></span>, <span class="symbol">:id</span> =&gt; <span class="instance-variable">item do %>
4 <%= render :partial => lite/form >
5 <= submit_tag ‘Edit’ >
6< end >
7
8<=
link_to Show, :action => show, :id => @item > |
9<= link_to ‘Back’, :action =
> list %>

app/views/lite/_form.rb

 1<%= error_messages_for 'item' %>
 2<!--[form:lite]-->
 3<p><label for="<%= @item_name.downcase %>_name">Name: </label>
 4<%= text_field @item_name.downcase, 'name',  {:value => @item.name} %></p>
 5<% if @item.methods.include?('level') then %> 
 6  <p><label for="<%= @item_name.downcase %>_level">Level: </label>
 7  <%= text_field @item_name.downcase, 'level',  {:value => @item.level} %></p>
 8<% end %>
 9<!--[eoform:lite]-->


 1<%= error_messages_for ‘item’ >
2<!—[form:lite]—>
3<p><label for="<= item_name.downcase %&gt;_name</span><span class="delimiter">&quot;</span></span>&gt;<span class="constant">Name</span>: &lt;<span class="regexp"><span class="delimiter">/</span><span class="content">label&gt;</span></span> <span class="line-numbers"> <a href="#n4" name="n4">4</a></span><span class="regexp"><span class="content">&lt;%= text_field @item_name.downcase, 'name', {:value =&gt; @item.name} %&gt;&lt;</span><span class="delimiter">/</span></span>p&gt; <span class="line-numbers"> <a href="#n5" name="n5">5</a></span>&lt;<span class="string"><span class="delimiter">% </span><span class="content">if</span><span class="delimiter"> </span></span><span class="instance-variable">item.methods.include?(level) then %>
6 <p><label for="<%= @item_name.downcase >_level">Level: </label>
7 <= text_field @item_name.downcase, ‘level’, {:value => @item.level} ></p>
8< end
%>
9<!—[eoform:lite]—>

Legacy Comments

These comments were imported automatically from an old version of this web site. Scroll down for the newest stuff.

Why aren’t you using REST?

@Aleksandr:

Good question!

The answer is that being it my first project in Rails, I was still a bit unsure about REST.

On a side note, I’m currently considering using ActiveScaffold for things like this now… Will blog about it soon-ish.