Skip to content

A simple Wiki with Ruby on Rails

by Michael Nielsen on November 12, 2007

I prepared the following simple demo for RailsNite Waterloo. It’s a very simple Wiki application, illustrating some basic ideas of Ruby on Rails development.

To get the demo running, we need a Ruby on Rails installation. I won’t explain here how to get such an installation going. See the Rails site to get things up and running. I’ll assume that you’re using an installation which includes Ruby on Rails version 1.2.* with MySQL, running on Windows, from the command line. Most of this should work with other installations as well, but I haven’t tested it.

We start from the command line, and move to the “rails_apps” directory, which typically sits somewhere within the Ruby on Rails installation. From the command line we run:

rails wiki
cd wiki

This creates a new directory called wiki, and installs some basic files into that directory. What are those files? To understand the
answer to that question, what you need to understand is that Ruby on Rails really has two parts.

The first part is the Ruby programming language, which is a beautiful object-oriented programming language. Ruby is a full-featured programming language, and can be used to do all the things other programming languages can do. Like most programming languages, Ruby has certain strengths and weaknesses; Ruby sits somewhere in the continuum of programming languages near Python and Smalltalk.

The second part of the framework is Ruby on Rails proper, or “Rails” as we’ll refer to it from now on. Rails is essentially a suite of programs, written in Ruby, that make developing web applications in Ruby a lot easier. What happened when you ran rails wiki above is that Rails generated a basic Ruby web application for you. What all those files are that were generated is the skeleton of a Ruby web application.

So what Rails does is add an additional layer of functionality on top of Ruby. This sounds like it might be ugly, but in fact Ruby is designed to be easily extensible, and in practice Rails feels like a very natural extension of ordinary Ruby programming.

To get a Rails application going, we need to do one more piece of configuration. This is generating a database that will be used to store the data for our application. We do this using mysqladmin, which comes with MySQL:

mysqladmin -u root create wiki_development

If you’re not all that familiar with MySQL you may be wondering whether you’ll need to learn it as well as Ruby and Rails. The answer is that for basic Rails applications you only need to know the very basics of MySQL. For more advanced applications you’ll need to know more, but the learning curve is relatively gentle, and you can concentrate on first understanding Ruby and Rails. In this tutorial I’ll assume that you have a basic understanding of concepts such as tables and rows, but won’t use any complex features of relational databases.

With all our configuration set up, lets start a local webserver. From the command line type:

ruby script/server

Now load up http://localhost:3000/ in your browser. You should see a basic welcome page. We’ll be changing this shortly.

Let’s get back to the database for a minute. You may wonder why we need a database at all, if Ruby is an object-oriented language. Why not just use Ruby’s internal object store?

This is a good question. One reason for using MySQL is that for typical web applications we may have thousands of users accessing a site simultaneously. Ruby wasn’t designed with this sort of concurrency in mind, and problems can occur if, for example, two users try to modify the same data near-simultaneously. However, databases like MySQL are designed to deal with this sort of problem in a transparent fashion. A second reason for using MySQL is that it can often perform operations on data sets much faster than Ruby could. Thus, MySQL offers a considerable performance advantage.

Using MySQL in this way does create a problem, however. Ruby is an object-oriented programming language, and it’s designed to work with objects. If all our data is being stored in a database, how can we use Ruby’s object-orientation? Rails offers a beautiful solution to this problem, known as Object Relational Mapping (ORM). One of the core pieces of Rails is a class known as ActiveRecord which provides a way of mapping between Ruby objects and rows in the database. The beauty of ActiveRecord is that from the programmer’s point of view it pretty much looks like the rows in the database are Ruby objects!

This is all a bit abstract. Let’s work through an example of ActiveRecord in action. The basic object type in our wiki is going to be a page. Let’s ask Rails to generate a model named Page:

ruby script/generate model Page

You should see the following:

      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/page.rb
      create  test/unit/page_test.rb
      create  test/fixtures/pages.yml
      create  db/migrate
      create  db/migrate/001_create_pages.rb

