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 <%= @item_name %></h1> <% form_tag :action => 'update', :id => @item do %> <%= render :partial => 'lite/form' %> <%= submit_tag 'Edit' %> <% end %> <%= link_to 'Show', :action => 'show', :id => @item %> | <%= link_to 'Back', :action => 'list' %> <% end %> %{color:red}*app/views/lite/_form.rb*% <% highlight :ruby do %> <%= 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]--> <% end %> |