Building a Forum with Ruby on Rails

Building a Forum From Scratch with Ruby on Rails

Today, we will be building a simple forum using Ruby on Rails, and we will be working up from the basics covering things like authentication and more advanced database techniques.

Final Product

Ruby on Rails

If you’re unfamiliar with Ruby on Rails, it is a web application framework built using the Ruby programming language. There have been some good tutorials published here on net.tuts+ on getting starting with Rails, and for a quick review you can see my previous article.

Step 1 – Create the Rails Application

To create our application, we use the rails command, followed by the name of our project, and that happens to be forum in this case. The terminal command below is what I used. Also, make sure you have changed into the proper directory in your terminal.

rails forum

After running the command you will see a long line of text with all the files that have just been created. Now you will want to change into the directory we just created.

Step 2 – Installing Gems

You are probably wondering what a Gem is. A Gem is a self-contained package for distributing Ruby programs and libraries. They come in handy very often, they make your life easier. We will be using a few gems for this project, listed below. Also, make sure that you have the latest version of RubyGems and your Gems by running:

gem update --system

(with sudo if needed)

gem update

(with sudo if needed)

  • nifty-generators: Ryan Bates’s amazing nifty-generators will be used for the nifty-authentication and nifty-scaffold generators
  • database: Make sure you have any gem you need to interface with the database you will use, I am going to use SQLite

Step 3 – Setting up the Authentication

Now that we have all the gems we need installed, run the following in your terminal: (make sure you are in the directory rails created)

script/generate nifty_authentication

You should see text fly by like above. Now go ahead and open up the folder in your favorite IDE. We are first going to edit the config/database.yml file. The file should be set up to use whatever database system you want, and in this case I will be using SQLite.

Making sure everything is setup properly, and then run the following command to migrate changes to our database:

rake db:migrate

Step 4 – The Application Structure

No, this part is not going to actually deal with the application layout in terms of overall design, we will work on that shortly. This section will actually deal with how the application will be built. One of the major upsides with Ruby on Rails is the plethora of generators available. The only problem that some people miss is that you need to plan you application to make these generators really go the extra-mile.

Below is how the models in the database will be organized and related is a rough manner:

  • Forum: A forum is the top-most level. Basically a group of topics that relate to one another.
    • Topic: a topic, also called a thread concerns a certain topic.
      • Post: a post belongs to a topic, and is a message by a certain user.

So we are going to use nifty-scaffold to generate three models, one for each of the parts listed above. So getting started, we are going to use the scaffold to create our forum. To do this, run the following command:

script/generate nifty_scaffold forum name:string description:text

Next we will scaffold for the topic:

script/generate nifty_scaffold topic name:string last_poster_id:integer last_post_at:datetime

Now we will scaffold the post:

script/generate nifty_scaffold post content:text

Then migrate the database:

rake db:migrate

Step 5 – Creating the Layout

Now, we are actually going to work on the the first part of the overall look of the site. Due to the fact we are using nifty_scaffold, we are also going to use nifty_layout to generate some of the basic layout, that we will change. To use nifty_layout, run:

script/generate nifty_layout

Now this was a real quick step just to get this working for now. We will come back to this later.

Step 6 – Setting up the Associations

So we are going to create associations in our models. Associations are ways to link models. Let’s first open up our app/models/forum.rb and you should see:

# app/models/forum.rb
class Forum < ActiveRecord::Base
end

Now we are going to add some code and it should look like the following, and I will explain it in a minute.

# app/models/forum.rb
class Forum < ActiveRecord::Base
  has_many :topics, :dependent => :destroy
end

The code has_many means exactly what it sounds like; it ‘has many’ and then we refer to our model Topic. You may wonder why it works when you add an ‘s’, and that is the ActiveRecord convention. The :dependent => :destroy means that when the forum is deleted, all of it’s topics will be too. Now we are going to open up our app/models/topic.rb and edit it to the following:

# app/models/topic.rb
class Topic < ActiveRecord::Base
  belongs_to :forum
  has_many :posts, :dependent => :destroy
end

Again, this is plain text. It says a Topic belongs to a Forum, and has many posts. We are also using :dependent => :destroy here to delete all of it’s children posts. Now open up app/models/post.rb and edit it to the following:

# app/models/post.rb
class Post < ActiveRecord::Base
  belongs_to :topic
end

Now we are going to create three migrations that will add our foreign keys to our database. If you had wanted to do this when you scaffolded, you could have, though the way we are going about it also teaches you another Rails technique, so I felt that this way would be better. So the create our first migration, run:

script/generate migration add_foreign_to_topics forum_id:integer

This command generates a migration named add_foreign_to_topics, automatically recognizes that we want to change the Topic model, and then we pass what we want to add. In this case we are adding a column named forum_id that is an integer type. To create the migration to add our foreign key of forum_id to our Topic model. Then next command will create the migration

script/generate migration add_foreign_to_posts topic_id:integer

Now we can migrate the database again with:

rake db:migrate

Step 7 – Getting Started on the Home Page

So now we can start on building the functionality of the site. So lets first open up config/routes.rb and add the following line near the commented out lines below:

# config/routes.rb
map.root :controller => "forums"

Lets save the file and delete or rename the file public/index.html. Now we want to open up the view for the index file in app/views/forums/index.html.erb and edit it to the following:

# app/views/forums/index.html.erb
<% title "Forums" %>

<table>
  <tr>
    <th width="70%">Forum</th>
    <th width="30%">Last Post</th>
  </tr>
  <% for forum in @forums %>
    <tr>
      <td><h4><%= link_to h(forum.name), forum_path(forum.id) %></h4>
        <small><%= forum.topics.count %> topics</small><br />
        <%=h forum.description %></td>
      <td class="right"></td>
      <td><%= link_to "Show", forum %></td>
      <td><%= link_to "Edit", edit_forum_path(forum) %></td>
      <td><%= link_to "Destroy", forum, :confirm => 'Are you sure?', :method => :delete %></td>
    </tr>
  <% end %>
</table>

<p><%= link_to "New Forum", new_forum_path %></p>

