{"id":289,"date":"2007-11-12T17:38:53","date_gmt":"2007-11-12T21:38:53","guid":{"rendered":"http:\/\/michaelnielsen.org\/blog\/?p=289"},"modified":"2010-07-19T14:17:52","modified_gmt":"2010-07-19T18:17:52","slug":"a-simple-wiki-with-ruby-on-rails","status":"publish","type":"post","link":"https:\/\/michaelnielsen.org\/blog\/a-simple-wiki-with-ruby-on-rails\/","title":{"rendered":"A simple Wiki with Ruby on Rails"},"content":{"rendered":"<p>I prepared the following simple demo for RailsNite Waterloo.  It&#8217;s a very simple Wiki application, illustrating some basic ideas of Ruby on Rails development.<\/p>\n<p>To get the demo running, we need a Ruby on Rails installation.  I won&#8217;t explain here how to get such an installation going.  See the <a href=\"http:\/\/www.rubyonrails.org\/\">Rails site<\/a> to get things up and running.  I&#8217;ll assume that you&#8217;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&#8217;t tested it.<\/p>\n<p>We start from the command line, and move to the &#8220;rails_apps&#8221; directory, which typically sits somewhere within the Ruby on Rails installation.  From the command line we run: <\/p>\n<pre><code>rails wiki\r\ncd wiki<\/code><\/pre>\n<p>This creates a new directory called wiki, and installs some basic files into that directory.  What are those files?  To understand the<br \/>\nanswer to that question, what you need to understand is that Ruby on Rails really has two parts.<\/p>\n<p>The first part is the <a href=\"http:\/\/www.ruby-lang.org\/en\/\">Ruby programming language<\/a>, 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.<\/p>\n<p>The second part of the framework is Ruby on Rails proper, or &#8220;Rails&#8221; as we&#8217;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 <code>rails wiki<\/code> 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. <\/p>\n<p>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.<\/p>\n<p>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:<\/p>\n<pre><code>mysqladmin -u root create wiki_development<\/code><\/pre>\n<p>If you&#8217;re not all that familiar with MySQL you may be wondering whether you&#8217;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&#8217;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&#8217;ll assume that you have a basic understanding of concepts such as tables and rows, but won&#8217;t use any complex features of relational databases.<\/p>\n<p>With all our configuration set up, lets start a local webserver.  From the command line type:<\/p>\n<pre><code>ruby script\/server<\/code><\/pre>\n<p>Now load up http:\/\/localhost:3000\/ in your browser.  You should see a basic welcome page.  We&#8217;ll be changing this shortly.<\/p>\n<p>Let&#8217;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&#8217;s internal object store?<\/p>\n<p>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&#8217;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.<\/p>\n<p>Using MySQL in this way does create a problem, however.  Ruby is an object-oriented programming language, and it&#8217;s designed to work with objects.  If all our data is being stored in a database, how can we use Ruby&#8217;s object-orientation?  Rails offers a beautiful solution to this problem, known as <em>Object Relational Mapping (ORM)<\/em>.  One of the core pieces of Rails is a class known as <em>ActiveRecord<\/em> which provides a way of mapping between Ruby objects and rows in the database.  The beauty of ActiveRecord is that from the programmer&#8217;s point of view it pretty much looks like the rows in the database <em>are<\/em> Ruby objects!<\/p>\n<p>This is all a bit abstract.  Let&#8217;s work through an example of ActiveRecord in action.  The basic object type in our wiki is going to be a <em>page<\/em>.  Let&#8217;s ask Rails to generate a model named <code>Page<\/code>:<\/p>\n<pre><code>ruby script\/generate model Page<\/code><\/pre>\n<p>You should see the following:<\/p>\n<pre><code>      exists  app\/models\/\r\n      exists  test\/unit\/\r\n      exists  test\/fixtures\/\r\n      create  app\/models\/page.rb\r\n      create  test\/unit\/page_test.rb\r\n      create  test\/fixtures\/pages.yml\r\n      create  db\/migrate\r\n      create  db\/migrate\/001_create_pages.rb<\/code><\/pre>\n<p>For our purposes, the important files are app\/models\/page.rb, which contains the class definition for the <code>Page<\/code> model, and 001_create_pages.rb, which is the file that will set up the corresponding table in the database<\/p>\n<p>(You&#8217;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 &#8211; it automatically pluralizes model names to get the corresponding database table name, and the cases can vary a lot.  It&#8217;s something to watch out for.)<\/p>\n<p>The next step is to decide what data should be associated with the <code>Page<\/code> model.  We&#8217;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:<\/p>\n<pre><code>class CreatePages &lt; ActiveRecord::Migration\r\n  def self.up\r\n    create_table :pages do |t|\r\n      t.column \"title\", :string\r\n      t.column \"body\", :string\r\n    end\r\n  end\r\n\r\n  def self.down\r\n    drop_table :pages\r\n  end\r\nend<\/code><\/pre>\n<p>This is known as a <em>migration<\/em>.  It&#8217;s a simple Ruby file that controls changes made to the database.  The migration can also be reversed &#8211; that&#8217;s what the &#8220;def self.down&#8221; 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.<\/p>\n<p>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.<\/p>\n<p>The actual creation of the database is now done by invoking the <em>rake<\/em> command, which is the Ruby make utility:<\/p>\n<pre><code>rake db:migrate<\/code><\/pre>\n<p>Incidentally, when run in devleopment mode (the default, which we&#8217;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&#8217;t apply to changes to the structure of the database, and it&#8217;s usually a good idea to restart the webserver after using rake to run a migration.  If you&#8217;re following along, do so now by hitting control C to interrupt the webserver, and then running <code>ruby script\/server<\/code> again.<\/p>\n<p>Now that we have a <code>Page<\/code> 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.<\/p>\n<p>To make this happen, we ask Rails to generate what is known as a <em>controller<\/em> for the <code>Page<\/code> model:<\/p>\n<pre><code>ruby script\\generate controller Page<\/code><\/pre>\n<p>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:<\/p>\n<pre><code>class PageController &lt; ApplicationController\r\nend<\/code><\/pre>\n<p>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:<\/p>\n<pre><code>class PageController &lt; ApplicationController\r\n\r\ndef create_page\r\nend\r\n\r\ndef display_page\r\nend\r\n\r\ndef edit_page\r\nend\r\n\r\nend<\/code><\/pre>\n<p>(Incidentally, the names here are a bit cumbersome.  I started with the simpler method names <code>create<\/code>, <code>display<\/code> and <code>edit<\/code>, and then wasted an hour or so, confused by various weird behaviour caused by the fact that the word <code>display<\/code> is used internally by Ruby on Rails.  A definite gotcha!)<\/p>\n<p>These methods don&#8217;t do anything yet.  In your browser, load the URL http:\/\/localhost:3000\/page\/create_page.  You&#8217;ll get an error message that says &#8220;Unknown action: No action responded to create&#8221;.  In fact, what has happened is that Rails parses the URL, and determines from the first part (&#8220;page&#8221;) that it should load page_controller.rb, and from the second part that it should call the <code>create_page<\/code> action.<\/p>\n<p>What is missing is one final file.  Create the file app\/views\/page\/create_page.rhtml, and add the following:<\/p>\n<pre><code>Hello world<\/code><\/pre>\n<p>Now reload the URL, and you should see &#8220;Hello world&#8221; in your browser. Let&#8217;s improve this so that it displays a form allowing us to create an instance of the <code>Page<\/code> model. Let&#8217;s re-edit the file so that it looks like this instead:<\/p>\n<pre><code>&lt;% form_for :page, :url =&gt; {:action =&gt; :save_page} do |form| %&gt;\r\n  &lt;p&gt;Title: &lt;%= form.text_field :title, :size =&gt; 30 %&gt;&lt;\/p&gt;\r\n  &lt;p&gt;Body: &lt;%= form.text_area :body, :rows =&gt; 15 %&gt;&lt;\/p&gt;\r\n  &lt;p&gt;&lt;%= submit_tag \"Create page\" %&gt;&lt;\/p&gt;\r\n&lt;% end %&gt;<\/code><\/pre>\n<p>There&#8217;s a lot going on in this code snippet.  It&#8217;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:<\/p>\n<pre><code>&lt;% INSERT RUBY CODE HERE %&gt;<\/code><\/pre>\n<p>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:<\/p>\n<pre><code>&lt;%= INSERT RUBY EXPRESSION TO BE EVALUATED HERE %&gt;<\/code><\/pre>\n<p>The first line of the snippet tells us that this is a form for objects of class <code>Page<\/code>, and that when the form is submitted, it should call the save_page action in the page controller, which we&#8217;ll add shortly.  The result of the form is pretty straightforward &#8211; it does more or less what you&#8217;d expect it to do.  Let&#8217;s add a <code>save<\/code> action (i.e., a method) to the page controller:<\/p>\n<pre><code>def save_page\r\n  new_page = Page.create(params[:page])\r\n  redirect_to :action =&gt; \"display_page\", :page_id =&gt; new_page.id\r\nend<\/code><\/pre>\n<p>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 <code>params[:page]<\/code>.  We then create a new <code>Page<\/code> model object using <code>Page.create(params[:page])<\/code>, which we call <code>new_page<\/code>.  Finally, we redirect to the action <code>display_page<\/code>, passing it as a parameter a unique id associated to the new page we&#8217;ve just created.<\/p>\n<p>Let&#8217;s now create a view for the <code>display_page<\/code> action.  Start by editing the <code>display_page<\/code> action so that it looks like:<\/p>\n<pre><code>def display_page\r\n  @page = Page.find_by_id(params[:page_id])\r\nend<\/code><\/pre>\n<p>What is happening is that the <code>:page_id<\/code> we were passed before is being passed in as a hash, <code>params[:page_id]<\/code>, and we are now asking Rails to find the corresponding model object, and assign it to the variable <code>@page<\/code>.  We now create a view template that will display the corresponding data, in app\/views\/page\/display_page.rhtml:<\/p>\n<pre><code>&lt;h1&gt;&lt;%= @page.title %&gt;&lt;\/h1&gt;\r\n&lt;%= @page.body %&gt;<\/code><\/pre>\n<p>Okay, time to test things out.  Let&#8217;s try loading up the URL http:\/\/localhost:3000\/page\/create_page.  Type in a title and some body text, and hit the &#8220;Create page&#8221; button.  You should see a webpage with your title and body text.<\/p>\n<p>Let&#8217;s modify the page slightly, adding a link so we can create more pages.  Append the following to the above code for the <code>display_page<\/code> template:<\/p>\n<pre>\r\n<code>&lt;%= link_to \"Create page\", :action =&gt; \"create_page\" %&gt;<\/code>\r\n<\/pre>\n<p>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&#8217;s worth getting used to using the Rails helpers.<\/p>\n<p>In our skeleton for the page controller we had an <code>edit_page<\/code> action.  This could be done along very similar lines to the <code>create_page<\/code> action we&#8217;ve already described. In fact, there&#8217;s an interesting alternative, which is to use Rails&#8217; built in Ajax (Javascript) libraries to edit the fields inplace.  We&#8217;ll try this instead.<\/p>\n<p>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:<\/p>\n<pre><code>&lt;html&gt;\r\n&lt;head&gt;\r\n&lt;%= javascript_include_tag :defaults %&gt;\r\n&lt;\/head&gt;\r\n&lt;body&gt;\r\n&lt;%= yield %&gt;\r\n&lt;\/body&gt;\r\n&lt;\/html&gt;<\/code><\/pre>\n<p>The <code>java_script_include_tag<\/code> 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 <code>yield<\/code> statement is.<\/p>\n<p>The final steps required to get this to work are to first delete the <code>edit_page<\/code> method from the page controller.  Then modify the controller by inserting two lines so that the top reads:<\/p>\n<pre><code>class PageController &lt; ApplicationController\r\n\r\nin_place_edit_for :page, :title\r\nin_place_edit_for :page, :body\r\n\r\n[...]<\/code><\/pre>\n<p>Modify app\/views\/display_page.rhtml so that it reads:<\/p>\n<pre><code>&lt;h1&gt;&lt;%= in_place_editor_field :page, :title %&gt;&lt;\/h1&gt;\r\n&lt;%= in_place_editor_field :page, :body %&gt;<\/code><\/pre>\n<p>Once again, we&#8217;re using Rails helpers to make something very simple. Let&#8217;s modify it a bit further, adding a <code>div<\/code> structure, adding a link to make page creation easy, and adding a list of all pages in the database, with links to those pages.<\/p>\n<pre><code>&lt;div id=\"main\"&gt;\r\n  &lt;h1&gt;&lt;%= in_place_editor_field :page, :title %&gt;&lt;\/h1&gt;\r\n  &lt;%= in_place_editor_field :page, :body, {}, {:rows =&gt; 10} %&gt;\r\n&lt;\/div&gt;\r\n\r\n&lt;div id=\"sidebar\"&gt;\r\n  &lt;p&gt;&lt;%= link_to \"Create a page\", :action =&gt; \"create_page\" %&gt;&lt;\/p&gt;\r\n  &lt;center&gt;&lt;h3&gt;Existing pages&lt;\/h3&gt;&lt;\/center&gt;\r\n  &lt;% for page in Page.find(:all) %&gt;\r\n    &lt;%= link_to page.title, :action =&gt; :display_page, :page_id =&gt; page.id %&gt;\r\n    &lt;br&gt;\r\n  &lt;% end %&gt;\r\n&lt;\/div&gt;<\/code><\/pre>\n<p>We&#8217;ll use a similar div structure for the <code>create_page<\/code> action:<\/p>\n<pre><code>&lt;div id=\"main\"&gt;\r\n  &lt;% form_for :page, :url =&gt; {:action =&gt; :save_page} do |form| %&gt;\r\n    &lt;p&gt;Title: &lt;%= form.text_field :title, :size =&gt; 30 %&gt;&lt;\/p&gt;\r\n    &lt;p&gt;Body: &lt;%= form.text_area :body, :rows =&gt; 15 %&gt;&lt;\/p&gt;\r\n    &lt;p&gt;&lt;%= submit_tag \"Create page\" %&gt;&lt;\/p&gt;\r\n  &lt;% end %&gt;\r\n&lt;\/div&gt;<\/code><\/pre>\n<p>Let&#8217;s modify the layout in app\/views\/layouts\/application.rhtml in order to load a stylesheet, and add a shared header:<\/p>\n<pre><code>&lt;html&gt;\r\n&lt;head&gt;\r\n&lt;%= stylesheet_link_tag 'application' %&gt;\r\n&lt;%= javascript_include_tag :defaults %&gt;\r\n&lt;\/head&gt;\r\n&lt;body&gt;\r\n\r\n&lt;div id=\"header\"&gt;\r\n  &lt;center&gt;&lt;h1&gt;RailsNite Wiki&lt;\/h1&gt;&lt;\/center&gt;\r\n&lt;\/div&gt;\r\n\r\n&lt;%= yield %&gt;\r\n&lt;\/body&gt;\r\n&lt;\/html&gt;<\/code><\/pre>\n<p>Finally, let&#8217;s drop a stylesheet in.  Here&#8217;s a very simple one, that goes in public\/stylesheets\/application.css:<\/p>\n<pre><code>body \t{\r\n\tfont-family: trebuchet ms, sans-serif;\r\n\tfont-size: 16px;\r\n\t}\r\n\r\n#header {\r\n\tposition: absolute;\r\n\ttop: 0em;\r\n\tleft: 0em;\r\n\tright: 0em;\r\n\theight: 5em;\r\n        background: #ddf;\r\n\t}\r\n\r\n#main {\r\n\tposition: absolute;\r\n\ttop: 5em;\r\n\tleft: 0em;\r\n\tright: 20em;\r\n\tpadding: 1em;\r\n\t}\r\n\r\n#sidebar {\r\n\tposition: absolute;\r\n\ttop: 5em;\r\n\tright: 0em;\r\n\twidth: 20em;\r\n\tbackground: #efe;\r\n\t}<\/code><\/pre>\n<p>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&#8217;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.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I prepared the following simple demo for RailsNite Waterloo. It&#8217;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&#8217;t explain here how to get such an installation going. See the Rails site to get things up&hellip; <a class=\"more-link\" href=\"https:\/\/michaelnielsen.org\/blog\/a-simple-wiki-with-ruby-on-rails\/\">Continue reading <span class=\"screen-reader-text\">A simple Wiki with Ruby on Rails<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-289","post","type-post","status-publish","format-standard","hentry","category-uncategorized","entry"],"_links":{"self":[{"href":"https:\/\/michaelnielsen.org\/blog\/wp-json\/wp\/v2\/posts\/289","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/michaelnielsen.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/michaelnielsen.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/michaelnielsen.org\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/michaelnielsen.org\/blog\/wp-json\/wp\/v2\/comments?post=289"}],"version-history":[{"count":1,"href":"https:\/\/michaelnielsen.org\/blog\/wp-json\/wp\/v2\/posts\/289\/revisions"}],"predecessor-version":[{"id":777,"href":"https:\/\/michaelnielsen.org\/blog\/wp-json\/wp\/v2\/posts\/289\/revisions\/777"}],"wp:attachment":[{"href":"https:\/\/michaelnielsen.org\/blog\/wp-json\/wp\/v2\/media?parent=289"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/michaelnielsen.org\/blog\/wp-json\/wp\/v2\/categories?post=289"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/michaelnielsen.org\/blog\/wp-json\/wp\/v2\/tags?post=289"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}