[Ruby ORM] Integrate Sequel to Ruby on Rails

Without any doubt, Ruby on Rails's still the hotest open source framework for web development. However, it doesn't mean RoR's perfect girl that all of guys wanna to date with. TeachEmpower has done a benchmark about web framework performance then RoR's position was the bottom of table. As a Rails lover, I want to figure out why Rails speed's so slow and find out the solution for this dark side. After some days experienced with Sequel, I has found the light at the end of tunnel.

1. Sequel - ActiveRecord slayer

There're so many article pointed out that ActiveRecord is one of reason contribute to poor performance of Rails. Someone has blamed it on ORM while other want to improve ActiveRercord performance by some precious tips which you can understand after at least over 5 years with Rails. Why we don't use another light weight ORM to replace ActiveRecord? The answer is Sequel, a rare project hasn't any issues yet on github (Good job, Sequel team!)

Different to ActiveRecord, a monolithic gem, Sequel is built based on modular architect when Core part contains just few needed functions and all others're kept in Extension/Plugins and used in necessary case. Because of this design, Sequel can run five time faster than Active record.

sequel-plugin_system.png

Besides, developer can increase flexibility by choosing suitable Sequel plugin or writing a separated module which can fit system's requirements. In this post instead of meantioning about the bright side of Sequel which has portraied in many blog posts(e.g http://twin.github.io/ode-to-sequel/) and comments on Github, I want to minimize the gap between Sequel and Rails developers who're almost familiar with ActiveRecord.

2. Intergrate Sequel to RoR

2.1 Basic setup

2.1.1 Clear away ActiveRecord

The first thing to do before bring Sequel to your life is elimiating everything of ActiveRecord. If you start a new project, let add -O or --skip-active-record params to your rails new command. If you want to get rid of ActiveRecord in existing project, let follow this:

  • In config/application.rb, remove require rails/all and add these line:
require "action_controller/railtie"
require "action_mailer/railtie"
require "sprockets/railtie"
require "rails/test_unit/railtie"
  • In config/application.rb, remove
config.active_record.raise_in_transactional_callbacks = true
  • In config/environments/development.rb, remove
config.active_record.migration_error = :page_load
  • In config/environments/production.rb, remove
config.active_record.dump_schema_after_migration = false

It's notable that we don't have to remove config/database.yml and the next part will be shown the reason why we have to keep it.

2.1.2 Install Sequel

Basically, Sequel is made for pure Ruby code so it doesn't fully support Rails stack as ActiveRecord. This's meaning that we have to setup Database from the starting point, manual generating migration files and create various tasks to do for migrating new data.

To make a shortcut, TalentBox team has made a terrific gem named sequel-rails which aim to bring Sequel close to ActiveRecord users. For installation, just add this line to Gemfile then bundle

gem 'sequel-rails'

To connect to database, a plus point for sequel-rails when employing config/database.yml and similary to ActiveRecord we don't need to change anything in configuration. This is the sample configuration for mysql

development:
  adapter: mysql2
  pool: 5
  timeout: 5000
  database: sample_sequel
  user: myuser
  password: 'mypassword'

Before start geeking, let spend few minutes to do some stuff in config files to make Sequel become your new home for development. These setting can be placed in config/application.rb or config/environments/ when you want some specify some extra functions for each environment

  • For schema:
    # Allowed options: :sql, :ruby.
    config.sequel.schema_format = :sql
    # Whether to dump the schema after successful migrations.
    # Defaults to false in production and test, true otherwise.
    config.sequel.schema_dump = true
  • Overriding database.yml: Sure, we can do that but I prefer to do it in yml files
    config.sequel.max_connections = 16
  • Database tasks: Most exciting part, by default
    config.sequel.load_database_tasks = true
When this flag's true, we can run database task as same as `ActiveRecord` commands but I'd like to specific command with `:sequel`
    config.sequel.load_database_tasks = :sequel
then instead of `rake db:migration`, I'll let all the world know that I'm using `Sequel` when type `rake sequel:migration`
  • Logger: indispensibale plugin that'll show off how fast of Sequel
    config.sequel.logger = Logger.new($stdout)
  • Default plugins: as mentioned above, the basic component of Sequel is plugin and definitely there're some plugins that you want to load by default. For example, we want to add timestamps into every model, so just push it into configuration
    config.sequel.after_connect = proc do
      Sequel::Model.plugin :timestamps, update_on_create: true
    end

