I just did the initial commit of the ruby on rails wiki engine I built – here and here for Wiki.Cullect.com – into the collaborative source-code site github.
These days, I’m pretty enamored of Haml, it’s more like HTML (unlike many other formatting engines) and it’s fast to write (unlike many other formatting engines).
1. Install Haml
Haml installs as a gem…1 gem install --no-ri haml
and a plugin… haml --rails path/to/your/wiki
2. Create Your Haml View
Open up app/views/show.html.erb
and convert it to Haml. Replacing it to:
.content
%h1= @page.title.gsub('_', ' ')
= link_to('Edit', page_edit_path(:page_title => @page.title))
= link_to('History', page_history_path(:page_title => @page.title))
= "Last Updated #{time_ago_in_words(@page.revisions.last.created_on)} ago by #{Person.find(@page.revisions.last.person_id).name}"
%p= auto_link(@page.revisions.last.parsed_contents)
Now, your wiki should look exactly like it did before we plugged in Haml.
3. Add Haml Support for Revisions
Open up app/models/revision.rb and update def parsed_contents with:
def parsed_contents
contents = contents.gsub(/(w*_w*)/) {|match| "<a href='#{match.downcase}'>#{match.gsub('_', ' ')}</a>"}
h = Haml::Engine.new(contents)
h.to_html
end
4. Try It Out
Load up a page in your wiki, click ‘edit’, and add in some simple Haml, something like:
%strong this should be bold
5. Add in Some More (non-Haml) Formatting
Once you’re comfortable with Haml, maybe add in some other simple formatting rules into revision.rb
#Auto-renders any image URL
contents = self.contents.gsub(/s(.*.(jpg|gif|png))s/, '<img src="1"/>')
# Bolds text inside asterisks
contents = contents.gsub(/*(.*)*/, '<strong>1</strong>')
# Wraps <code> around text inside '@'
contents = contents.gsub(/@(.*)@/, '<code>1</code>')
# Blockquotes text inside double-quotes
contents = contents.gsub(/s"(.*)"s/, '<blockquote>"1"</blockquote>')
# Italicizes text inside underscores
contents = contents.gsub(/s_(.*)_s/, '<em>1</em>')
6. Make it Better
After playing around with Haml and other formatting, you’ll quickly see some quirks – some room for improvement. Go for it, and let me know when you get something interesting.
Here’s how to build a very simple Ruby on Rails-based wiki engine 1.
1. Create the Rails App
rails wiki
and cd wiki to get inside the app.
Remember: create your database (i.e. wiki_development) and update config/database.ymlappropriately
2. Generate the Scaffolding
There are 3 nouns in this wiki system: People, Pages, Revisions. Let’s create the models for each of them.
run script/generate scaffold Person
Now open up wiki/db/migrate/*_create_people.rb and add: t.column "name", :string inside the create_table block to store the Person’s name.
Add Person.create :name => "First Person"
after the create_table block to give us an initial Person.
run script/generate scaffold Page
Now open up wiki/db/migrate/*_create_pages.rb and add: t.column "title", :string inside the create_table to store the Page’s title and add Page.create :title => "Home_Page" after the create_table block to give us an initial Page.
run script/generate scaffold Revision
Now open up wiki/db/migrate/*_create_revisions.rb and add:
# Reference to the Person that created the Revision
t.column "person_id", :integer
# The contents of the Revision
t.column "contents", :text
# Reference to the Page this Revision belongs to
t.column "page_id", :integer
3. Run the database migrations
rake db:migrate
4. Describe the relationships between the Models
People should be able to create many Revisions.
Open /app/models/person.rb and add – after the first line: has_many :revisions
Pages should have many Revisions
Open /app/models/page.rb and add – after the first line: has_many :revisions
Revisions should reflect they belong to both Pages and People
Open /app/models/revision.rb and add – after the first line:
belongs_to :page
belongs_to :person
5. Draw up the Routes
Open up config/routes.rb and update them to handle wiki-fied words. Copy the following after the map.resources block
map.root :controller => 'pages', :action => 'show', :id => 1
Now run script/server and load up a browser.
If things are working correctly, you should see simply ‘Edit | Back’ as links
6. Create the Views
Open /app/views/revisions/new.html.erb and add
< %= f.text_area :contents %>
< % fields_for :person do |p| %>
< %= p.text_field :name %>
< % end %>
right after < %= f.error_messages %> for the Revision’s content, the corresponding Page and the author’s name
Loading up http://0.0.0.0:3000/revisions/new should give you the form with the text area, a text field, and a submit button you just created.
Try entering something into the form and click ‘Create’. You should see ‘Revision was successfully created.’ Now, if you pop into your database, you should see the new row in the Revisions table with the contents you entered, but NULL values for person_id and page_id.
Let’s remedy that.
Open /app/controllers/revisions_controller.rb and add this line right after the @revision declaration in both def new and def create
@revision.update_attribute('person_id', Person.find_or_create_by_name(params[:person][:name]).id)
Reload http://0.0.0.0:3000/revisions/new and re-fill out the form. After hitting ‘Create’ you should have another Revision record in your database with some digit in the person_id field
Now, let’s tie the Revision form we created to a Page.
Open up /app/views/revisions/new.html.erb and change the form_for to: < % form_for @revision, :url => new_revision_path, :html => {:method => :post} do |f| %>
Next, right after < %= f.text_area :contents %> add: < %= f.hidden_field :page_id, :value => @page.id %>
Now, rename /app/views/revisions/new.html.erb to /app/views/revisions/_new.html.erb. This turns the form into a partial, which is fine, because Revisions only exist within Pages, never on their own.
Next, replace everything in /app/views/pages/edit.html.erb with
<h1>Update "< %= @page.title.gsub('_', ' ') %>"</h1>
< %= render :partial => 'revisions/new' %>
If you load up 0.0.0.0:3000/pages/1/edit right now, you’ll get a ‘Called id for nil’ error. That’s because the Revision partial is referencing @revision, which doesn’t exist in /pages. Yet.
Open up /app/controllers/pages_controller.rb and scroll to ‘def edit’.
Add @revision = @page.revisions.last || Revision.new right after the @page declaration
Refreshing 0.0.0.0:3000/pages/1/edit should now correctly render the form.
Fill it out, hit ‘Create’ and check the database. You should have a new record with a number in the page_id column.
But we can’t see it because 0.0.0.0:3000/pages/1 doesn’t show anything.
Open up /app/views/pages/show.html.erb and add:
<h2>< %= @page.title.gsub('_', ' ') %></h2>
<p>< %= @page.revisions.last.contents %></p>
<p>last edited by < %= @page.revisions.last.person.name %> on < %= time_ago_in_words(@page.revisions.last.created_at) %> ago</p>
to the first line and save.
Refreshing 0.0.0.0:3000/pages/1 should show the text you entered.
7. Update the Model, Controller, Views, and Routes to Acknowledge Wiki-fied Words (separated with an underscore ‘_’)
Open up /app/models/revision.rb and add:
def parsed_contents
contents = self.contents.gsub(/(w*_w*)/) {|match| "<a href='#{match.downcase}'>#{match.gsub('_', ' ')}</a>"}
end
Open up /app/controllers/pages_controllers.rb, find def show and change: @page = Page.find(params[:id])
to
@page = Page.find_or_create_by_title(params[:title])
return redirect_to page_edit_url(:title => @page.title) if @page.revisions.empty?
Open up /app/controllers/revisions_controllers.rb, find def create and change each : format.html... line to format.html { redirect_to page_base_url(:page_title => @revision.page.title) }
Next, scroll down to def edit and change: @page = Page.find(params[:id])
to @page = Page.find_by_title(params[:title])
Now, in /app/views/pages/show.html.erb change the Edit link to build the new route: < %= link_to 'Edit', page_edit_url(:title => @page.title) %> |
1. There are leaner Ruby frameworks to build wikis in (Camping or Merb come to mind.). I picked Rails for 2 reasons; My servers and deployment process was already set up for Rails, I wanted to loosely integrate this wiki into another existing Rails-based application (Cullect.com).
I’ve worked with wikis for project documentation and team communication. Their power is in their organic growth and how they put the responsibility of accuracy on the reader. When I talk with others about wikis, one question always arises:
What if somebody writes something that’s not good?
At the heart of a wiki lies 2 equal responsibilities;
The Author is responsible for writing accurate, useful, and interesting things.
The Reader is responsible for changing things to make them better.
For the most part, these 2 responsiblities quickly make a very comprehensive knowledge base. If for whatever reason they fail, there’s always rolling back to a previous version of the page.