Up and Going in Roda: A Simple Ruby Blog
In my last article we built a simple Roda website, which let us deliver some rather static pages. We’ll build on that work by implementing some basic blogging features such as User accounts and Posts.
Note: as this article was posted in April 2015 I can not guarantee it will still work with the latest versions Roda.
We’ll be using a PostgreSQL database, the defacto DB of many Rubyist, which we’ll interact with using Sequel, an excellent ORM by Jeremy Evans, who is also the developer behind Roda.
Please make sure to install PostgreSQL before continuing. Once that’s done we need to add the appropriate RubyGems to our Gemfile;
After running bundle install
we need to connect Sequel to the database. In a real world application we’d want to keep our myapp.rb
uncluttered by placing the configuration in a separate database.yml
file, and ORM initialization in database.rb
, but I’ll simplify that for this tutorial by including everything right in the main app file.
It can be useful to use environment variables for things like usernames and passwords, which is what I’ve done here, but if you’d like, just replace the ENV
directly with your database user/password credentials. If your host
setting is different then make sure to change that too.
Rake Tasks for the Database
Using the Psql interactive terminal to create your database and tables can get tiresome pretty quickly, so we’ll create some useful rake tasks (actually based on the Padrino generators!) to make our life easier. These tasks will allow us to create/drop the database using just the db:create
and db:drop
rake commands, along with managing the tables with a db:migrate
command.
This is pretty typical rake code so I won’t go into too much explanation, but please do read the Rake documentation if you’re not sure what is going on here.
$ mkdir -p ~/myapp/lib/tasks
We’ll need a Rakefile
with some basic set up configuration;
And now for the tasks themselves;
The create_db
and drop_db
methods do nothing more than generate and execute postgres commands. We pass in the Sequel configuration details (config = Sequel::Model.db.opts
) set in myapp.rb
, which are used to set the credentials for the postgres createdb
and dropdb
commands.
If you’ve previously worked with Rails then you’ll be familiar with the command for listing available tasks;
$ rake -T
which should display the following list;
rake db:create # Create the database
rake db:drop # Drop the database
rake db:migrate # Perform migration up to latest migration available
rake db:migrate:down # Perform migration down (erase all data)
rake db:migrate:up # Perform migration up to latest migration available
With these in place we can now create the myapp_development
database using the following command;
$ rake db:create
=> Creating database 'myapp_development'
<= db:create executed
User Accounts
To prevent just any old visitor to our site from posting content we need to have them sign in to get that kind of access, which means we’re going to need user accounts. There’s a lot of details to consider in regards to user security, but I don’t want this post to get too long, so I’m going to skip over much of those details and just post up the code. I do recommend you read the official Roda documentation and look over the sample projects to get a better understanding of how this works within the framework.
Creating the User table
Sequel comes with its own migrations DSL that we’ll use to insert a users
table into the database.
If you’ve used migrations in other frameworks such as Ruby on Rails then this should look pretty familiar. If this is new to you then here a few details to help clarify things;
id
is set as the unique primary key (auto-incrementing); each user must have a unique id.email
is also set to unique as this will be used as part of the login credentials.password_hash
to store the users’ password (encrypted with bcrypt - see below)null: false
just means that these fields are required.- any column using
unique
will automatically have a database index created.
Once you’re happy with the migration you can apply that to the database with the following command;
$ rake db:migrate
<= db:migrate:up executed
(Run this command each time you create a new migration file)
User Model
Inheriting from Sequel::Model
gives us all the ORM functionality directly on the User
class, which leaves us to just add some validations and encryption related code;
Neither :password
nor :password_confirmation
are actually saved to the database; they’re only provided for validating form data. To protect a user password (just in case our server gets hacked) we’ll encrypt passwords with BCrypt before saving them to the database. The before_save
callback hands off the password to BCrypt, which is then saved to the password_hash
column.
Add the Bcrypt gem to the Gemfile;
User Routes and Views
Along with pages for viewing and creating users, we also need to have some way for them to login and logout. We should probably implement some basic authotrization too, so that only signed in users can administer accounts and post new content. There’s a lot of moving parts to this and I’m not sure this tutorials is the place to go into a lot of detail, but I will include some basic protection such as using CSRF tokens on HTML forms and by enabling user sessions using the Rack Session/Protection plugins.
In the code below you’ll notice I’ve also added the validations plugin, which will help in making sure we have entered valid form data before trying to save the record.
(run bundle install
)
I’m going to add login/logout routing as well as routes for user pages all in one go. It’s a lot of code but if you break it down into small pieces it should be easy to follow;
This gives us these basic routes;
GET /login
- display log in form to visitorPOST /login
- user submits their log in credentialsPOST /logout
- user signs outGET /users/new
- where a “new user” HTML form will be displayed.GET /users/:id
- get a user profile (:id corresponds to their primary key).GET /users
- listings of all usersPOST /users
- for saving a new user to the database.
When a user tries to sign in we need some way to validate their credentials, which is done via the User
class;
authenticate
first looks for a User with the given email and then checks that the given password matches the one stored in the database; first by using BCrypt to decrypt the stored password then running the check.
Finally it’s time to add all the views for these routes. Here’s a simple login form;
An index page for listing all current users and their details;
The new view contains a form in which we can enter a users’ details (note the inclusion of a csrf_tag
);
And lastly a simple show view;
When a user clicks the “logout” link, we have to send POST
request to the server. If you’re feeling adventurous you could implement this using Javascript, but I’m going to do it with a plain old form. While we’re in the layout view let’s also add a couple of menu items for “login” and “users”;
At the moment guest visitors can administer users which is not very secure. In Roda it’s quite easy to prevent guests from accessing these features, but before we do that it’s very important for us to create a user account, otherwise we won’t be able to access them ourselves!
Go ahead, boot up the server, rackup
, visit localhost:9292/users
and create yourself an account, I’ll wait.
User Authentication and Authorization
Our final task on User Accounts is to restrict guest visitors from accessing user pages. For this we’ll be using Rack::Session to enable users to sign in. We’ve already provided some basic authorization when we used session[:user_id]
to prevent guest visitors from seeing the Users and logout links, and we can use this same technique inside the routes.
Simple!
As routes are processed linearly, Roda won’t process anything after this authorization check unless a session has been set, which happens when the user signs in successfully. This is going to be restrictive in a real world app, but for our purpose it should be fine.
That’s all the basic user account stuff done now. Of course there’s plenty more than could be added; perhaps a little more eye-candy; displaying form errors when the user input is wrong would be very useful; or showing “flash” messages when a new user is created successfully.
That was a lot of work so go take a short break. Make yourself a nice cup of tea and when you come back we’ll work on the task of giving registered users the ability to create posts.
Posts: Model, Routes and Views
In creating user accounts we learned all the nescessary skills for allowing registered users to write new posts. In this section we’ll create a migration, model, routing and a few view templates.
Don’t forget to apply the migrations before continuing.
There’s two main differences between the posts
and users
migrations
- We need to know which user wrote which post, so I’ve set up a foreign key constraint with
user_id
, along with creating an index for that column. - I’ve added
created_at
andupdated_at
columns so as to keep track of when a post is created, which will be useful when sorting posts.
To improve database performance I’ve also added an index for created_at
, but if you feel you need to sort posts via the modified date then you’ll also want to add an index for that column too.
Sequel comes with a useful plugin for automatically populating these timestamps on record creation;
It’s important that we add associations between the user and post models;
A User
can have many posts, but a Post
can only have one user. Don’t forget to tell User
about the association as well;
If you ever need to delete a user then it’s likely you’ll want to delete their posts too, so I’ve also added the on_delete: :cascade
contraint. Sequel will now automatically delete all of this user posts when their account is deleted.
The posts routing is as follows;
Hmmm, this is a bit ugly! Our earlier decision for simple user authorization is coming back to bite us, as we’re having to split up the post routes into two separate groups; we need to allow guest visitors to view individual posts, but not create new ones, so I’ve had to straddle the session[:user_id]
check. Perhaps in a later tutorial we’ll look at a cleaner and more versatile solution to authorization.
Compared to the user routes there’s a couple of differences worth noting;
- User is assigned automatically to that which is set in the session;
@post.user = User[session[:user_id]]
- sorting is done via
created_at
, but in reverse order!
Notice also that we haven’t created a route for a posts listing (index
). As posts are going to be displayed in a blog-like way, we don’t need to create this additional view. Just new
and show
views;
And the post view;
To give easy access to the new page we should add a “New Post” link in the nav bar;
Boot up the server, log in and create a test post. Then when you visit the posts page; localhost:9292/posts/1
(if that was your first post) you should see your newly created post.
Sometimes posts need to be updated so let’s create the editing routes and views next.
Editing Posts
The “edit form” is pretty much the same as the “new form”, so copy/paste that into a new file; views/posts/edit.erb
change the form action parameter to /posts/<%= @post.id %>/edit
and update any appropriate text labels; e.g. change “New Post” to “Edit Post”.
Give yourself a nice “edit” link in views/posts/show.erb
by adding <a href="/posts/<%= @post.id %>/edit">Edit Post</a>
, and don’t forget to wrap it in a session[:user_id]
authorization check (take a look at the navigation bar code if you can’t remember how to do that.)
Homepage Posts Feed
Most typical blogs include a “posts feed” directly on the homepage, and ordered to show the latest post first. All that’s need to enable this for our application is to update the r.root
route and adjust the homepage.erb
view to iterate through a collection of posts;
Here we request a reverse order collection, placing it in the @posts
variable, which we can then iterate over in the homepage view;
On restarting your server you should now be able to browse around you new blog.
Final Words
We set up Roda to deliver some simple static pages, and then added User accounts and Posts to produce a simple blog-like website. There are many improvements to be made before putting this site live for the world to see, but you have enough knowledge about Roda and Sequel to move forward on your own. Here’s some ideas on what you could work on next;
- Allow users to delete their posts (
Rack::MethodOverride
and the Roda plugin:all_verbs
are your friends here). - Introduce Pagination on your homepage (using the Sequel
:pagination
extension). - Use partials to clean up your views (via the Roda
:partials
plugin) - the post new/edit forms would be prime candidates for this. - Add a
check_permissions
method for user authorization in routes and views.
I’ll be creating plenty of Roda tutorials over the coming months, which may well include these topics, so do check be here regularly.
I hope you found this two part tutorial useful (part one can be found here) and I’d really appreciate your feedback, so please do add your comments below.
PART 1: Up and Going in Roda: Static Ruby Websites is also available.