Ok, I think now we almost done and we can start to enjoy Sequel

2.2 Use Sequel like ActiveRecord

2.2.1 Migration

It's undeniable that ActiceRecord has provided a rich tasks working with rake command so sequel-rails gem has exploited this advantages of ActiveRecord. To print out all tasks that sequel-rails provides, let use rake -T or rake --tasks. And here're all tasks specific for migration

rake db:create[env]                        # Create the database defined in config/database.yml for the current Rails.env
rake db:create:all                         # Create all the local databases defined in config/database.yml
rake db:drop[env]                          # Drop the database defined in config/database.yml for the current Rails.env
rake db:drop:all                           # Drops all the local databases defined in config/database.yml
rake db:force_close_open_connections[env]  # Forcibly close any open connections to the current env database (PostgreSQL specific)
rake db:migrate                            # Migrate the database to the latest version
rake db:migrate:down                       # Runs the "down" for a given migration VERSION
rake db:migrate:redo                       # Rollbacks the database one migration and re migrate up
rake db:migrate:reset                      # Resets your database using your migrations for the current environment
rake db:migrate:up                         # Runs the "up" for a given migration VERSION
rake db:reset                              # Drops and recreates the database from db/schema.rb for the current environment and loads the seeds
rake db:rollback                           # Rollback the latest migration file or down to specified VERSION=x
rake db:schema:dump                        # Create a db/schema.rb file that can be portably used against any DB supported by Sequel
rake db:schema:load                        # Load a schema.rb file into the database
rake db:seed                               # Load the seed data from db/seeds.rb
rake db:setup                              # Create the database, load the schema, and initialize with the seed data
rake db:structure:dump[env]                # Dump the database structure to db/structure.sql
rake db:test:prepare                       # Prepare test database (ensure all migrations ran, drop and re-create database then load schema)

As we can see, it's bacsically same as ActiveRecord tasks so we don't need to change the habit when working with migration. About migration files, when entering rails generate migration create_users, a new migration file's created follow timestamps format which's similar to ActiveRecord. Now let dig into content of migration files

Sequel.migration do
  change do
    create_table :users do
      primary_key :id
      String :name
      DateTime :created_at
      DateTime :updated_at
    end
  end
end

There're some different points when working Sequel migration:

  • primary_key and foregin_key: If id of record is implicit in ActiveRecord, Sequel requires to define by primary_key. However, the appearance of foregin_key help us to easily define the relation with other tables. E.g: when an user has many posts
    Sequel.migration do
      change do
        create_table :posts do
          primary_key :id
          foreign_key :user_id, :users, index: true
          String :content, text: true
          DateTime :created_at
          DateTime :updated_at
        end
      end
     end
  • index: Just adding the params index: true in the column
  • Timestamps: for created_at and updated_at columns, we need to place it into record and enable plugin :timestamps inside Model
  • Data types: let a glance at this migration file
    Sequel.migraion do
      change do
        create_table(:columns_types) do
          Integer :a0                         # integer
          String :a1                          # varchar(255)
          String :a2, :size=>50               # varchar(50)
          String :a3, :fixed=>true            # char(255)
          String :a4, :fixed=>true, :size=>50 # char(50)
          String :a5, :text=>true             # text
          File :b                             # blob
          Fixnum :c                           # integer
          Bignum :d                           # bigint
          Float :e                            # double precision
          BigDecimal :f                       # numeric
          BigDecimal :f2, :size=>10           # numeric(10)
          BigDecimal :f3, :size=>[10, 2]      # numeric(10, 2)
          Date :g                             # date
          DateTime :h                         # timestamp
          Time :i                             # timestamp
          Time :i2, :only_time=>true          # time
          Numeric :j                          # numeric
          TrueClass :k                        # boolean
          FalseClass :l                       # boolean
        end
      end
    end
What I love in Sequel migration's simple, functional but still comprehensive. Same datatype is wrap into same group, if we want large space for them, just specify in optional fields. That's why we don't need `Text` type because it's almost same with `String` except size (lol). Besisdes, with `TrueClass` and `FalseClass`, we can forget about `Boolean, default: true/false`. So interesting!

2.2.2 Model

I think this part we can refer from document page of Sequel because everything I write will be duplicated from Github page

3. The bottom line

Sometimes changing's deeply paintful, especially with something that belongs to soul but it's not imposible. Let take my posts as a reference for using Sequel in Rails until it replaces completely ActiveRecord in next versions of Ruby on Rails.