Ruby on Rails: Creating a 10 minute wiki

Posted by Adrian O'Connor Tue, 01 May 2007 13:27:00 GMT

Further to a previous article, explaining the virtues of a wiki for open communication, here is a very basic (very basic indeed) implementation of a wiki, in Rails, that can work as a very useful baseline for growing your own bespoke tool.

I shall assume that you have knowledge of creating a Rails application, configuring the database and running generators etc.

Source code is included, for download, at the end of the article.

Wiki Project

After creating your wiki project (i.e. rails wiki) and your database (mine is wiki_development) you need to create your model. I am a fan of using migrations to do this, so I shall create the application’s only model, WikiPage.

ruby script/generate model WikiPage

Our wiki pages will have a url, some text and, for good measure, time stamp columns. That is all. This dedscribes our entire application.

Here is my migration

001_create_wiki_pages.rb:
class CreateWikiPages < ActiveRecord::Migration
  def self.up
    create_table :wiki_pages do |t|
      t.column "page_url", :string
      t.column "page_text", :text
      t.column "created_at", :datetime
      t.column "updated_at", :datetime
    end
  end

  def self.down
    drop_table :wiki_pages
  end
end

When you run rake db:migrate, assuming everything is wired up correctly, your database should contain one table.

I am going to use a controller called wiki to perform the ‘heavy’ lifting of the application.

ruby script/generate controller wiki

I know that my wiki controller will need to perform at least two different tasks: show and edit.

I shall define the following in my controller:

def index
  redirect_to :action => :browse
end

def browse
end

def edit
end

both the browse and edit actions will need to load a wiki page, based on the url. Urls are going to be of the form: /wiki/browse/MyPageName, so params[:id] is what we’re interested in. If the id is missing, we’ll default to a page called start.

I’ll add this method to my controller:
def page_url
  url = params[:id] ||= "Start"
end

Naturally, the browse and edit actions will want to load a WikiPage object based on the supplied URL. However, the way to create a new page in a wiki is to browse to a URL that doesn’t yet exist, and then edit that page. This means that we need to handle missing pages by ‘pretending’ they exist in both actions. We’ll create a get_current_page method, to avoid repeating ourselves:

private

  def get_current_page

    current_page = WikiPage.find(:first, :conditions => 'page_url = \'' + page_url + '\''  )

    if (current_page == nil)

      current_page = WikiPage.new
      current_page.page_url = page_url
      current_page.page_text = ''

    end

    return current_page

  end

I can now flesh out the actions to load a WikiPage (for browse) and to save a wiki page on post back (for edit):

def browse

  @wiki_page = get_current_page

end

def edit

  @wiki_page = get_current_page

  if (request.post?)

    @wiki_page.update_attributes(params[:wiki_page])
    @wiki_page.save!

    redirect_to :action => :browse, :id => @wiki_page.page_url

  end

end

That’s all very basic stuff, and you’ll know most it, because it is what you learned from Agile Web Development with Rails.

Our HTML files, once populated to contain the minimum support for our functionality (and a small measure of style), look like this:

views/layouts/wiki.rhtml:
<html>
<head>
<style type="text/css">
#page_title { border-bottom:dotted 1px #dadada; margin-bottom:18px; }
#page_content { margin:12px 0px; padding:0px 8px; border:dotted 1px #aaa; border-bottom:dotted 1px #aaa; }
#page_content h1 { margin:4px 0px; }
#page_content textarea { width:100%; border:none; }
#page_options { padding:4px; font-family:verdana; font-size:8pt; }
#page_meta_data { padding:4px; font-family:verdana; font-size:8pt; color:#ccc; }
#page_options a:link, #page_options a:visited { color:#555; text-decoration:none; }
#page_options a:hover, #page_options a:active { color:red; text-decoration:underline; }
a:link, a:visited { color:navy; }
a:hover, a:active { color:purple; }
</style>
</head>
<body>
    <%= yield %>
</body>
</html>
/views/wiki/browse.rhtml:
<div id="page_content">
    <h1 id="page_title"><%= @wiki_page.page_url %></h1>
    <%= @wiki_page.processed_page_text %>
</div>

<div id="page_options">
    <%= link_to("home", :action => :index) %>
    <%= link_to("edit", :action => :edit, :id => @wiki_page.page_url) %>
</div>

<div id="page_meta_data">
    Created at <%= @wiki_page.created_at %><br />
    Last updated at <%= @wiki_page.updated_at %><br />
</div>
/views/wiki/edit.rhtml
<h1>Editing '<%= @wiki_page.page_url %>'</h1>
<form method="post">
<div id="page_content">
    <%= text_area(:wiki_page, :page_text) %>
</div>
<div id="page_options">
    <input type="submit" value="Save" />
    <input type="button" value="Cancel" onclick="location = '/wiki/browse/<%= @wiki_page.page_url %>';" />
</div>
</form>

OK, one final thing to do. In browse I don’t display the plain text of the wiki page. I call a non-existent method in WikiPage, processed_page_text. This is because our wiki should retain things like paragraph breaks without requiring the user to enter raw HTML. There is also another convention in simple wikis, in that if you smash two capitalized words together, LikeThis, that they will automatically become a link, LikeThis. It’s crude but effective.

I added the following method to my WikiPage model that both formats text, using textile, and performs the processing of words containing two or more capital letters:

/models/wiki_page.rb
require 'redcloth'

def processed_page_text

  r = RedCloth.new self.page_text.gsub(/(([A-Z][a-z]*)([A-Z][a-z]*)+)/, '<a href="/wiki/browse/\1">\1</a>')

  return r.to_html

end

When you fire the application up and point at the browse controller, you should see an emply Start page. You can edit this page, grow your wiki and modify it to meet your own requirements!

Comments

Leave a response

Comments