This episode marks the beginning of a miniseries in GoRails that I'd like to do that it's going to talk about building a forum in Rails. We're not necessarily building this to make the best forum in the world, but we're using this as our template to build out a bunch of different features that you'll probably use in other applications.
Here, if you go to gorails.com, click on the forum link on the navigation, you'll be taken to a page like this that is the list of forum threads that we currently have. This is for subscribers right now so if you subscribe you have access to the forum, and if you post questions on here we'll contribute back, I’ll be sure to post and we'll discuss things like how you should design a feature or how you should implement it, what are the trade-offs of the ways that you want to do things so feel free to subscribe and join the forum and ask questions but this series is going to talk about how I built this and how I implemented search, what's the tradeoff of the way we did it right now, how did we lock this down and then how did we go about making it flexible so when you click on a post, you can see that we have avatars for users and those avatars come from gravatar, another thing that we'll add in a future episode is uploading avatars through carrierwave so you can default through something like gravatar but if they want to override it you'll be able to upload your own photo. Doing things like these times and dates here are actually presented using the local time gem which takes UTC timestamps and then converts it to what your browser is currently in. So we're going to go through a lot of cool things including markdown and syntax highlighting like you can see here and as we go build this out you'll be able to see actually how I designed and developed the website that you can currently use and that is hopefully going to explain lots of things about the thought process you want to use when you go through building an application like this and you'll be able to take lots of these things because they're bite-sized-chunks that you're probably going to use in a different application or a different area. So the forum is a really good example of lots of these little features that you're going to want to have in other applications. Because I wanted to build this anyways, it made a perfect sense to create a miniseries out of it.
This first episode is going to be talking about building the architecture for the forum which is going to include building the basic rails application designing the forum thread model, user model and the forum post model and moving from there. So we'll probably also talk about how to adjust things for performance reasons, like every time that you view a thread, if you want to show the number of posts in there. When you throw the post maybe we should cache that number, use a counter cache and so on. We'll go through a lot of different topics using this same application to explain it all. So this episode, we'll start now by jumping into the terminal and building our forum Rails app, so I'll put this on GitHub and you'll be able to take a look as well as we build this out, and let’s get started.
Now the version of rails that we're going to use for this forum is going to be 4.2.0 beta1, so I want to cover sending email notifications in the application so we're going to use ActiveJob for this, and then we're probably going to interface it with a gem, something like sucker_punch or sidekiq so that we can install that gem, run it on our server, and then the background workers will get processed using ActiveJob's interfface to that gem. We'll get to take a look to at some of the features in Rails 4.2 and implement them inside of our Rails app.
First let's make check to make sure that I have rails 4.2.0beta1 installed, and I do. So let's create a new application, let's call it gorails_forum.
Once this gets all of the gems installed, we'll dive into the application and start talking about the database models that we want to use.
Okay, now that that's done, let's open up our application in MacVim and I'm going to hop into this blank text file here and let's talk about the models that we want. So the first thing I usually do when I'm building an application is I try and think about the database structure that I'm going to use in the application and then sort of how it's going to work and then I know what sort of controllers I need, what actions I need in there and then the views and so on. I start from the model first and then build the rest out around that.
The first model that we need is definitely obvious, we need users. A forum isn't really much without users and I'm going to use Devise for this, so Devise is just sort of my go-to gem for authorization. If you're following along, you're welcome to use anything you want. They'll all pretty much work very similarly. You can build this from scratch as well, but just know that you're going to need an email address and password. Email for sure, because we're going to use that for gravatars in the future episodes. So use whatever authentication mechanism you want. We'll use Devise, so you'll probably get familiar with devise watching these screencasts.
If we go back to the forum, the next model we can kind of see that we need is these threads, so each of these has a title and has many posts inside of it. So we need something to represent this larger picture of a thread, so let's call this a forum thread. The reason I'm going to call it a forum thread instead of just a thread is because I'm actually building this inside the GoRails web app, and so this forum thread allows me to scope this names so they know always that they're very closely related to the forum, so this also benefits you if you pull this out into your own Rails engine or gem that could be used and you pull this out into something like that that's reusable that you want to put in many apps, you can use this and it will always be safely sort of scoped into that namespace there so that you have the forum threads and so on automatically handled, and it likely isn't going to conflict with something you already have in your app. So these threads are going to have many posts inside of them but you know, first of course let's say:
Users need at least an email and they need at least a password.
Forum threads are going to need a user id, so we know who created the thread, and we're also going to need to know the title, or the subject of that. I can't remember exactly what I used, title and subject are almost interchangeable, you can use the one that makes sense for you.
And then forum posts are going to be related to these forum threads so this is going to have a forum thread id, and it's also going to have a user id, so then we'll know who wrote that post and then we're going to have a body which would be a text field. So these will be integers, and the subject will be a string, and that's just the default so we don't have to specify it, but for note taking it will be better. That's really all we need because we have users, they will have many forum threads, because of this, and that means that we can create the belongs to (association) to a user here. We'll have a has many forum posts, and that's because this belongs to a user but it also belongs to a forum thread.
If you haven't been very familiar with associations in Rails yet, you'll notice that every time that I say there's a column on this model with an ID after it. So, user id and forum thread id, that means that on that model, you want a belongs to (association) and on the other side, you'll use a has many. These don't have any columns to talk to the post or threads but we get that because it's the reverse of this association. So the belongs to is the important one, it means that the user id or whatever_id column is on that model and then you can go to the other one and write it as has_many or has_one.
Because we have the has_many's for these two, for this (Forum posts) and for this (Forum threads), we need the has_many here (Forum threads) for forum posts. So these are the associations that we will have in our models once we create them, and that will allow us to talk between all of them pretty seamlessly and we'll have no trouble building our queries and then displaying that in the forum.
I'm just going to save this as the Database Structure and I'm going to save this as the README.md so we'll start building the README later and build this out as we go along and talk about sort of what this is so you can use this for reference later if you're new to Rails.
First thing you need to do, is we want to install a couple gems, so you can see here's the new Gemfile by default for Rails 4.2, things are a little bit different than you might be used to, but it's basically the same, so I'm going to install the devise gem. Let's grab the latest Devise and we've got 3.3.0 so we'll come back here, add
gem "devise", "~> 3.3.0"
I'm also going to install the annotate gem and we want just the latest version of that, always. I'm also going to add the gem for better errors and binding of caller. So we'll have these three always available. I'm also going to make this a development only gem, so I'm just going to add these to the bottom, you can organize this as you go along.
We'll use annotate so that you can see those columns that we're going to create inside of a comment in our models that are errors, you can check out the BetterErrors episode if you're not familiar with that, but we'll go ahead and run bundle to install these gems, and there we go
#Rest of the gems gem "devise", "~> 3.3.0" group :development do gem "annotate" gem "better_errors" gem "binding_of_caller" end
And now we can run
rails g annotate:install
to install the annotate functionality so that when we automatically run a model generation, it will comment it for us. Then we'll run
rails g devise:install
And this will tell us to do a few things, which I'm going to get to later but we won't do it right away, so then the next thing we'll do is
rails g devise User
and we're going to install the user model, so I'm going to run another couple ones, so we'll generate those two models that we talked about, so we'll generate the forum thread and this is going to have a user_id which is an integer, and it will have a subject, which will be a string
In this point, Chris gets an error because of a compatibility problem, at the time you're viewing this you probably won't have this problem, if you do, updating your devise gem should do the trick. so you can skip to 15:00 if you want
And we got an error, because this is probably not compatible with Rails 4.2 just yet, so the 3.3.0 version is most likely not fully compatible and quick Google search brought me to: Rails 4.2 compatibility is an open issue on devise, so we'll be able to use a branch for this so that we can handle it as well. So we'll be able to use the branch and then when they release that version we'll be able to upgrade to that, and it looks like there's not good backwards compatibility with this latest version, so that's why it's taking a little bit longer. So we're going to install the devise gem from the master branch here, and so we'll say:
gem "devise", github: "plataformatec/devise", branch: "lm-rails-4-2"
Because it looks like this is the one that we want to use for Rails 4.2 until it gets fully merged into master.
So let's try running bundle again and then we'll run the rails generate install after this finishes and we have the latest version of devise.
All right, now that that's installed, let's run this command again
rails g model ForumThread user_id:integer subject
And it looks like it worked this time, so you want currently use that lm-rails-4-2 branch, and next we can run
rails g model ForumPost forum_thread_id:integer user_id:integer body:text
Now that all our models are generated, we can run
rake db:migrate to create that database, and then we'll go into those models and create the associations like we did in our comment. So if you look in app/models you'll be able to see a forum post, forum thread and users:
has_many :forum_threads has_many :forum_posts
belongs_to :user has_many :forum_posts
belongs_to :user belongs_to :forum_thread
So that wraps up this episode and we've built the basic architecture for our forum and next episode we're going to talk about building out the controllers, the actions and the views for our forum in this miniseries, so I will see you next week when we dive into all of that.
Transcript written by Miguel
Would it be possible to add categories to this? With like forum_thread belongs to categories and categories has many forum _threads?
Absolutely! I'd set it up the way you mentioned where categories are higher level than forum threads. Then you could have
categories#index as the root of the application and list out those, then forum threads inside. That's basically how http://ubuntuforums.org/ operates.
Major props to this video, as well as ALL of your other screencasts. I've been doing Rails for a little over a year now, but I can really appreciate the subtle 'beginner' things that you mention, such as the simple associations explanation, in your videos; I feel like that definitely helps some people who are wanting to dive into Rails at an early level. Keep up the good work Chris!