all repos — h3rald @ 9187a4dd3e01b0592103432a479da5892ef80ec8

The sources of https://h3rald.com

contents/articles/simply-on-rails-3-shared-controller.html

 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
-----
title: Simply on Rails - Part 3: LiteController
content-type: article
timestamp: 1185076980
tags: rails
-----
<p>Enough with concepts, ideas and diagrams: it&#8217;s time to start coding something. Everyone knows what&#8217;s the first step when creating a Rails applications, but anyhow, here it is:</p>
<div class='ruby'><pre><code>rails italysimply</code></pre></div><p>Then I create a new development database, load it up with the schema I <a href="/blog/simply-on-rails-2-database-design">previously</a> prepared and modify the <code>config/database.yml</code> to be able to connect to it. Nothing new here.<br />
I actually had to modify the schema a little bit:</p>
<ul>
	<li>I changed all the names for the foreign keys to something more evocative than &#8220;has_many&#8221; or &#8220;has_one&#8221;</li>
	<li>I added a <em>level</em> column to the <em>states</em>, <em>availabilities</em> and <em>conditions</em> table</li>
	<li>I removed the <em>description</em> column from the categories table</li>
</ul>
<p>Great, but&#8230; hang on: now some of the database tables look awfully similar with each other:</p>
<ul>
	<li>statuses</li>
	<li>states</li>
	<li>roles</li>
	<li>types</li>
	<li>tags</li>
	<li>conditions</li>
	<li>availabilities</li>
	<li>categories</li>
</ul>
<p>They all have a name column, some of them have a name column as well, they&#8217;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&#8230;<br />
Anyhow, I&#8217;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&#8217;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 &amp; C. came out with an interesting <a href="http://dereksivers.com/rails-shared-controller.html">Shared Controller</a> concept, which could be just what I&#8217;m looking for in this case. Actually I need something really simple in this case:</p>
<ul>
	<li>Put all the <span class="caps">CRUD</span> logic into one controller</li>
	<li>Create only one set of views</li>
</ul>
<p>Here&#8217;s the controller:</p>
<p><span style="color:red;"><strong>app/controllers/admin/lite_controller.rb</strong></span><br />
<div class='ruby'><pre><br />
<div class='ruby'><pre><code>class Admin::LiteController &amp;lt; ApplicationController</p>
layout &#8216;admin&#8217;
before_filter :prepare

def prepare
@item_name = model.to_s
end

def index
list
end
verify :method =&gt; :post, :only =&gt; [ :destroy, :create, :update ],
:redirect_to =&gt; { :action =&gt; :list }
def list
ordering = model.column_names.include?(&#8216;level&#8217;) ? &#8216;level <span class="caps">ASC</span>&#8217; : &#8216;name <span class="caps">ASC</span>&#8217;
@items = model.find(:all, :order =&gt; ordering)
render(&#8216;lite/list&#8217;)
end
def show
@item = model.find(params[:id])
render(&#8216;lite/show&#8217;)
end
def new
@item = model.new
render(&#8216;lite/new&#8217;)
end
def create
<code>item = model.new(params[:"#{</code>item_name.downcase}&quot;])
if @item.save
flash[:notice] = @item_name+&#8217; was successfully created.&#8217;
redirect_to :action =&gt; &#8216;list&#8217;
else
render(&#8216;lite/new&#8217;)
end
end
def edit
@item = model.find(params[:id])
render(&#8216;lite/edit&#8217;)
end
def update
@item = model.find(params[:id])
if <code>item.update_attributes(params[:"#{</code>item_name.downcase}&quot;])
flash[:notice] = @item_name+&#8217; was successfully updated.&#8217;
redirect_to :action =&gt; &#8216;list&#8217;
else
render(&#8216;lite/edit&#8217;)
end
end
<p>end</code></pre></div></notextile></p>
<p>Then all I need to do is create eight controllers with just a few lines of code in each:</p>
<p><span style="color:red;"><strong>app/controllers/admin/statuses_controller.rb</strong></span><br />
<div class='ruby'><pre><code>class Admin::StatusesController &lt; Admin::LiteController
  def model
    Status
  end
end</code></pre></div></p>
<p>Basically, I just need to specify which model the specific controller takes care of, Ruby&#8217;s inheritance does the rest. The model name will be passed to the views like this:</p>
<p><span style="color:red;"><strong>app/controllers/admin/lite_controller.rb</strong></span><br />
<div class='ruby'><pre><code>def prepare
	@item_name = model.to_s
end</code></pre></div></p>
<p>And each method uses the <code>model</code> method to access the model, like this:</p>
<p><span style="color:red;"><strong>app/controllers/admin/lite_controller.rb</strong></span><br />
<div class='ruby'><pre><code>def create
	@item = model.new(params[:"#{@item_name.downcase}"])
	if @item.save
		flash[:notice] = @item_name+' was successfully created.'
		redirect_to :action =&gt; 'list'
	else
		render('lite/new')
	end
end</code></pre></div></p>
<p>Note how the params are collected:</p>
<div class='ruby'><pre><code>@item = model.new(params[:"#{@item_name.downcase}"])</code></pre></div><p><code>params[:"#{</code>item_name.downcase}&quot;]@ at runtime becomes <code>params[:status]</code> or <code>params[:role]</code> etc. etc., depending on which controller is called. Sweet.</p>
<p>The views? Modified accordingly:</p>
<p><span style="color:red;"><strong>app/views/lite/edit.rb</strong></span><br />
<div class='ruby'><pre><code>&lt;h1&gt;Editing <br />
<div class='ruby'><pre><code>&amp;lt;h1&amp;gt;Editing &lt;%= @item_name %&gt;&amp;lt;/h1&amp;gt;</p>
<p>&lt;% form_tag :action =&gt; &#8216;update&#8217;, :id =&gt; @item do <span>&gt;<br />
  &lt;</span>= render :partial =&gt; &#8216;lite/form&#8217; <span>&gt;<br />
  &lt;</span>= submit_tag &#8216;Edit&#8217; <span>&gt;<br />
&lt;</span> end %&gt;</p>
<p>&lt;%= link_to &#8216;Show&#8217;, :action =&gt; &#8216;show&#8217;, :id =&gt; @item <span>&gt; |<br />
&lt;</span>= link_to &#8216;Back&#8217;, :action =&gt; &#8216;list&#8217; %&gt;</code></pre></div></notextile></p>
<p><span style="color:red;"><strong>app/views/lite/_form.rb</strong></span><br />
<div class='ruby'><pre><code><%= error_messages_for 'item' %>
&lt;!--[form:lite]--&gt;
&lt;p&gt;&lt;label for="<%= @item_name.downcase %>_name"&gt;Name: &lt;/label&gt;
<%= text_field @item_name.downcase, 'name',  {:value =&gt; @item.name} %>&lt;/p&gt;
<% if @item.methods.include?('level') then %> 
  &lt;p&gt;&lt;label for="<%= @item_name.downcase %>_level"&gt;Level: &lt;/label&gt;
  <%= text_field @item_name.downcase, 'level',  {:value =&gt; @item.level} %>&lt;/p&gt;
<% end %>
&lt;!--[eoform:lite]--&gt;</code></pre></div></p>