For our purposes, the important files are app/models/page.rb, which contains the class definition for the Page model, and 001_create_pages.rb, which is the file that will set up the corresponding table in the database

(You’ll notice, by the way, that 001_create_pages.rb is pluralized and in lower case, when our original model is not. This is one of the more irritating design decisions in Rails – it automatically pluralizes model names to get the corresponding database table name, and the cases can vary a lot. It’s something to watch out for.)

The next step is to decide what data should be associated with the Page model. We’ll assume that every page has a title, and a body, both of which are strings. To generate this, edit the file db/migrate/001_create_pages.rb so that it looks like this:

class CreatePages < ActiveRecord::Migration
  def self.up
    create_table :pages do |t|
      t.column "title", :string
      t.column "body", :string
    end
  end

  def self.down
    drop_table :pages
  end
end

This is known as a migration. It’s a simple Ruby file that controls changes made to the database. The migration can also be reversed – that’s what the “def self.down” method definition does. By using a series of migrations, it is possible to both make and undo modifications to the database structure used by your Rails application.

Notice, incidentally, that Rails created most of the migration code for you when you asked it to generate the model. All you have to do is to fill in the details of the fields in the database table / object model.

The actual creation of the database is now done by invoking the rake command, which is the Ruby make utility:

rake db:migrate

Incidentally, when run in devleopment mode (the default, which we’re using) the Rails webserver is really clever about reloading files as changes are made to them. This means that you can see the effect of changes as you make them. However, this doesn’t apply to changes to the structure of the database, and it’s usually a good idea to restart the webserver after using rake to run a migration. If you’re following along, do so now by hitting control C to interrupt the webserver, and then running ruby script/server again.

Now that we have a Page class set up, the next step is to add a way of interacting with the model over the web. Our wiki is going to have three basic actions that it can perform: (1) creating a page; (2) displaying a page; (3) editing a page.

To make this happen, we ask Rails to generate what is known as a controller for the Page model:

ruby script\generate controller Page

Once again, this generates a whole bunch of Ruby code. The most important for us is the file app/controller/page_controller.rb. When generated it looks like:

class PageController < ApplicationController
end

What we want is to add some Ruby methods that correspond to the three actions (displaying, creating, and editing a page) that we want to be able to do on a page. Edit the file to add the three method definitions:

class PageController < ApplicationController

def create_page
end

def display_page
end

def edit_page
end

end

(Incidentally, the names here are a bit cumbersome. I started with the simpler method names create, display and edit, and then wasted an hour or so, confused by various weird behaviour caused by the fact that the word display is used internally by Ruby on Rails. A definite gotcha!)

These methods don’t do anything yet. In your browser, load the URL http://localhost:3000/page/create_page. You’ll get an error message that says “Unknown action: No action responded to create”. In fact, what has happened is that Rails parses the URL, and determines from the first part (“page”) that it should load page_controller.rb, and from the second part that it should call the create_page action.

What is missing is one final file. Create the file app/views/page/create_page.rhtml, and add the following:

Hello world

Now reload the URL, and you should see “Hello world” in your browser. Let’s improve this so that it displays a form allowing us to create an instance of the Page model. Let’s re-edit the file so that it looks like this instead:

<% form_for :page, :url => {:action => :save_page} do |form| %>
  <p>Title: <%= form.text_field :title, :size => 30 %></p>
  <p>Body: <%= form.text_area :body, :rows => 15 %></p>
  <p><%= submit_tag "Create page" %></p>
<% end %>

There’s a lot going on in this code snippet. It’s not a raw html file, but rather a template which blends html and Ruby. In particular, if you want to execute Ruby code, you can do so using:

<% INSERT RUBY CODE HERE %>

All Ruby code is treated as an expression, and returns a value. If you want the value of that expression to be displayed by the template, you use a slight variant of the above, with an extra equals sign near the start:

<%= INSERT RUBY EXPRESSION TO BE EVALUATED HERE %>

