all repos — h3rald @ b3442cd80d633f1412303de98a8301e8b8fd86c0

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
 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
-----
title: "Simply on Rails - Part 3: LiteController"
content-type: article
timestamp: 1185076980
tags: "rails"
-----
<p>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:</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=/articles/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'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'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 &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'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'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'
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') ? &#8216;level <span class="caps">ASC</span>' : &#8216;name <span class="caps">ASC</span>'
@items = model.find(:all, :order =&gt; ordering)
render(&#8216;lite/list')
end
def show
@item = model.find(params[:id])
render(&#8216;lite/show')
end
def new
@item = model.new
render(&#8216;lite/new')
end
def create
<code>item = model.new(params[:"#{</code>item_name.downcase}&quot;])
if @item.save
flash[:notice] = @item_name+' was successfully created.'
redirect_to :action =&gt; &#8216;list'
else
render(&#8216;lite/new')
end
end
def edit
@item = model.find(params[:id])
render(&#8216;lite/edit')
end
def update
@item = model.find(params[:id])
if <code>item.update_attributes(params[:"#{</code>item_name.downcase}&quot;])
flash[:notice] = @item_name+' was successfully updated.'
redirect_to :action =&gt; &#8216;list'
else
render(&#8216;lite/edit')
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'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', :id =&gt; @item do <span>&gt;<br />
  &lt;</span>= render :partial =&gt; &#8216;lite/form' <span>&gt;<br />
  &lt;</span>= submit_tag &#8216;Edit' <span>&gt;<br />
&lt;</span> end %&gt;</p>
<p>&lt;%= link_to &#8216;Show', :action =&gt; &#8216;show', :id =&gt; @item <span>&gt; |<br />
&lt;</span>= link_to &#8216;Back', :action =&gt; &#8216;list' %&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>