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 “has_many” or “has_one”</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… 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…<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 & 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 &lt; ApplicationController</p> 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 <span class="caps">ASC</span>' : ‘name <span class="caps">ASC</span>' @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 <code>item = model.new(params[:"#{</code>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 <code>item.update_attributes(params[:"#{</code>item_name.downcase}"]) flash[:notice] = @item_name+' was successfully updated.' redirect_to :action => ‘list' else render(‘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 < 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 => '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}"]@ 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><h1>Editing <br /> <div class='ruby'><pre><code>&lt;h1&gt;Editing <%= @item_name %>&lt;/h1&gt;</p> <p><% form_tag :action => ‘update', :id => @item do <span>><br /> <</span>= render :partial => ‘lite/form' <span>><br /> <</span>= submit_tag ‘Edit' <span>><br /> <</span> end %></p> <p><%= link_to ‘Show', :action => ‘show', :id => @item <span>> |<br /> <</span>= link_to ‘Back', :action => ‘list' %></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' %> <!--[form:lite]--> <p><label for="<%= @item_name.downcase %>_name">Name: </label> <%= text_field @item_name.downcase, 'name', {:value => @item.name} %></p> <% if @item.methods.include?('level') then %> <p><label for="<%= @item_name.downcase %>_level">Level: </label> <%= text_field @item_name.downcase, 'level', {:value => @item.level} %></p> <% end %> <!--[eoform:lite]--></code></pre> </div> </p> |