The first line of the snippet tells us that this is a form for objects of class Page, and that when the form is submitted, it should call the save_page action in the page controller, which we’ll add shortly. The result of the form is pretty straightforward – it does more or less what you’d expect it to do. Let’s add a save action (i.e., a method) to the page controller:

def save_page
  new_page = Page.create(params[:page])
  redirect_to :action => "display_page", :page_id => new_page.id
end

What happens is that when the submit tag is clicked by the user, the details of the form field are loaded into a Ruby has called params[:page]. We then create a new Page model object using Page.create(params[:page]), which we call new_page. Finally, we redirect to the action display_page, passing it as a parameter a unique id associated to the new page we’ve just created.

Let’s now create a view for the display_page action. Start by editing the display_page action so that it looks like:

def display_page
  @page = Page.find_by_id(params[:page_id])
end

What is happening is that the :page_id we were passed before is being passed in as a hash, params[:page_id], and we are now asking Rails to find the corresponding model object, and assign it to the variable @page. We now create a view template that will display the corresponding data, in app/views/page/display_page.rhtml:

<h1><%= @page.title %></h1>
<%= @page.body %>

Okay, time to test things out. Let’s try loading up the URL http://localhost:3000/page/create_page. Type in a title and some body text, and hit the “Create page” button. You should see a webpage with your title and body text.

Let’s modify the page slightly, adding a link so we can create more pages. Append the following to the above code for the display_page template:

<%= link_to "Create page", :action => "create_page" %>

This calls a Rails helper method that generates the required html. Of course, in this instance it would have been almost equally easy to insert the html ourselves. However, the syntax of the above helper method generalizes to much more complex tasks as well, and so it’s worth getting used to using the Rails helpers.

In our skeleton for the page controller we had an edit_page action. This could be done along very similar lines to the create_page action we’ve already described. In fact, there’s an interesting alternative, which is to use Rails’ built in Ajax (Javascript) libraries to edit the fields inplace. We’ll try this instead.

To do it, we need to make sure that the appropriate Javascript libraries are loaded whenever we load a page. There are many ways of achieving this, but one way is to generate a general html layout that will be applied application wide. Create a file named app/views/layouts/application.rhtml with the contents:

<html>
<head>
<%= javascript_include_tag :defaults %>
</head>
<body>
<%= yield %>
</body>
</html>

The java_script_include_tag helper ensures that the appropriate javascript libraries are loaded. Whenever any view from the application is displayed, the output from the view template will be inserted where the yield statement is.

The final steps required to get this to work are to first delete the edit_page method from the page controller. Then modify the controller by inserting two lines so that the top reads:

class PageController < ApplicationController

in_place_edit_for :page, :title
in_place_edit_for :page, :body

[...]

Modify app/views/display_page.rhtml so that it reads:

<h1><%= in_place_editor_field :page, :title %></h1>
<%= in_place_editor_field :page, :body %>

Once again, we’re using Rails helpers to make something very simple. Let’s modify it a bit further, adding a div structure, adding a link to make page creation easy, and adding a list of all pages in the database, with links to those pages.

<div id="main">
  <h1><%= in_place_editor_field :page, :title %></h1>
  <%= in_place_editor_field :page, :body, {}, {:rows => 10} %>
</div>

<div id="sidebar">
  <p><%= link_to "Create a page", :action => "create_page" %></p>
  <center><h3>Existing pages</h3></center>
  <% for page in Page.find(:all) %>
    <%= link_to page.title, :action => :display_page, :page_id => page.id %>
    <br>
  <% end %>
</div>

We’ll use a similar div structure for the create_page action:

<div id="main">
  <% form_for :page, :url => {:action => :save_page} do |form| %>
    <p>Title: <%= form.text_field :title, :size => 30 %></p>
    <p>Body: <%= form.text_area :body, :rows => 15 %></p>
    <p><%= submit_tag "Create page" %></p>
  <% end %>
</div>

