01.01.06

rubyGreg, part 2

Posted in Gregarius, ruby/rails at 11:13 am by Haris

Well, time to continue with the next tutorial in the process of porting the Gregarius source code to Ruby on Rails. You can read the steps taken so far here. You can also see the results here. Doesn’t look very nice right now, but once we are done with it, it will not be that bad. I had to make some changes to the code while trying to get it to run on Dreamhost, mostly following these instructions. The changes I had to make to the code had to do with the fact that the channel_id is not the name of the foreign key in the original database. Instead, the original Gregarius database uses cid as the foreign key, so we need to add the set_table_name "gregarius_item" line in the Item model. I also had to change the find commands in the controller. Later I found out that the find_all and find_all_by_channel_id forms are deprecated, and instead we are encouraged to use the find(:all,other_options) form instead. So at this point the channels controller looks like this:

class ChannelController < ApplicationController

  def index
    @channels = Channel.find(:all)
  end

  def list_items
    @channel = Channel.find(@params['id'])
    @items = Item.find(:all, :conditions => "channel_id = '#{@params['id']}'")
  end
end

while the items controller looks like this:

class ItemController < ApplicationController

  def details
    @item = Item.find(@params['id'])
  end

  def list_details
    @channel = Channel.find(@params['channel_id'])
    @items = Item.find(:all, :conditions => "channel_id = '#{@params['channel_id']}'")
  end
end

We also had a number of views written, but I am now going to rewrite them, the reason being that I am now going to introduce layouts. The layouts philosophy is as follows: Instead of having files for header, footer etc, and importing them into all the views, you instead have a layout, which contains the header, footer etc, and which imports the content through the @content_for_layout directive. This is better demonstrated via an example. So let us create the file app/views/layouts/standard_layout.rhtml. This will contain the common information present in all views corresponding to the items and channels controllers, and the code for it for now will be:

<html>
    <body>
<!-- The header part of this layout -->
<center>This is the header</center><br />
<!-- The content of this layout -->
    <%= @content_for_layout %>
<!-- The footer part of this layout -->
<br /><center>This is the footer</center>

    </body>
</html>

Notice the <%= @content_for_layout %> part. This is exactly where the content from each view will be displayed. There are two more places where we need to change things. One is the controllers. We need to tell them about the layout. So right after the class ... line in the controllers, we will add the directive

layout "standard_layout"

Next we need to modify the views so that they don’t contain the stuff that is now in the layout file. So app/views/channel/index.rhtml will now look like this:

<h1>All Channels</h1>
<ul>
    <% @channels.each do |channel| %>
        <li><%= link_to(channel.title, {:action => "list_items", :id => "#{channel.id}"}) %></li>
    <% end %>
</ul>

while app/views/channel/list_items.rhtml looks like this:

<h1><%= @channel.title %></h1>
<ul>
    <% @items.each do |item| %>
        <li><%= link_to(item.title, {:action => "details", :id => "#{item.id}", :controller => "item"}) %></li>
    <% end %>
</ul>
<%= link_to("See all items from this feed.", {:action => "list_details",  :controller => "item", :channel_id => "#{@channel.id}"}) %>

And similar changes for the item views. This is what the channels index page would look like at this point:

Implementing a basic layout

We probably should do something more useful with the layout. Let’s try to model it on the layouts from Gregarius. First, I will just copy the style-sheets from the standard theme in Gregarius. These are called look.css and layout.css. The only thing that needs to be changed is the relative path to the images, which I also copied. All these things go to the directories public/stylesheets and public/images respectively. so the relative path is now, instead of ./media/, ../images/. Easy enough. Note that you can also find what is going wrong by looking at the log folder, and the developement.log file.

Now to modify the standard_layout file, we will use what is called a Helper. A helper is simply a place to put methods that you want to call from your templates. In this case, I’ll create a method called show_header. Note that when last time we used the generate script, it created helpers for us, associated to the various controllers. The helper we will use is the applications helper, located in app/helpers/application_helper.rb. Because of its name, the application controller knows about it automagically, and will use it in any templates anywhere. If we wanted to use our own helper, which we might want to do to allow custom themes at some point, we would need to specify that in the application controller. But that’s for another time. For now, lets add the following codes to app/views/layouts/standard_layout.rhtml and app/helpers/application_helper.rb respectively:

<!-- The header part of this layout -->
    <div id="nav" class="frame">
        <%= show_header  %>
    </div>
<!-- The content of this layout -->

and:

# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper

  def show_header
    xm = Builder::XmlMarkup.new
    xm.h1("Gregarius", "id" => "top")
    xm.ul("class" => "navlist") { 
      xm.li("class" => "active") {|x| x << link_to('<span>H</span>ome', :action => 'index', :controller => 'item', :accesskey => 'h')}
      xm.li {|x| x << link_to('<span>R</span>efresh', :action => 'refresh', :controller => 'item', :accesskey => 'r')}
      xm.li {|x| x << link_to('<span>S</span>earch', :action => 'index', :controller => 'item', :accesskey => 's')}
      xm.li {|x| x << link_to('A<span>d</span>min', :action => 'index', :controller => 'admin', :accesskey => 'd')}
    }

  end

end

The show_header method probably needs a bit of explaining. It uses the Builder class, which is designed to create xml content. We start by creating a new xml builder object, called xm. Then we proceed to create a bunch of tags using it. The line xm.h1("Gregarius", "id" => "top") generates the code <h1 id="top">Gregarius</h1>. The next command creates a ul tag, with content whatever the block creates. The lines with the xm.li look a bit weird, but what happens is the following: the parameter in the block stands for the code to be inserted between the opening and closing tags. Then we append (<<) to it a link created by the link_to command that we saw last time, and the rest are just arguments to that command. The reason we did not place the result of the link_to command in the same way as we did the the "Gregarius" above is that the xm builder object escapes the text it is passed, and hence for instance xm.h1("<br />Hi") would produce: <h1>&lt;br /&gt;Hi</h1>, which is not what we want, hence the above workaround.

I will keep it short this time and not add the sidebar and footer yet. For those keeping track, the code is now 112 lines, and rubyGreg currently looks like this, though the links don’t yet work:

Header added

You can actually download the code here, though you would need to change the database.yml file to fit your needs, among other stuff. And of course, you can see the result in action here.

The sidebar will be more interesting, because we need to access the database for it. At some point I will need to also worry about putting plugin hooks at some point, one of the great features of Gregarius. Until then, have a happy new year.

Later

Leave a Comment