How to build and deploy a blog application in Padrino

"Many developers fall in love with the simplicity and expressiveness of Sinatra but quickly come to miss a great deal of functionality provided by other web frameworks such as Rails when building non-trivial applications." -- "Why" section of Padrino documentation.

When it comes to creating a web application, most developers have no hesitation in choosing Ruby on Rails. There is nothing wrong with that, but sometimes it is a triumph of form over the content. Rails has a large number of built-in tools, that a typical web application uses only a subset of. This affects both the performance of an application and its future maintainability.

Padrino framework comes to the rescue. It is a simple, lightweight framework built on top of Sinatra, which by itself supports the most popular components such as slim, activerecord, rspec, less, and many others.

This article will walk you through building and deploying a simple blog with an admin panel. A similar tutorial can be found on the official website but unfortunately it is a little out of date.

Demo application and source code

Application created during this tutorial is hosted on Shelly Cloud under http://padrino-sample-blog-app.shellyapp.com/ address. Source code can be found on github smt116/padrino-sample-blog-app repository.

Instalation

All you need is a padrino gem. As of this writing, the latest, released version is 0.11.4

$ gem install padrino

Successfully installed padrino-0.11.4
1 gem installed

Generating new project

Padrino just like Ruby on Rails, provides a tool which allows you to easily generate a controller, migration, or the entire project. In the application we will use PostgreSQL as a database, slim for view rendering, rspec for tests and jquery for scripts.

# Generate new project

$ padrino generate project sample_blog \
 --adapter=postgres \
 --migration-format=timestamp \
 --orm=activerecord \
 --test=rspec \
 --renderer=slim \
 --script=jquery
# Install gems

$ cd sample_blog && bundle install

Using padrino generate project --help you can check all of the supported components.

Database setup

As it was said before, the application will use PostgreSQL to store data.

Credentials

Database credentials can be found in config/database.rb file. Instead of storing login and password in file, it is better to use variables. Dotenv gem allows us to store sensitive data in .env files. Then this file can be loaded by our application and we can use environment variables defined inside.

Adding Dotenv to project

First, add the dotenv gem to Gemfile:

 # Project requirements
 gem 'rake'
+gem 'dotenv'

Than run bundle install:

$ bundle install

Creating .env files with database credentials

We will read database credentials from a .env.[environment] file, so we need to create a file at least for the development environment. Of course if we want to run tests or run application in production mode, we also need to create an .env file for those environments. These files should have the following structure:

POSTGRESQL_DATABASE=database
POSTGRESQL_USER=user
POSTGRESQL_PASSWORD=password
POSTGRESQL_HOST=host

For example:

# .env.development

POSTGRESQL_DATABASE=sample_padrino_blog_development
POSTGRESQL_USER=padrino_blog
POSTGRESQL_PASSWORD=password
POSTGRESQL_HOST=localhost

Please remember to add all your .env files to your .gitignore if you are working on a git repository.

Using environment variables in database configuration

Dotenv allows us to use environment variables instead of constants in a configuration file. With this in mind, code in config/database.rb can be simplified to:

ActiveRecord::Base.configurations[Padrino.env] = {
  adapter:   'postgresql',
  database:  ENV['POSTGRESQL_DATABASE'],
  username:  ENV['POSTGRESQL_USER'],
  password:  ENV['POSTGRESQL_PASSWORD'],
  host:      ENV['POSTGRESQL_HOST'],
  port:      5432
}

Loading credentials from env files

The last step is to load proper .env file in Padrino using Dotenv. We can do this by editing config/boot.rb.

 # Load our dependencies
 require 'rubygems' unless defined?(Gem)
 require 'bundler/setup'
 Bundler.require(:default, PADRINO_ENV)
+
+# Read database credentials from .env.environment file
+require 'dotenv'
+Dotenv.load ".env.#{Padrino.env}"

Create database

If you correctly performed the steps above, you can now create a database from the command line. Application uses activerecords, so it is enough to invoke rake ar:create.

$ rake ar:create
 DEBUG -   (472.4ms)  CREATE DATABASE "sample_padrino_blog_development" ENCODING = 'utf8'

This command will create database for the development environment by default. If you want to create a database for another environment, you need to specify it by setting PADRINO_ENV before rake. For example, to initialize database for tests, run:

PADRINO_ENV=test rake ar:create

Creating Post model

Post model will contain only two fields: title and body. Let's generate Post model using a Padrino generator.

$ padrino generate model post title:string body:text
       apply  orms/activerecord
       apply  tests/rspec
      create  models/post.rb
      create  spec/models/post_spec.rb
      create  db/migrate/20131122093218_create_posts.rb

As we can see, new migration is created, so the next step is to update database.

$ rake ar:migrate

Adding validations

We assume that the title and body should not be empty, so we should add a basic validation.

# models/post.rb

class Post < ActiveRecord::Base
  validates :title, presence: true
  validates :body, presence: true
end

Creating Post controller