Let’s modify the layout in app/views/layouts/application.rhtml in order to load a stylesheet, and add a shared header:

<html>
<head>
<%= stylesheet_link_tag 'application' %>
<%= javascript_include_tag :defaults %>
</head>
<body>

<div id="header">
  <center><h1>RailsNite Wiki</h1></center>
</div>

<%= yield %>
</body>
</html>

Finally, let’s drop a stylesheet in. Here’s a very simple one, that goes in public/stylesheets/application.css:

body 	{
	font-family: trebuchet ms, sans-serif;
	font-size: 16px;
	}

#header {
	position: absolute;
	top: 0em;
	left: 0em;
	right: 0em;
	height: 5em;
        background: #ddf;
	}

#main {
	position: absolute;
	top: 5em;
	left: 0em;
	right: 20em;
	padding: 1em;
	}

#sidebar {
	position: absolute;
	top: 5em;
	right: 0em;
	width: 20em;
	background: #efe;
	}

There you have it! A very simple Wiki in 42 lines of Rails code, with a few dozen extra lines of templates and stylesheets. Of course, it’s not much of a wiki. It really needs exception handling, version histories for pages, user authentication, and a general clean up. But it is nice to see so much added so quickly, and all those other features can be added with just a little extra effort.

From → Uncategorized

11 Comments
  1. That’s not a Wiki at all, that’s a simple CMS. A Wiki would require at a minimum CrammingWordsTogether automatically creates links to other Wiki pages whether they exist or not and if not clicking the link offers to creates the new page.

    The template code is also mixed with ruby, so all the template html should be included in the line count.

  2. Michael Nielsen permalink

    I’m not eager to get caught up in arguments over definitions. I think it’s pretty clear that I’m using the term wiki very loosely here. My micro-app is in no sense a complete wiki or CMS.

    In regard to the second, I wrote:

    “A very simple Wiki in 42 lines of Rails code, with a few dozen extra lines of templates and stylesheets”

    I don’t see that there’s any inaccuracy here.

  3. John Schulman permalink

    In place editing has been moved to a plugin in Rails 2. The above code will probably work if do script/plugin install in_place_editing

  4. as john has said you need to install the plugin for inplace editor using ruby script/plugin install in_place_editing

    and to make it work properly You need to apply this patch for inplace editor

    http://dev.rubyonrails.org/attachment/ticket/10055/in_place_editing_should_work_with_csrf_and_rjs.patch

    good article Michael

  5. You had errors because the standard method names are: index show new create edit update destroy

    … i.e. show, not display.

  6. Sai – I think you’re probably seeing those errors because you’re using a newer version of Rails. This was written under Rails 1.2.

  7. “These methods don’t do anything yet. In your browser, load the URL http://localhost:3000/page/create_page. You’ll get an error message that says “Unknown action: No action responded to create”. In fact, what has happened is that Rails parses the URL, and determines from the first part (“page”) that it should load page_controller.rb, and from the second part that it should call the display_page action.”

    Don’t you mean “create_page” at the end of this paragraph?

  8. Thankyou Dave! Yes, indeed, I do. Fixed.

  9. Onalenna permalink

    Hi Michael, is it possible for you to adapt this tutorial for the new version of rails and ruby?

    [MN: Sorry, don’t have time! But if you’d like to, feel free to make the modifications!]

  10. Richard permalink

    Hi Michael, I am getting a route error when its attempts to save a page, after the “Create Page” button is clicked.

    This is my error:

    Routing Error

    No route matches [POST] “/assets”
    Try running rake routes for more information on available routes.

    I hope you could offer guidance! Unfortunately I need to finish making a wiki within a couple hours!

    Thanks

  11. I see you say this was written under Rails 1.2. I am assuming this will not work under Rails 3.2.3. I tried it on Linux (openSUSE 12.1) and it works perfectly (after installing Git, curl and RVM) when I do what you did but it’s a different story on Windows. Do I have to alter the code. For instance say rails new wiki instead of the code shown.

Comments are closed.