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;
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:drop rake commands, along with managing the tables with a
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;
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
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
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;
idis set as the unique primary key (auto-incrementing); each user must have a unique id.
password_hashto store the users’ password (encrypted with bcrypt - see below)
null: falsejust means that these fields are required.
- any column using
uniquewill 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)
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;
: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
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.
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 visitor
POST /login- user submits their log in credentials
POST /logout- user signs out
GET /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 users
POST /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
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
And lastly a simple show view;
When a user clicks the “logout” link, we have to send
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,
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.
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
- 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
updated_atcolumns 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;
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
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.
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.
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::MethodOverrideand the Roda plugin
:all_verbsare your friends here).
- Introduce Pagination on your homepage (using the Sequel
- Use partials to clean up your views (via the Roda
:partialsplugin) - the post new/edit forms would be prime candidates for this.
- Add a
check_permissionsmethod 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.