As easily as before, we will generate index and show actions. Those methods will handle requests from the browser and render proper views in response. The index action is responsible for displaying a list of all posts, while the get action is responsible for displaying a single post.

$ padrino generate controller posts get:index get:show
      create  app/controllers/posts.rb
      create  app/helpers/posts_helper.rb
      create  app/views/posts
       apply  tests/rspec
      create  spec/app/controllers/posts_controller_spec.rb

Now we have to fill in the methods in app/controllers/posts.rb. By the way, get rid of unnecessary comments.

SampleBlog::App.controllers :posts do
  get :index do
    @posts = Post.all(order: 'created_at desc')
    render 'posts/index'
  end

  get :show, with: :id do
    @post = Post.find_by_id(params[:id])
    render 'posts/show'
  end
end

With those actions in place our routes will now look like this:

$ rake routes

Application: SampleBlog::App
    URL                 REQUEST  PATH
    (:posts, :index)      GET    /posts
    (:posts, :show)       GET    /posts/show/:id

We do not have a root page yet. The easiest solution at this point is to redirect / to the /posts page. To do this, simply add the following code just after enable :sessions line in app/app.rb file.

get :index do
  redirect url(:posts, :index)
end

Adding views for Post

The controller is trying to render views, which do not exist yet. Now it is the time to add them. We will use partials to keep our code DRY.

# app/views/posts/index.slim

- @title = "Posts"

= partial 'posts/post', collection: @posts

The last line above means that the content of the app/views/posts/post view will be inserted for each element of the @posts collection. We can also pass a single object to the partial. For example in the show view we have a single post variable, so we can use object: @post instead of collection: @posts.

# app/views/posts/show.slim

- @title = @post.title

= partial 'posts/post', object: @post

p
  = link_to 'View all posts', url_for(:posts, :index)

The _post partial view that was used above have the following structure:

# app/views/posts/_post.slim

h3
  = link_to post.title, url_for(:posts, :show, id: post)
h6
  = "Posted #{time_ago_in_words(post.created_at || Time.now)} ago"
p
  = simple_format(post.body)
hr

Adding simple layout based on Bootstrap

We want to use the same layout for all pages, so all we need is to fill app/views/layouts/application.slim with the following content:

doctype html
html
  head
    title #{@title if @title} | Padrino Sample Blog Application
    = stylesheet_link_tag "//netdna.bootstrapcdn.com/bootstrap/3.0.1/css/bootstrap.min.css"

  body
    .container
      .header
        h3.text-muted Padrino Sample Blog Application

    hr

    .container
      == yield

Padrino allows us to have different layouts for different pages or even to not have layout for specific page. Examples can be found in official guides.

Application preview

Preview

Adding administration panel

Our application works but we would like to be able to manage posts. Padrino provides ready-to-use admin panel. All you need to do is go through a few commands, and slightly modify the application code.

Firstly we need to generate an admin component.

$ padrino generate admin
       force  .components
      create  admin
       exist  admin
      create  admin/controllers/base.rb
      create  admin/controllers/sessions.rb
      create  public/admin
...
      insert  Gemfile
        gsub  admin/controllers/accounts.rb

This command also generated db/seeds.rb file. The default form of rake db:seed will ask us for a login and password. Let's modify this file and put values instead of using shell.ask:

 #   name = shell.ask("What's your name?")
 #   shell.say name
 #
-email     = shell.ask "Which email do you want use for logging into admin?"
-password  = shell.ask "Tell me the password to use:"
+email     = "foo@bar.baz"
+password  = "password"

 shell.say ""

Once this is done, we can move on to installing new gems, running migrations and seeding the database.

$ bundle install
$ rake ar:migrate
$ rake db:seed

Adding posts management in the admin panel

The admin panel is ready, but you can't manage posts with it yet. To make that possible, generate a management module based on the post model:

$ padrino generate admin_page post
      create  admin/views/posts
      create  admin/controllers/posts.rb
      create  admin/views/posts/_form.slim
      create  admin/views/posts/edit.slim
      create  admin/views/posts/index.slim
      create  admin/views/posts/new.slim
      insert  admin/app.rb

Now you can manage posts in administration panel.

Creating association between post and user

At the moment post is not associated with the user who added it. We need to add account field to the Post using migrations.

$ padrino generate migration AddAccountToPost account_id:integer
       apply  orms/activerecord
      create  db/migrate/20131122213359_add_account_to_post.rb
$ rake ar:migrate

We want to be able to display the author of the post, so we need to define a method that returns the relevant fields and put it into views.

# models/post.rb

 class Post < ActiveRecord::Base
+  belongs_to :account
+
   validates :title, presence: true
   validates :body, presence: true
+
+  def author
+    "#{account.name} #{account.surname}" if account
+  end
 end
# app/views/posts/_post.slim

 h3
   = link_to post.title, url_for(:posts, :show, id: post)
 h6
