The dRuby Book

3.3 Putting Them Together

Let’s combine the CGI script that we created first and the ReminderView that we just created.

Download the code files from http://www.druby.org/sidruby/code/, and browse to the reminder_view.rb file.

Set up this script in your web server and run it. It should show the list of items in the browser. Now you’re ready to make things pretty.

Making the View Look Prettier

Displaying the list with <ul> looks too simple. Let’s change the code to display with the <table> tag. Let’s change ReminderView#create_erb as follows and display the CGI:

  def self.create_erb​
  ​ ERB.new(<<EOS)​
  <html><head><title>Reminder</title></head>
  <body>
  <table border='1'>
  <% @db.to_a.each do |k, v| %>
  <tr><td><%= k %></td><td><%=h v %></td></tr>
  <% end %>
  </table>
  </body>
  </html>
  EOS
  end

As you can see, you can easily change the view by replacing the eRuby script.

The table looks good, but it will look even better if it alternates the background color for each row. Let’s first create a class that displays two different colors, one after another. Name the class BGColor.

  class BGColor​
  ​​
  def initialize​
①  ​ @colors = ['#eeeeff', '#bbbbff'] ​
  ​ @count = -1​
  end
  ​ attr_accessor :colors​
  ​​
②  def next_bgcolor ​
  ​ @count += 1​
  ​ @count = 0 if @colors.size <= @count​
  "bgcolor='#{@colors[@count]}'"
  end
  ​​
③  alias :to_s :next_bgcolor ​
  end

BGColor alternates the background between #eeeeff and #bbbbff.

colors=(ary): Sets an array of background colors.

next_bgcolor: Returns background color strings (for example, bgcolor=’#eeeeff’). Returns a different color every time the method is called.

to_s: Alias to next_bgcolor.

Then add the bg_color method to instantiate BGColor from ReminderView.

  class ReminderView​
  ​ ...​
  def bg_color​
  ​ BGColor.new​
  end
  end

Once the bg_color method is added, we can call the method from the ERB template. Let’s add bg_color in the tr tag to set the background color attribute.

  ​<html><head><title>Reminder</title></head>​
  ​<body>​
  ​<table>​
  ​<% bg = bg_color​
  ​ @db.to_a.each do |k, v| %>​
  ​<tr <%= bg %>><td><%= k %></td><td><%=h v %></td></tr>​
  ​<% end %>​
  ​</table>​
  ​</body>​
  ​</html>​

The cool thing about this approach is that you can hide the logic to swap colors and use <%= bg %> as if it were just another tag. The trick is that <%= statement %> calls to_s of the object internally, so <%= bg %> returns the result of bg.to_s, which is an alias to next_bgcolor.

Adding and Deleting Items

So far, we’ve implemented code to list items. In this section, we’ll implement code to add items. Let’s build the view first. We’ll add a text field to add items at the bottom of the item lists.

  class ReminderView​
  ​ ...​
  def self.create_erb​
  ​ ERB.new(<<EOS)​
  <html><head><title>Reminder</title></head>
  <body>
  <form method='post'>
  <table>
  <% bg = bg_color
  @db.to_a.each do |k, v| %>
  <tr <%= bg %>><td><%= k %></td><td><%=h v %></td></tr>
  <% end %>
  <tr <%= bg %>>
  <td><input type="submit" name="cmd" value="add" /></td>
  <td><input type="text" name="item" value="" size="30" /></td>
  </tr>
  </table>
  </form>
  </body>
  </html>
  EOS
  end
  ​ ...​

Let’s add the ReminderView#create_erb method. Once added, let’s run the CGI and make sure that the form is added.

Next, let’s think about parsing the CGI request and adding it to the Reminder server. We’ll follow these steps:

  1. Check the command type of the query. We’ll handle only adding for now.

  2. Retrieve text from the text field.

  3. If there is a string, then normalize the encoding and add the item to the Reminder server.

Let’s add the code to implement the preceding logic.

  class ReminderCGI < WEBrick::CGI​
  def do_request(req, res)​
①  ​ cmd ,= req.query['cmd'] ​
  case cmd​
  when 'add'
  ​ do_add(req, res)​
  end
  end
  ​​
  def do_add(req, res)​
②  ​ item ,= req.query['item'] ​
  return if item.nil? || item.empty?​
③  ​ item.encode('utf-8') ​
  return unless item.valid_encoding?​
  ​ @db.add(item)​
  end

Did you notice the ,= expression at ① and ②? This ,= req.query[key] is an idiom often used for Ruby CGI scripts (see Multiple Assignment).

If item has strings assigned, then the string is normalized at ③, before being added to the Reminder server.

Note that I set the encoding of this CGI and Reminder server to utf-8. You can see it as a Content-Type of the HTML. This means you also have to convert outside strings to utf-8 format. In this example, you convert the string into UTF with encode and then make sure it is converted to the valid encoding using the valid_encoding? method.

So far, our code shows the list of items, and we can add a new item. The last step is to delete an item.

Let’s add logic to do_request to delete the item when delete is specified as a command.

  def do_request(req, res)​
  ​ cmd ,= req.query['cmd']​
  case cmd​
  when 'add'
  ​ do_add(req, res)​
  when 'delete'
  ​ do_delete(req, res)​
  end
  end

Here is the definition of do_delete:

  def do_delete(req, res)​
  ​ key ,= req.query['key']​
  return if key.nil? || key.empty?​
  ​ @db.delete(key.to_i)​
  end

Next, add a view layer to generate a link to the command.

  ​<% bg = bg_color​
  ​ @db.to_a.each do |k, v| %>
  <tr <%= bg %>>​
  ​ <td><%= k %></td>
  <td><%=h v %></td>
  ​ <td><%=a_delete(k)%>X</a><td>
  </tr>
  <% end %>

a_delete is a utility method to generate a link to delete an item. It takes a key of the item to delete. (In a real system, you should not delete an item via a link, because a web bot can automatically follow the link and delete the item.)

  class ReminderView​
  ​ ...​
  def make_param(hash)​
  ​ hash.collect do |k, v|​
  ​ u(k) + '=' + u(v)​
  end.join(';')​
  end
  ​​
  def anchor(query)​
  %Q+<a href="?#{make_param(query)}">+
  end
  ​​
  def a_delete(key)​
  ​ anchor('cmd' => 'delete', 'key'=>key)​
  end

Download the complete script and browse to reminder_final.rb to see the finished product!

We’ve come a long way. If you just want to write a website, it’s easier to use an existing framework. But what I wanted to show you here is how you can add a web view on top of dRuby so that you can access dRuby in various ways. Using frameworks will constrain you to do things in a certain way, but using dRuby and ERB directly will give you more flexibility.