A continuation from:
Ruby on Rails for Architects #4 - Analysis and DesignMODELSTo have a working WIKI, we not only need Pages, but also Revisions. In the previous article, we created the Pages model, view and controller. For this exercise, we will create the Revisions model, and expand the design. Run the following command (from your project's root directory) to create the Revisions model.
ruby script/generate model revision
Within the
app/models directory, you will find both a
page.rb and
revision.rb. Viewing them will reveal empty class structures with no methods. But it is important to note that in the previous article we still had a fully functionally CRUD application. This is due to the fact that these classess extend the
ActiveRecord:Base object, will will dynamically at runtime grant a series of methods to this object allowing it to query the database table matching the pluralized form of the model object. Rails is smart enough to know that the plural of revision is revisions, and the plural of page is pages. It also assumes you have followed the comment convention to use underscores for column names. So for example, first_name in the database would be automatically translated into firstName. Helper methods are also provided to assist in finding and interacting with the data in the database. Even though this is an empty class structure, it is possible to call
Page.find_by_name() or
Revision.find_by_id(). We will see the power of this in a little bit.
While the models work fine individually, we need to inform Rails of the referential integrity and relationships which exist between these two models. A single page may have many revisions, and a single revision has a single parent page.
In
revision.rb add inside of the class:
belongs_to :page
In page.rb add inside of the class:
has_many :revisions
CONTROLLERNext we will configure the controller. Edit the
app/controllers/wiki_controller.rb file. We will add a method for each method we wish to expose to the user via the URL. As we discovered with our scaffolding of the
pages controller in a previous article, the controller in question is referenced by the URL, followed by the method inside of the controller. Therefore, if we have a method in our wiki controller known as
view. Then that method will be executed when we point our browser to
http://localhost:3000/wiki/view.
The URL string, and how it is interpreted by the controller is edited through the
config/routes.db file. Let's open that file now and make some specific changes for our new wiki controller. Add the following two lines into routes.rb:
map.connect '', :controller => "wiki"
map.connect 'wiki/:action/:name', :controller => "wiki"
The first pattern matches empty quotes for the controller 'wiki'. This will map a URL of http://localhost:3000 and direct it to the
index method of the
wiki controller. This is useful for removing that Ruby on Rails starting page we see when we start the web server. For the Rails page to be fully eradicated, you must also delete the html file
public/index.htmlThe second pattern informs the controller that the URL should be expected to be parsed in a manner in which the controller's name (wiki) is followed by the action, or method on the controller to execute, followed by the name of the WIKI page we wish to perform an action on (actions being edit, view, etc). Therefore, http://localhost:3000/wiki/view/Home will load the
app/controllers/wiki_controller.rb controller, and call the
view method upon in, passing a parameter of name="Home".
Let's add some functionality to our controller. Paste the following methods into the
app/controllers/wiki_controller.rb:
def index
redirect_to :action=> 'view', :name => 'Home'
end
def view
@name = params[:name]
@page = Page.find_by_name(@name)
end
def edit
@name = params[:name]
@page = Page.find_by_name(@name)
end
def save
@content = params[:content]
@name = params[:name]
@page = Page.find_by_name(@name)
if !@page
@page = Page.create(:name => @name)
end
Revision.create(:page => @page, :content => @content)
redirect_to :action => :view, :name => @name
end
Let's quickly walk through these methods one by one.
index - This method is called whenever the controller is accessed directly. (e.g. http://localhost:3000/wiki). In these instances, we simply redirect to viewing of the home page of the wiki.
view - Pull the @name variable off of the URL parameters that was passed, and use the Page model to find an object in the database matching on the name field. Remember above where I explained the find_by methods are dynamically accessed thanks to ActiveRecord.
edit - This method will be invoked in order to display the edit form to the user. This is necessary since we will wish to show them the content which currently exists in the database, and give the user to modify it (rather than present them with a blank form). Therefore, this method mimics view. The difference between view and edit is the .html which will be delivered to the end user. By default, the view which will be delivered will match the name of the method in the controller. Therefore after the view method is called, app/views/view.rhtml will be displayed, and edit will cause app/views/edit.rthml to render. More about this in a minute.
save - The user will not see this URL, it is called from the form displayed within app/views/edit.rthml. The purpose of this method is to create a new record in the Page database table, if it does not yet exist. Then a new Revision record is created in the database. While not a requirement now, we wish to have the ability to
VIEWSTo save some coding, Rails comes bundled with the ability to support layouts. We can create a layout for our controller by creating a file called
app/views/layouts/wiki.rthml. This layout will be rendered alongside each view which is executed by the controller. Create the
wiki.rthml file and add some simple content such as this:
<html>
<head>
<title>
Wiki > <%= request.path_parameters['action'] %>
</title>
</head>
<body>
<%= yield :layout %>
</body>
</html>
This simple layout will allow us to remove the skeleton of the page (e.g. html,head,title,body) from our views, making them appear less cluttered.
Before we can finish, we have 2 more files to create.
app/views/view.rthml and
app/views/edit.rthml.
app/views/view.rthml:
<%= link_to 'view', :action => 'view', :id => @name %>
<%= link_to 'edit', :action => 'edit', :id => @name %>
<%= @page.revisions.last.content if @page %>
app/views/edit.rthml:<%= link_to 'view', :action => 'view', :id => @name %>
<%= link_to 'edit', :action => 'edit', :id => @name %>
<h2 id="pageName"><%= @name %></h2>
<%= form_tag :action => "save" %>
<input type="hidden" name="name" value="<%= @name %>">
<textarea name="content" rows="20">
<%= @page.revisions.last.content if @page %>
</textarea>
<input name="Save" type="submit" value="Save" id="Save">
</form>
Tags beginning with <%= contain executable ruby code, rendering the output of their result to the screen. Since these views were executed after the controller's methods, the variables used in the controller are available here. Therefore,
<%= @name => outputs the
@name variable created in the controller, same goes for the
@page variable.
Rails offers a series of helper methods to display dynamic HTML tags.
link_to and form_tag both ouput HTML tags (link_to outputs 'A' anchor/link tags, and form_tag outputs 'form' tags).
Thanks to ActiveRecord, we can query all of the Revisions for a page using the @page.revisions syntax. Here, we take that power a step further, asking ActiveRecord to only return us the most recent of revisions (@page.revisions.last), and then displaying the value of the contents field from the database (@page.revisions.last.content).
WORKING WIKINow we have a functioning wiki. Once again, start Rail's embeded web server:
ruby script/server
and point your browser to http://localhost:3000. You can now edit and view pages by manipulating the URL. In our next, and final article.... we will learn how to transform the text in the database to WIKI markup language.
Continued in:
Ruby on Rails for Architects #6 - Spit and Polish