I changed the table header text into the text that we will be putting in each cell, as well as the width of the cells There are still changes that we are going to make later. Now, open up the application.css file in the public/stylesheets directory. I added the 960.gs CSS Reset and Typography files to the top of the stylesheet. Then I added the following styles to the end of the file:

# public/stylesheets/application.css
table tr th {background:#222; color:#fff; padding:0; border:#111 solid 1px;}
table tr td {border:1px #ccc solid; padding:5px;}
table tr td.right {background: #eee;}
table tr td h4 {margin:0; padding:0; font-weight:normal;}

Now if you want to fire up the server with script/server, you should see the following: (P.S. I added a record into the database just to give an example.)

Now that we have our index action taken care of, lets work on our show action for the forum. This file lies in app/views/forums/show.html.erb. We are going to copy most of the code from our index action into this one, and the final code is below:

# app/views/forums/show.html.erb
<% title @forum.name  %>

<table>
  <tr>
    <th width="60%">Topic Title</th>
    <th width="10%">Replies</th>
    <th width="30%">Last Post</th>
  </tr>
  <% for topic in @forum.topics %>
    <tr>
      <td><%= link_to h(topic.name), topic_path(topic.id) %>
      <td><%= topic.posts - 1 %></td>
      <td class="right"></td>
      <td><%= link_to "Show", topic %></td>
      <td><%= link_to "Edit", edit_topic_path(topic) %></td>
      <td><%= link_to "Destroy", topic, :confirm => 'Are you sure?', :method => :delete %></td>
    </tr>
  <% end %>
</table>

<p><%= link_to "New Topic", "/topics/new?forum=#{@forum.id}" %></p>

I know I have not explained the code in the two views above, so I’ll explain it now. So the first line we set the title for the page. We then create a table and set up the table headers. After, we start a loop for each item in the topics of our forum, and uses the local variable of topic. We then create a link for each topic, then the number of posts, minus 1 to account for the original post. Then we have an extra td that we will use later, and then the admin links, which we will also take care of later.

Step 8 – Working on the Forms

So now that we have the basic layout for our front end. We need to work on the form to create a topic. You should have something that looks like the following:

Well this currently does not work for what we want to do. We currently do not create a post when we create a topic, and we currently give users access to options they should not see. So open up app/views/topics/_form.html.erb. Your current file should look like:

# app/views/topics/_form.html.erb
<% form_for @topic do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <%= f.label :last_poster_id %><br />
    <%= f.text_field :last_poster_id %>
  </p>
  <p>
    <%= f.label :last_post_at %><br />
    <%= f.datetime_select :last_post_at %>
  </p>
  <p><%= f.submit "Submit" %></p>
<% end %>

So we are going to delete the parts with the last_poster_id and last_post_at. We are also going to add in a text area for the post and some hidden fields. You will see that I do not use the Rails helpers, and that is just my personal preference. Your final form should look like:

# app/views/topics/_form.html.erb
<% form_for @topic do |f| %>
  <%= f.error_messages %>
  <% if params[:forum] %><input type="hidden" id="topic_forum_id" name="topic[forum_id]" value="<%= params[:forum] %>" /><% end %>
  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <textarea name="post[content]" cols="80" rows="20"><%= @post.content %></textarea>
  </p>
  <p><%= f.submit "Create" %></p>
<% end %>

And it should look like this in your browser:

We will be saving these in our controller. So, save the view and open up the app/controllers/topic_controller.rb and find the create action. Edit it so it looks like:

# app/controllers/topic_controller.rb
def create
  @topic = Topic.new(params[:topic])
  if @topic.save
    @topic = Topic.new(:name => params[:topic][:name], :last_poster_id => current_user.id, :last_post_at => Time.now, :forum_id => params[:topic][:forum_id])

    if @post.save
      flash[:notice] = "Successfully created topic."
      redirect_to "/forums/#{@topic.forum_id}"
    else
      redirect :action => 'new'
    end
  else
    render :action => 'new'
  end
end

We have added in code to handle creating the post that goes with the topic. Now to make this easier we are going to delete the index action in our controller. We are also going to update the update and destroy actions to redirect properly, so change the redirect_to on these two actions to:

redirect_to "/forums/#{@topic.forum_id}"

Now go ahead and open up app/controllers/posts_controller.rb and delete the index and show actions.

Step 9 – Setting up More Associations

If you noticed that I skipped two associations above, I applaud you on noticing. The associations we skipped were between users and their posts and topics. To do this lets first create two migrations and migrate the changes to the database:

script/generate migration add_user_to_posts user_id:integer
script/generate migration add_user_to_topics user_id:integer
rake db:migrate

Now, to tell ActiveRecord about these associations, open up app/models/user.rb and add the following lines under before_save.

# app/models/user.rb
has_many :posts
has_many :topics

Now open up both app/models/post.rb and app/models/topic.rb and add the following lines:

# app/models/post.rb & app/models/topic.rb
belongs_to :user

Now we have the extra associations to continue.

Step 10 – Polishing up the Forums List

So now that have have the basics now, we are going to continue on and polish up the forums listing, after we will continue on and polish the topics list.

Let’s first open up app/models/forum.rb. We are going to add a new method to help us find the most recent post in a forum. The method that we are going to add is below:

# app/models/forum.rb
def most_recent_post
  topic = Topic.first(:order => 'last_post_at DESC', :conditions => ['forum_id = ?', self.id])
  return topic
end

This method finds the first Topic that has a forum ID of whatever the ID of the forum we called this method upon, and then sorts it by date and returns the value.

So open up the app/views/forums/index.html.erb and find the table cell inside of the loop that has a class of right. We are going to change it to the following:

# app/views/forums/index.html.erb
<td class="right"><% if forum.most_recent_post %><%= distance_of_time_in_words_to_now forum.most_recent_post.last_post_at %> ago<% else %>no posts<% end %></td>

This code may look confusing, so I will go through it. First we reference to the method we added to our model, and see if it returned a record, because we may have a forum without any topics. Next, if we have a record, we use the date helpers Rails provides to parse the time into readable text. If we don’t have a record returned, we then output ‘no posts.’ Your page should look like:

Now that we have added the date, we are going to add in the link to the user who posted. Back in the view, change that table cell to look like the following:

# app/views/forums/index.html.erb
<td class="right"><% if forum.most_recent_post %>
  <%= distance_of_time_in_words_to_now forum.most_recent_post.last_post_at %> ago by
  <%= link_to forum.most_recent_post.user.username, "/users/#{forum.most_recent_post.last_poster_id}" %>
  <% else %>no posts<% end %>
</td>

Your page should now look like:

Step 11 – Polish the Topic List

So now we are going to head back to our app/views/forums/show.html.erb file. The title of this step may be a bit confusing due to the fact that we are editing the forum’s show action, but actually working on the Topics list. Now that you have the file open, find the table cell inside the look with the class of ‘right’ again. We are going to be using the same date helper, and our code will look like:

# app/views/forums/show.html.erb
<td class="right"><%= distance_of_time_in_words_to_now topic.last_post_at %> ago by <%= link_to topic.user.username, "/users/#{topic.last_poster_id}" %></td>

Now if you open up your browser, you should see:

Step 12 – Working on the Topic View

So now that we have everything in the Forum and Topic lists sorted out, we are going to work on the third major view. Go ahead and open up app/views/topics/show.html.erb and edit it so it looks more like:

# app/views/topics/show.html.erb
<% title @topic.name %>

<% for post in @topic.posts %>
<div class="post">
  <span class="left"><%= post.user.username %><br /><%= link_to "Delete", post, :confirm => 'Are you sute?', :method => :delete%></span>
  <span class="right"><%= post.content %></span>
</div>
<% end %>

<p>
  <%= link_to "Edit", edit_topic_path(@topic) %> |
  <%= link_to "Destroy Topic", @topic, :confirm => 'Are you sure?', :method => :delete %> |
  <%= link_to "View All", topics_path %>
</p>

Let’s also add in some styling:

# public/stylesheets/application.css
.post {width:100%; display:table;}
.post span {padding:5px; border:3px solid #eee; margin:0;}
.post span.left {width:150px; background:#eee; border:3px solid #eee; display:table-cell;}
.post span.right { border:3px solid #eee; display:table-cell;}

And now viewing a topic will look like this:

Step 13 – User Show Action

When a username is clicked on, we want to show their profile, correct? So to do this open up the app/controllers/users_controller.rb and and the show action below:

# app/controllers/users_controller.rb
def show
  @user = User.find(params[:id])
end

That is all we will need for this action in our controller. Now create a file named show.html.erb in app/views/users, and place the following inside:

# app/views/users/show.html.erb
<% title 'user: ' + @user.username %>
<div id="topics">
  <strong>topics</strong>
  <% @user.topics.each do |topic| %>
    <%= link_to topic.name, topic_url(topic.forum.id) %>
  <% end %>
</div>

That simple amount of code will display the user’s topics. No, there is not a limit here so we would technically show all of the topics by that user. The most ideal solution would be to create methods to handle this in our User model, but I will not do that here.

Step 14 – Working With Posts

So now that we are almost done, we need to work on the functions to edit a post. Let’s first open up app/controllers/topics_controller.rb and our view for the show action. First, on the controller, we are going to delete the edit and update functionality. You may wonder why, and the reason is that we are not going to allow a user to edit the topic, but rather posts. Yes, this does not allow a user to change the title of a topic but that is better than writing the extra logic required to edit a topic. So go ahead and delete those actions, and you can also delete the corresponding views.

Now open up the app/views/topics/show.html.erb file. First, in the paragraph tag at the bottom, delete the Edit link, the first one in the list. Then go up to the loop, and before the link to delete the post, we are going to add in a link to edit the post. The view when we are done should look like:

# app/views/topics/show.html.erb
<% title @topic.name %>

<% for post in @topic.posts %>
<div class="post">
  <span class="left"><%= link_to post.user.username, user_url(post.user) %><br /><%= link_to "Edit", edit_post_path(post) %><br /><%= link_to "Delete", post, :confirm => 'Are you sute?', :method => :delete %></span>
  <span class="right"><%= post.content %></span>
</div>
<% end %>

<p>
  <%= link_to "Reply", "#{new_post_path}?topic=#{@topic.id}" %> |
  <%= link_to "Destroy Topic", @topic, :confirm => 'Are you sure?', :method => :delete %> |
  <%= link_to "View All", forum_path(@topic.forum_id) %>
</p>

We also added a reply link for functionality we will add in a minute. Now, when you click on the link to edit, it should work. Then if you try and change the post, Rails gives you an error. Well, we don’t want to have it redirecting where it is currently, so open up app/controllers/posts_controller.rb, scroll down to the create action, and find the line “redirect_to @post.” We are going to change the line so the final action looks like:

# app/controllers/posts_controller.rb
def create
  @post = Post.new(:content => params[:post][:content], :topic_id => params[:post][:topic_id], :user_id => current_user.id)
  if @post.save
    flash[:notice] = "Successfully created post."
    redirect_to "/topics/#{@post.topic_id}"
  else
    render :action => 'new'
  end
end

So that code will redirect us now to the show action for the forum id of the post. We only have one problem though, the create action is not passed the topic_id in the view, so open up app/views/posts/_form.html.erb and add the following line under the error messages.

# app/views/posts/_form.html.erb
<% if params[:topic] %><input type="hidden" id="topic_forum_id" name="post[topic_id]" value="<%= params[:topic] %>" /><% end %>

So now that we also need to update the Topic that we have a new post. So to do this, we are going to again change the create action to look like:

# app/controllers/posts_controller.rb
def create
  @post = Post.new(:content => params[:post][:content], :topic_id => params[:post][:topic_id], :user_id => current_user.id)
  if @post.save
    @topic = Topic.find(@post.topic_id)
    @topic.update_attributes(:last_poster_id => current_user.id, :last_post_at => Time.now)
    flash[:notice] = "Successfully created post."
    redirect_to "/topics/#{@post.topic_id}"
  else
    render :action => 'new'
  end
end

What we did was after the post was saved, we used the topic_id to find our topic, and then update two columns in our database. Go ahead and copy those two lines that we added into the same place on our update action, so it looks like:

# app/controllers/posts_controller.rb
def update
  @post = Post.find(params[:id])
  if @post.update_attributes(params[:post])
    @topic = Topic.find(@post.topic_id)
    @topic.update_attributes(:last_poster_id => current_user.id, :last_post_at => Time.now)
    flash[:notice] = "Successfully updated post."
    redirect_to @post
  else
    render :action => 'edit'
  end
end

We also changed the redirect on this one so that it does the same as the create action. Now go down to the destroy action, and change the redirect to:

redirect_to forums_url

Step 15 – Putting the Final Touches on the Forum

Now, we need to do some quick cleaning up in our views for links that are unnecessary or no longer work. First up, the show action for the forums, so open up app/views/forums/show.html.erb and delete the show and edit links so that the view looks like:

# app/views/forums/show.html.erb
<% title @forum.name  %>

<table>
  <tr>
    <th width="60%">Topic Title</th>
	<th width="10%">Replies</th>
    <th width="30%">Last Post</th>
  </tr>
  <% for topic in @forum.topics %>
    <tr>
      <td><%= link_to h(topic.name), topic_path(topic.id) %>
	  <td><%= topic.posts.count - 1%></td>
      <td class="right"><%= distance_of_time_in_words_to_now topic.last_post_at %> ago by <%= link_to topic.user.username, "/users/#{topic.last_poster_id}" %></td>
	  <td><%= link_to "Destroy", topic, :confirm => 'Are you sure?', :method => :delete %></td>
    </tr>
  <% end %>
</table>

<p><%= link_to "New Topic", "/topics/new?forum=#{@forum.id}" %></p>

We also need to update the Topic creation method so that it adds in the user_id of our current user, so open up app/controllers/topics_controller.rb and change the Topic.new line to:

# app/controllers/topics_controller.rb
@topic = Topic.new(:name => params[:topic][:name], :last_poster_id => current_user.id, :last_post_at => Time.now, :forum_id => params[:topic][:forum_id], :user_id => current_user.id)

We also need to clean up the app/views/forums/index.html.erb file, so go ahead and open it up and delete the show link so the file looks like:

# app/views/forums/index.html.erb
<% title "Forums" %>

<table>
  <tr>
    <th width="70%">Forum</th>
    <th width="30%">Last Post</th>
  </tr>
  <% for forum in @forums %>
    <tr>
      <td><h4><%= link_to h(forum.name), forum_path(forum.id) %></h4>
        <small><%= forum.topics.count %> topics</small><br />
        <%=h forum.description %></td>
      <td class="right"><% if forum.most_recent_post %><%= distance_of_time_in_words_to_now forum.most_recent_post.last_post_at %> ago by <%= link_to forum.most_recent_post.user.username, "/users/#{forum.most_recent_post.last_poster_id}" %><% else %>no posts<% end %></td>
      <td><%= link_to "Edit", edit_forum_path(forum) %></td>
      <td><%= link_to "Destroy", forum, :confirm => 'Are you sure?', :method => :delete %></td>
    </tr>
  <% end %>
</table>

<p><%= link_to "New Forum", new_forum_path %></p>

Step 16 – Administrative Functions

Currently, any user can edit, delete, and create anything they want. So we are going to open up our Application helper in app/helpers/application_helper.rb. We are going the add in a few extra methods to help us deal with authentication and user levels. Before the ‘end’ tag, add these methods:

# app/helpers/application_helper.rb
def admin?
  if current_user.permission_level == 1 || current_user.id == 1
    return true
  else
    return false
  end
end

def owner?(id)
  if current_user.id == id
    return true
  else
    return false
  end
end

def admin_or_owner?(id)
  if (admin? || owner?(id))
    return true
  else
    return false
  end
end

These methods allow us is find out if a user is an admin, and owner by passing in the user_id of what we are looking for, and one that handles both. The method checking for an admin also allows the first user to be an admin automatically. So we are first going to open up the Forum index.html.erb file. We are going to add in some extra code now to hide the create, edit, and destroy forum links from anyone but admins. We are going to wrap each of these links with <% if admin? %> and <% end %>. The final code will look like:

# app/views/forums/index.html.erb
<% title "Forums" %>

<table>
  <tr>
    <th width="70%">Forum</th>
    <th width="30%">Last Post</th>
  </tr>
  <% for forum in @forums %>
    <tr>
      <td><h4><%= link_to h(forum.name), forum_path(forum.id) %></h4>
        <small><%= forum.topics.count %> topics</small><br />
        <%=h forum.description %></td>
      <td class="right"><% if forum.most_recent_post %><%= distance_of_time_in_words_to_now forum.most_recent_post.last_post_at %> ago by <%= link_to forum.most_recent_post.user.username, "/users/#{forum.most_recent_post.last_poster_id}" %><% else %>no posts<% end %></td>
      <% if admin? %><td><%= link_to "Edit", edit_forum_path(forum) %><% end %></td><% end %>
      <% if admin? %><td><%= link_to "Destroy", forum, :confirm => 'Are you sure?', :method => :delete %></td><% end %>
    </tr>
  <% end %>
</table>

<p><% if admin? %><%= link_to "New Forum", new_forum_path %></p>

Now if you try and reload the page, you will receive and error saying “undefined method `permission_level’.” The error is telling you that there is no column named permission_level. We need to create a migration, so run the following commands to create and migrate the changes:

script/generate migration add_permissions_to_users permission_level:integer
rake db:migrate

Now, everything should work properly. Now, we are going to move onto the show action, so open up app/views/forums/show.html.erb, and edit it to look like the following:

# app/views/forums/show.html.erb
<% title @forum.name  %>

<table>
  <tr>
    <th width="60%">Topic Title</th>
	<th width="10%">Replies</th>
    <th width="30%">Last Post</th>
  </tr>
  <% for topic in @forum.topics %>
    <tr>
      <td><%= link_to h(topic.name), topic_path(topic.id) %>
      <td><%= topic.posts.count - 1%></td>
      <td class="right"><%= distance_of_time_in_words_to_now topic.last_post_at %> ago by <%= link_to topic.user.username, "/users/#{topic.last_poster_id}" %></td>
      <% if admin? %><td><%= link_to "Destroy", topic, :confirm => 'Are you sure?', :method => :delete %></td><% end %>
    </tr>
  <% end %>
</table>

<p><% if logged_in? %><%= link_to "New Topic", "/topics/new?forum=#{@forum.id}" %><% end %></p>

What we did was make the destroy action admin only, and the create topic action logged in only. Now, we are going to work on the Topic show action, the file being app/views/topics/show.html.erb. Open it up and edit it so it looks like:

# app/views/topics/show.html.erb
<% title @topic.name %>

<% for post in @topic.posts %>
<div class="post">
  <span class="left"><%= link_to post.user.username, user_url(post.user) %><br /><% if logged_in? %><% if admin_or_owner?(post.user.id) %><%= link_to "Edit", edit_post_path(post) %><br /><%= link_to "Delete", post, :confirm => 'Are you sute?', :method => :delete %><% end %><% end %></span>
  <span class="right"><%= post.content %></span>
</div>
<% end %>

<p>
  <% if logged_in? %><%= link_to "Reply", "#{new_post_path}?topic=#{@topic.id}" %> |<% end %>
  <% if admin? %><%= link_to "Destroy Topic", @topic, :confirm => 'Are you sure?', :method => :delete %> |<% end %>
  <%= link_to "View All", forum_path(@topic.forum_id) %>
</p>

We again just implemented some methods so that unregistered users and users that do not have enough permissions are unalbe to see some links. Now that we have the links locked down, we are going to lock down the controllers, so first open up app/controllers/forum_controller.rb and at the op right under the class declaration, add:

  before_filter :admin_required, :except => [:index, :show]

So, now if you try your application, you will notice that Rails will give us an error, and that is because he have not defined admin_required. To add this, we are going to define it in our application controller, so open it up and add the following method right before the last end tag:

# app/controllers/application_controller.rb
def admin_required
  unless current_user && (current_user.permission_level == 1 || current_user.id == 1)
    redirect_to '/'
  end
end

This says that unless we are an admin, redirect to the home page. We now are going to continue to our topics controller, just like with the forums contoller, we are going to add some before filters, and this time, add:

# app/controllers/topics_controller.rb
before_filter :login_required, :except => [:index, :show]
before_filter :admin_required, :only => :destroy

So what we are saying is that you must be logged in for every action except index and show, and you must be an admin to access the destroy action. Next, we are going to work on our posts controller. This one is going to be a tad more work. First, add the before filter:

# app/controllers/posts_controller.rb
before_filter :login_required

Next, we are going to add in an extra method to our application_controller.rb file to handle this. Here is the method we are going to add:

# app/controllers/application_controller.rb
def admin_or_owner_required(id)
  unless current_user.id == id || current_user.permission_level == 1 || current_user.id == 1
    redirect_to '/'
  end
end

This method checks to see if the user_id passed to it equals our current user’s id, or if our current user is an admin. Back to our posts controller, and edit the actions of edit, update, and destroy so they look like:

# app/controllers/posts_controller.rb
def edit
  @post = Post.find(params[:id])
  admin_or_owner_required(@post.user.id)
end

def update
  @post = Post.find(params[:id])
  admin_or_owner_required(@post.user.id)
  if @post.update_attributes(params[:post])
    @topic = Topic.find(@post.topic_id)
    @topic.update_attributes(:last_poster_id => current_user.id, :last_post_at => Time.now)
    flash[:notice] = "Successfully updated post."
    redirect_to "/topics/#{@post.topic_id}"
  else
    render :action => 'edit'
  end
end

def destroy
  @post = Post.find(params[:id])
  admin_or_owner_required(@post.user.id)
  @post.destroy
  flash[:notice] = "Successfully destroyed post."
  redirect_to forums_url
end

Now that we have the authentication done, we have one last step.

Step 17 – Finishing the Application Layout

So now we are going to add a header to our application layout file. Go ahead an open up app/views/layouts/application.html.erb and add the following right after the body tag:

# app/views/layouts/application.html.erb
<div id="header">
	<h1>My Forums!</h1>
	<% if logged_in? %>
		Welcome <%= current_user.username %>! Not you?
		<%= link_to "Log out", logout_path %>
	<% else %>
		<%= link_to "Sign up", signup_path %> or
		<%= link_to "log in", login_path %>.
	<% end %>
</div>

Now open up our CSS file and add the following line:

# public/stylesheets/application.css
#header {width:75%; margin:0 auto;}

Opening up your web browser, you should see something like the following:

Conclusion

I hope you enjoyed this tutorial, and if you made your way through this monstrosity, I commend you! If you are looking for a forum, this wouldn’t be a bad start, but Beast (altered_beast in this case) is a great forum with a really nice and smooth design. Below is a screen with stats about our program, and it took 276 lines of code (excluding views) to build this forum!


Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • Subodh D. Choure

    Hi !
    I already posted my query. i want to provide some additional information regarding this.
    The code in tuts is working and displays the interface asking for login or signup
    with title My Forums.
    But when I clicked on sign up it gives error.

    RuntimeError in Users#new

    Showing app/views/users/new.html.erb where line #5 raised:

    Called id for nil, which would mistakenly be 4 — if you really wanted the id of nil, use object_id

    Extracted source (around line #5):

    2:
    3: Already have an account? .
    4:
    5:
    6:
    7:
    8:

    RAILS_ROOT: D:/InstantRails-2.0-win/rails_apps/forum
    Application Trace | Framework Trace | Full Trace

    D:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_controller/record_identifier.rb:76:in `dom_id’
    D:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_view/helpers/record_identification_helper.rb:16:in `dom_id’
    D:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_view/helpers/form_helper.rb:293:in `apply_form_for_options!’
    D:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_view/helpers/form_helper.rb:277:in `form_for’
    D:/InstantRails-2.0-win/rails_apps/forum/app/views/users/new.html.erb:5:in `_run_erb_app47views47users47new46html46erb’

    ======================

    my code for file : app\views\users\new.html.erb is as

    Already have an account? .s

    I’m trying this on windows

    I tried your other tuts which works fine.
    I’m sure this will do with some modifications.

    Please help..

    • kljh

      hello
      the code works fine but I also have some problem with it..

  • amewaypoelp

    Hello to All the Guests and Members,
    My computer worked not correctly, many mistakes and buggs. Help me, please to fix buggs on my computer.
    I used Windows 2003.
    Thanks,
    amewaypoelp

  • Keensebeich

    Hope you are having a good time,

    I’m glad to introduce you a shipping company with low rates usamailforward.com
    The first box I received from them was well packed and in good order! If you ask me – I will never use any other forwarding company.
    Sorry to bother you with this – but this really helped me and I hope this will help you!

    Keren

  • http://hubpages.com/hub/Thrive-Learning-Institute unfanytut

    Wow…

    3 hrs ago I got a message from 9722840600 @ 972-284-0600 and for some reason thought the the person calling was a scam.

    I’m so sick and tired of promotional calls – I decided to complain to the gov and complain.

    Anyway, I feel like such a fool Gulf Coast Western -the oil drilling corporation- was trying to make contact who I interviewed with last month – we’re calling to tell me I got the job!

    Wow, I really feel dumb. But I think I’ve lost my chance…!!

  • waveslider

    I have experienced the same problem as Subodh.

    It seems to occur when doing anything that involves the User model. For instance, I get the following error whenever I attempt to visit the forum index page, because it calls the admin? methodfrom the application helper. It seems that ActiveRecord is not mapping fields in the users table through method_missing correctly:

    undefined method `permission_level’ for nil:NilClass
    Extracted source (around line #23):

    20: no posts
    21:
    22:
    23:
    24:
    25: ‘Are you sure?’, :method => :delete %>
    26:

    Is this a bug related to Authlogic, maybe? Or is it a rails bug?

    Thanks in advance

  • Div

    # def create
    # @topic = Topic.new(params[:topic])
    # if @topic.save
    # @topic = Topic.new(:name => params[:topic][:name], :last_poster_id => current_user.id, :last_post_at => Time.now, :forum_id => params[:topic][:forum_id])
    #
    # if @post.save
    # flash[:notice] = “Successfully created topic.”
    # redirect_to “/forums/#{@topic.forum_id}”
    # else
    # redirect :action => ‘new’
    # end
    # else
    # render :action => ‘new’
    # end
    # end

    In this you dont have a variable called @post & you are checking @post.save. Which is leading to a error nil.save ! Also I ahve seen one more error in app/views/topics/_form.html.erb here also you ahve used @post with out defining it.
    May I know the reason please..I am getting error & not bale move forward.

    • erik

      Did you ever get this working?

  • TorpElorietop

    Hey

    Really glad to get into this forum
    Its what I am looking for.
    Hope to know more member here.

  • Snuff

    @waveslider

    I had this error while attempting to read a forum/topic/post without user access.
    I simply resolved it by adding a !nil for the admin? condition on app/helper/application_helper

    def admin?
    if !nil || current_user.permission_level == 1 || current_user.id == 1
    return true
    else
    return false
    end
    end

    With an account (and after some fix) everything was working so i knew it was due to the fact that an user who haven’t got an account haven’t got an id too so the admin? fonction was called with nil and the if condition couldn’t deal with this nil.

    After that, my guest can visit all the forums/posts/topics, cannot reply/create new ones and see the Destroy action but by clicking on it he’s sent to the Create an account form so i guess that’s not important to fix this but it shouldn’t be hard.

    Also
    forum_id and user_id errors : I think that the updated version of nifty_scaffold is buggy.
    I had to add this line to add/models/
    - topic.rb : attr_accessible :name, :last_poster_id, :last_post_at, :user_id, :forum_id
    - user.rb : attr_accessible :username, :email, :password, :password_confirmation, :user_id
    - forum.rb : attr_accessible :name, :description, :forum_id
    - post.rb : attr_accessible :content, :user_id, :forum_id, :topic_id

    Because when i wanted to create a topic, my console (execute > cmd > ruby script/server) reported me that it can’t get theses informations as they are private, so everything was bugged without giving him what he want.

    I don’t think all these accessible attributes are necessary and this might lead to some security breach but atleast my forum is working without any rails error now ;D

    Thanks for this tutorial !

  • http://blogesaurus.com/ Gypetyclape

    Hey everyone! I just finished my first wordpress blog site, what do you think? Any advice would be great http://blogesaurus.com/ 0667470742

  • Eddie

    These networks of sites are great. Quality articles and well maintained.

    Except when you encounter an article such as this one written by Alex Coomans. I have no idea how a quality site allowed a tutorial such as this one be submitted without checking if everything works fine. A tutorial should help the user learn and now have them ripping their hair out in frustration. Especially when they’ve done the step exactly as stated, but it breaks.

    Tutorials and work such as this brings down the quality of the site. This article should not still be up and accessible unless the author or the site fixes the code and make sure IT WORKS.

  • Hopes

    It’s working fine..except.. when i click “new Post”, it’s giving the following error in the webpage.

    HTTP Status 404 –

    ——————————————————————————–

    type Status report

    message

    descriptionThe requested resource () is not available.

    ——————————————————————————–

    GlassFish Server Open Source Edition 3.0.1

    _______________________________________________________________________
    Do anyone know how to solve this?

  • Hopes

    Sorry.. the error is when i click “New Topic”..

  • Hopes

    Guys.. can anyone please post the working code..? I can’t fix the errors arising in the code..

  • http://www.pierwszemiejsce.net.pl Paul from Poland

    I merely wished to officially say “Hi there” to everyone here. In my opinion , that this appears like an unusually appealing place to be online.

    I cannot wait to begin. Do you have any sort of guidance for someone in the beginning stages? Any certain section that you would advise more versus others to begin?

    I want to to share with you a quote that i have often found to be tremendously motivational in order to start formal introductions:

    [quote]Never believe in mirrors or newspapers. ~Tom Stoppard[/quote]
    Greetings,
    Paul

  • Teegan

    Can this code work for rails below version 3? Because I am having a lot of errors and not too sure if it is a lost cause.

  • Terrebenthx

    Hello

    I’m new…Nice forum !

    My name is Benoit, 22 years old (French).

    Nice to meet you !

  • http://www.chennaimoms.com Rachel

    Hi!

    First time i involve with ruby on rails development for the site of http://www.chennaimoms.com and this is not a small site development because this is a social networking website for mothers..and this forum is really very good for developers…

    Thank you for your helpful information.. and please check it out my site and give your valuable comments..

    especially please check blog page give your comments..

  • http://nislab.human.waseda.ac.jp/kandou/archives/2009/01/09/page_id=20 Asia Girman

    Thank you for creating this blog and sharing your viewpoint. I enjoy reading it during my lunch break at work. It’s nice to consider my thoughts off work even if for only 30 mins. Cheers

  • Svetaprettygirl

    Please! Leave more information and more links about next themes:
    1. my boyfriend test
    2. dating agencies dorset
    3. dating nude severus
    4. new zealand adult dating
    5. alberta dating speed

  • Soycleenginge

    всем здравствуйте
    решил поделиться информацией про ремонт квартир недорого
    заказывал услуги лучшей
    сделали быстро и качественно

    • Inf

      удачно поделился =) я тоже поделисюсь для KSL =)

  • JRailsGuy

    Has anyone found a better tutorial for a forum for Rails? Or know what changes need to be made to get this working. I am slowing making changes but comes across many errors, I will list a few:
    Step 7 -> forums/show -> topic.posts – 1 -> topic.posts.count
    Step 8 -> delete ->
    Step 8-> topics_controller, I still haven’t figured out how to get ‘create’ to work, current_user_id returns nil??
    Step 8->new (add line) -> @post = Post.new
    Step 10 -> distance_of_time_in_words_to_now forum.most_recent_post.last_post_at -> time_ago_in_words(forum.most_recent_post.last_post_at)
    Step 10 -> : I cannot get this to work yet, problem with username
    Step 11-> : Same as above ???

    Any suggestions? Thanks

  • JRailsGuy

    As you can see do not copy and paste anything in here, type it and do not include ‘%’ they see to give a problem:
    Step 8 -> delete ->
    Step 10 -> link_to forum.most_recent_post.user.username, “/users/….. : I cannot get this to work yet, problem with username
    Step 11-> link_to topic.user.username, “/users/….. : Same as above ???

  • JRailsGuy

    Also forgot to mention, why does ‘current_user.id’ not work (returns nil). Is it suppose to (Rails3), or do I have something wrong? Thanks

  • J

    To follow up on why usernames and current_user.id doesnt work. Well this is because a user is not logged in. Skip to Step 17 and add the info to application.html.erb so you can log on and have a user id. This should make topics_controller – create also work and save the id. Still some minor problems but at least framework now works.

    • Erik

      Anyone have this packed as a tarball or a .zip in Rails 2 or Rails 3? I’m running into a few of the issues when developing this in Rails 2. Please email me at erik.klix@gmail.com.

  • adrian

    i’m getting this error so i cannot create new topics what could it be?

    NoMethodError in Topics#new

    Showing app/views/topics/_form.html.erb where line #9 raised:

    undefined method `content’ for nil:NilClass

    Extracted source (around line #9):

    6:
    7:
    8:
    9:
    10:
    11:
    12:

    Trace of template inclusion: app/views/topics/new.html.erb

    RAILS_ROOT: C:/Users/Adrian/Desktop/gamesore – copia
    Application Trace | Framework Trace | Full Trace

    C:/Ruby187/lib/ruby/gems/1.8/gems/activesupport-2.3.8/lib/active_support/whiny_nil.rb:52:in `method_missing’
    C:/Users/Adrian/Desktop/gamesore – copia/app/views/topics/_form.html.erb:9:in `_run_erb_app47views47topics47_form46html46erb_locals_form_object’
    C:/Users/Adrian/Desktop/gamesore – copia/app/views/topics/_form.html.erb:1:in `_run_erb_app47views47topics47_form46html46erb_locals_form_object’
    C:/Users/Adrian/Desktop/gamesore – copia/app/views/topics/new.html.erb:3:in `_run_erb_app47views47topics47new46html46erb’

    Request

    Parameters:

    {“forum”=>”1″}

  • adrian

    6:
    7:
    8:
    9:
    10:
    11:
    12:

    sorry that’s the code missing

  • adrian

    mm ok sorry again, the code is the one from step 8 the one we change from the original topics newhtml.erb the problem is on the “content”

  • Heather

    Up and working with ubuntu/rails3/ruby1.9/heroku. Very easy and only a few bugs that other users already had answers to. Thanks a lot!

    • erik

      How did you get this running in Rails 3? Can you send me a packed version of source? I have a bunch of errors when creating in Rails 2. You can email me at erik.klix@gmail.com

  • http://vmexpertise.com Peter

    Hi there
    I got stuck on the 8th module.

    Showing app/views/topics/_form.html.erb where line #9 raised:
    undefined method `content’ for nil:NilClass

    6:
    7:
    8:
    9:
    10:
    11:
    12:

    This happens when I click “new topic” in one of the forums.

    This website: http://www.railsrocket.com/undefined-method-foo

    gives an advice to add attr_accessor :content to the class.

    I’ve changed the class to look like this:

    class Post < ActiveRecord::Base
    attr_accessible :content
    attr_accessor :content
    belongs_to :topic
    end

    But it didn’t help.

    What else should I do ?

  • PrirEnenomymn

    Hello.
    well been moving right along now, and experimenting a bit, So far i’ve got backups from IMGBurn and Nero (dvd video) to play in a standalone player fine. I still can’t get any to show up as a data rom or a DVD for that matter ,in the xbmc (xbox media center), except when I burn one in toast on my powerbook (data disk/default) same media and all, same files. Or when I had this NEC burner hooked up to a mac… (you know this story). I cant understand why a burning software makes so much difference. especially when Nero had so many options and Toast has like none. I’m gonna try Easy Media Creator 9, because this is the same company that makes toast. hmmm suck a good program on the mac but such a cheesy lookin pc program??? my name is nitepeople but I can only have ten characters

  • http://www.skportfolio.vacau.com Shehzad

    Please teach me using step by step tutorial of how to create a shopping cart using Ruby on Rails.

  • http://strefastylu.pl/lampy/sufitowe/ lampy sufitowe

    are there any tutorials for RoR online for free? or ebooks meaby…

    thanks for informative post with instruction and ‘examples’.

  • http://love.monro-surf.ru/ lovemonro
  • SpurneKneence

    Guy .. Excellent .. Amazing .. I will bookmark your site and take the feeds additionallyI am glad to find so many useful information right here in the post, we want develop extra techniques on this regard, thanks for sharing. . . . . .

  • Bralkkemkek

    leather boots.
    3fc1f2e1ae1334d2

  • Dacu

    I keep getting the same error… When I put ‘script/generate nifty_authentication’ into the console, ‘ Script is not recognized as an internal or external command, operable program or batch file.

    What am I doing wrong? I’m in the correct directory. So I’m not sure what the hell is going on.

    Please help!

  • Paul

    Step 7. I have error like this. What is funny topics and posts are working fine. Any ideas?

    NoMethodError in ForumsController#index

    undefined method `all’ for Forum:Module
    Rails.root: C:/Sites/forum

    Application Trace | Framework Trace | Full Trace
    app/controllers/forums_controller.rb:3:in `index’
    Request

    Parameters:

    None
    Show session dump

    Show env dump

    Response

    Headers:

    None

    • shail

      I also have the same error. Don’t know why the ‘all’ method not working.

  • http://www.prokura-inkasso.pl Radca prawny Poznań

    I also have the same problem.

  • beatgonza24

    Hello guys!

    I found some errors in this code and also some things that you have to do different is you’re using Rails 3… I’m a newbie in RoR, so this is helpful for people like me. I’m going to write the fixes, this might save you a lot of time.

    1. Change the commands to use the nifty-generators gem (You can check the link http://github.com/ryanb/nifty-generators/tree/master).

    Before you had to write: script/generate nifty_authentication
    Now: rails g nifty:authentication

    This applies also for other commands, like the ones for running the migrations:

    Before: script/generate migration add_foreign_to_topics forum_id:integer
    Now: rails g migration add_foreign_to_topics forum_id:integer

    2. To make the destroy link work in rails 3:
    Go to app/views/layouts/application.html.erb and change:

    Before:
    After:

    3. In previous versions of Rails, you had to use so that any HTML would be escaped before being inserted into the page. In Rails 3 and above, this is now the default.

    Before:
    Now:

    4. Your models might include attr_accessible, so you might get this error: ActiveModel::MassAssignmentSecurity::Error: Can’t mass-assign protected attributes:

    When you used attr_accessible in your model, you told Rails to only accept those attributes via mass assignment.

    Solution: erase the attr_accessible code lines.

    5. When you use the nifty:authentication and run the migrations, then try to signup, you might get something like this: ‘uninitialized constant User::BCrypt’

    Solution: Include this on your Gemfile: gem “bcrypt-ruby”, :require => “bcrypt”, then run bundle install and restart your server.

    6. The doesn’t work the first time, cause you don’t have users, I deleted this conditions and then I added an user to make it work, but I also saw here this solution:

    In the helpers/application_helper.rb, change the admin? method:

    if !nil || current_user.permission_level == 1 || current_user.id == 1

    7. There is an error in the topics/_form.html.erb, cause you can’t access the @post.content if you’re about to make a new one, so I included this condition:

    8. There are some changes you have to do on the topics_controller:

    def new
    @topic = Topic.new
    end

    def create

    @topic = Topic.new(:name => params[:topic][:name], :last_poster_id => current_user.id, :last_post_at => Time.now, :forum_id => params[:topic][:forum_id], :user_id => current_user.id)

    if @topic.save
    @post = Post.new(:content => params[:post][:content], :topic_id => @topic.id, :user_id => current_user.id)

    if @post.save
    flash[:notice] = “Successfully created topic.”
    redirect_to “/forums/#{@topic.forum_id}”
    else
    redirect :action => ‘new’
    end
    else
    render :action => ‘new’
    end
    end

    9. Go to views/forums/show.html.erb and change (in case you’re getting this error: can’t convert Fixnum into Array )

    Before:
    After:

    10. There is an error in the views/users/show.html.erb, cause when you click on a topic, it doesn’t send you to that topic, instead it’s taking the forum id.

    Before:
    After:

    I’m sorry if this is too big, I wrote all I could remember, there might be some other fixes I did… and I can’t assure you these are the right solutions, but they worked to me. Take care!

  • TumusyRurry

    услуги комиссионера

  • sharath

    what is meant by bootstrap in ruby on rails and how it is implemented how to declare this and use.

  • sharath

    what is meant by bootstrap in ruby on rails and how it is implemented how to declare this and use.

  • argo_sar
  • t3rmin41

    Hello,

    I’m getting stuck with this step:

    # config/routes.rb
    map.root :controller => “forums”

    If add this to routes.rb, “rails server” command doesn’t launch HTTP server and server exits with error. If I don’t add this line, when type in browser localhost:3000/forums I get error:

    undefined method `all’ for Forum:Module

    How to sort it out?

    • http://twitter.com/PanickedKernel Noah Cabiac

      I ran into the same problem. I was using rails 3 so doing this seemed to do the trick

      # config/routes.rb
      root :to => ‘forums#index’

  • shail

    Step 7. I have an error

    NoMethodError in ForumsController#index

    undefined method `all’ for Forum:Module

  • Mohawk Guy

    Hey, I have a question. Ok, so how would I be able to use Ruby and all that and then design the forum using HTML and CSS. I mean I just want a forum page on my website, not the whole thing being a forum. like this site: simpleportal.net

  • berry

    i am berry from uk i want to share my happiness with the general public of what DR cafai of africa has done for me in the last few weeks i was once in love this guy called mccatty we in love with each other until travelled out of my state for two year and we promise ourselve to be together forever, but before return from my journey he where now having another lover when i try to come back to he. He told me i should go away i love him so much that i could not let he go just like that then i told a friend about it and she advice me and recommend this man ogun for me when i visit he at cafaispiritualtemple@yahoo.com he only ask me to buy some items for sacrifices to help me get my ex back and he actualy did it and it work well and today i am happy with incase any one is out there with same problem or any kind i advice he or she to contact this man today at cafaispiritualtemple@yahoo.com and with what he did for me i belive he can also help you thank once again Dr cafai