-  = "Posted #{time_ago_in_words(post.created_at || Time.now)} ago"
+  ' Posted
+  - if post.author
+    ' by #{post.author}
+  ' #{time_ago_in_words(post.created_at || Time.now)} ago
 p
   = simple_format(post.body)
 hr

The final step is to add the author to the post after its creation and add author to the table with all posts in admin panel.

# admin/controllers/posts.rb

   post :create do
     @post = Post.new(params[:post])
+    @post.account = current_account
     if @post.save
# admin/views/posts/index.slim

@@ -28,6 +28,7 @@ div class="tabs-content"
         th class='header' = mat(:post, :id)
         th class='header' = mat(:post, :title)
         th class='header' = mat(:post, :body)
+        th class='header' = mat(:post, :author)
         th class='header' = mat(:post, :created_at)
         th class='header' = mat(:post, :updated_at)
         th class='header list-row-action-header'
@@ -39,6 +40,7 @@ div class="tabs-content"
           td class='list-column' = post.id
           td class='list-column' = post.title
           td class='list-column' = post.body
+          td class='list-column' = post.author

Application preview

Preview

Deploying on Shelly Cloud

Due to requirements page in documentation, we need to specify application server by adding it to our Gemfile. We can pick thin or puma.

 source 'https://rubygems.org'

 # Server requirements
-# gem 'thin' # or mongrel
+gem 'thin' # or puma

 # Project requirements
 gem 'rake'

Shelly Cloud uses rake db:migrate and rake db:setup for database setup. Our application is using activerecord, so we already have rake db:migrate, rake db:create and rake db:seed as a aliases of rake ar:* tasks. What is missing is rake db:schema:load. Instead of we have rake ar:schema:load. What we can do, is to make an alias in Rakefile.

 PadrinoTasks.use(:database)
 PadrinoTasks.use(:activerecord)
 PadrinoTasks.init
+
+namespace :db do
+  namespace :schema do
+    task :load do
+      Rake::Task['ar:schema:load'].invoke
+    end
+  end
+end

The last problem that may occur is related to Rack::Protection and session hijacking. We will not describe this issue during this tutorial. Just disable the module for now.

# config/apps.rb

 Padrino.configure_apps do
   # enable :sessions
   set :session_secret, '...'
-  set :protection, true
+  set :protection, except: :session_hijacking
   set :protect_from_csrf, true
 end

If you can not disable protecting from session_hijacking, you can use SSL. This will secure transmission between application and users. The simplest way is to use rack-ssl-enforcer.

We are ready to go through a quick start and create application on Shelly Cloud. We can move forward if you already have account on Shelly Cloud (if not, please sign up).

Now just invoke shelly add in project directory and follow the instructions. That's all. Your blog is available at https://code-name.shellyapp.com. Administration panel is located under the /admin. You can log in using the login and password provided in db/seeds.rb.

As you can see, there is no .env file with database credentials for Shelly Cloud. This is because Shelly Cloud uses the environment variables to store database credentials, so custom configuration is not necessary. More information can be found in documentation.

Demo application and source code

Live demo is hosted on Shelly Cloud under http://padrino-sample-blog-app.shellyapp.com/. Source code can be found on github smt116/padrino-sample-blog-app repository.

FAQ

I can not generate new project

There is a known issue that will be fixed in next version of Padrino. Below is a simple workaround for this problem.

echo "gem 'padrino'" > Gemfile && bundle install

Can I use cache in Padrino application?

Yes, Padrino supports various types of cache storages such as Memcache or Redis. You can find more information in the official guides. It is worth to mention that Shelly Cloud provides Memcache out-of-the-box for free. Just as Redis, MongoDB or Websockets.

Can I use a different database driver than activerecord?

Basically yes. Just keep in mind that Shelly Cloud uses rake db:migrate, rake db:setup and rake db:schema:load for managing PostgreSQL databases. These tasks are invoked after deployments (depending on whether the schema file exists). If you use different driver with PostgreSQL you need to remember to make aliases for these tasks or create dummy rake db:migrate, rake db:setup and rake db:schema:load and invoke proper tasks manually using shelly rake task.

If you are using different database engine than PostgreSQL (Shelly Cloud also supports MongoDB and Redis), you need to create dummy rake db:migrate and rake db:setup tasks due to requirements.

Can I send e-mails with Padrino?

Yes, mailer is one of Padrino built-in features. On Shelly Cloud, sending e-mails works almost out-of-the-box. All you have to do is to set the sending method to smtp, so instead of via :sendmail use via :smtp in mailer. You can find examples here.

Does Padrino support localization?

Yes, Padrino uses I18n, so localization works similarly to Ruby on Rails. Please take a look at localization guides.

How can I access console on Shelly Cloud?

Just invoke shelly console and then require './config/boot'. This will run irb on your virtual server and then load the application environment.


Shelly Cloud is a platform for hosting Ruby and Ruby on Rails applications. You can focus on development without getting distracted by deployment, optimization and maintenance.


Want to be up-to-date with Shelly?

Follow us on Twitter and get latest news about platform, ongoing issues and blog posts.

Talk to a human