Sequel
Sequelは「Ruby用のシンプルで柔軟かつ強力なSQL Toolkit」として開発された、Rubyエコシステムで高く評価されているデータベース抽象化ライブラリです。「データベースとの効率的な対話」をコンセプトに、直感的なDSLとSQLの柔軟性を兼ね備え、PostgreSQL、MySQL、SQLite等主要データベースをサポート。軽量設計でありながらモデル機能、マイグレーション、接続プーリング、トランザクション管理等、本格的なアプリケーション開発に必要な機能を包括的に提供しています。
GitHub概要
jeremyevans/sequel
Sequel: The Database Toolkit for Ruby
トピックス
スター履歴
ライブラリ
Sequel
概要
Sequelは「Ruby用のシンプルで柔軟かつ強力なSQL Toolkit」として開発された、Rubyエコシステムで高く評価されているデータベース抽象化ライブラリです。「データベースとの効率的な対話」をコンセプトに、直感的なDSLとSQLの柔軟性を兼ね備え、PostgreSQL、MySQL、SQLite等主要データベースをサポート。軽量設計でありながらモデル機能、マイグレーション、接続プーリング、トランザクション管理等、本格的なアプリケーション開発に必要な機能を包括的に提供しています。
詳細
Sequel 2025年版はRubyにおけるデータベース操作の定番ライブラリとして15年以上の開発実績と成熟したAPIを誇ります。ActiveRecordとは異なる軽量アプローチを採用し、SQLに近い記述でありながらRubyらしい美しいDSLを提供。データセット(Dataset)とモデル(Model)の分離設計により、柔軟性と保守性を両立。複雑なクエリも直感的に記述でき、生のSQLとDSLの混在も可能。スレッドセーフな接続プーリング、豊富なプラグインシステム、SQLインジェクション対策等、プロダクション環境での使用に耐える堅牢性を持ちます。
主な特徴
- 直感的DSL: SQLライクでありながらRubyらしい美しいクエリ記述
- 軽量設計: ActiveRecordより軽量で高速なパフォーマンス
- 豊富なデータベースサポート: PostgreSQL、MySQL、SQLite、Oracle等対応
- 柔軟なアーキテクチャ: DatasetとModelの分離、生SQL混在可能
- プラグイン機構: 豊富なプラグインによる機能拡張
- スレッドセーフ: 接続プーリングと安全な並行処理
メリット・デメリット
メリット
- SQLに近い直感的なDSLで学習コストが低い
- ActiveRecordより軽量で高速、メモリ効率が良い
- 複雑なクエリも美しく記述可能で可読性が高い
- 生SQLとDSLの混在により柔軸性と最適化を両立
- 豊富なプラグインで機能を自由に拡張可能
- 十分にテストされ安定性が高く、長期間保守されている
デメリット
- ActiveRecordほど広く普及しておらず情報が限定的
- Railsとの統合がActiveRecordほど緊密ではない
- Convention over Configurationより明示的な設定が必要
- 一部の高度な機能でActiveRecordに劣る場合がある
- エコシステムがActiveRecord中心のRailsより小さい
- 初学者にはActiveRecordより取っ付きにくい可能性
参考ページ
書き方の例
インストールと基本設定
# Gemfile
gem 'sequel', '~> 5.75'
gem 'pg' # PostgreSQL用
gem 'mysql2' # MySQL用
gem 'sqlite3' # SQLite用
# 基本的な接続設定
require 'sequel'
# PostgreSQL接続
DB = Sequel.connect('postgres://user:password@localhost/my_database')
# MySQL接続
# DB = Sequel.connect('mysql2://user:password@localhost/my_database')
# SQLite接続(開発・テスト環境)
# DB = Sequel.connect('sqlite://my_database.db')
# 接続オプション付き
DB = Sequel.connect(
adapter: 'postgres',
host: 'localhost',
database: 'my_database',
user: 'username',
password: 'password',
max_connections: 10,
pool_timeout: 5
)
基本的なクエリ操作
require 'sequel'
# データベース接続
DB = Sequel.connect('sqlite://test.db')
# テーブル作成
DB.create_table :users do
primary_key :id
String :name, null: false
String :email, unique: true
Integer :age
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
DateTime :updated_at
end
# データセットの基本操作
users = DB[:users]
# データの挿入
user_id = users.insert(
name: '田中太郎',
email: '[email protected]',
age: 30,
created_at: Time.now
)
# 複数データの挿入
users.multi_insert([
{ name: '佐藤花子', email: '[email protected]', age: 25 },
{ name: '鈴木次郎', email: '[email protected]', age: 35 },
{ name: '高橋三郎', email: '[email protected]', age: 28 }
])
# データの取得
all_users = users.all
puts "全ユーザー: #{all_users}"
# 条件付き検索
adult_users = users.where(Sequel[:age] >= 30).all
young_users = users.where { age < 30 }.all
specific_user = users.where(email: '[email protected]').first
# 複雑な条件
users_in_age_range = users.where(age: 25..35).all
users_with_pattern = users.where(Sequel.like(:name, '%田中%')).all
# ソート
sorted_users = users.order(:age).all
desc_sorted = users.order(Sequel.desc(:created_at)).all
# リミットとオフセット
paginated = users.limit(10).offset(20).all
高度なクエリとJOIN操作
# より複雑なテーブル構造
DB.create_table :posts do
primary_key :id
String :title, null: false
String :content, text: true
foreign_key :user_id, :users
DateTime :published_at
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
end
DB.create_table :comments do
primary_key :id
String :content, null: false
foreign_key :post_id, :posts
foreign_key :user_id, :users
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
end
# データセット定義
users = DB[:users]
posts = DB[:posts]
comments = DB[:comments]
# JOIN操作
# INNER JOIN
posts_with_users = posts
.join(:users, id: :user_id)
.select(Sequel[:posts][:title], Sequel[:users][:name])
.all
# LEFT JOIN
all_posts_with_users = posts
.left_join(:users, id: :user_id)
.select_all(:posts)
.select_append(Sequel[:users][:name].as(:author_name))
.all
# 複雑なJOIN
posts_with_comment_count = posts
.left_join(:comments, post_id: :id)
.group(Sequel[:posts][:id])
.select(
Sequel[:posts][:title],
Sequel[:posts][:content],
Sequel.count(Sequel[:comments][:id]).as(:comment_count)
)
.all
# サブクエリ
popular_posts = posts.where(
id: comments
.group(:post_id)
.having { count.function.* > 5 }
.select(:post_id)
).all
# ウィンドウ関数
ranked_posts = posts
.select(
:id,
:title,
:user_id,
Sequel.function(:row_number).over(
partition: :user_id,
order: Sequel.desc(:created_at)
).as(:rank)
)
.all
# 集約関数
stats = users
.select(
Sequel.count(:id).as(:total_users),
Sequel.avg(:age).as(:average_age),
Sequel.min(:age).as(:youngest),
Sequel.max(:age).as(:oldest)
)
.first
モデル定義とORM機能
# Sequelモデルの定義
class User < Sequel::Model
# テーブル名の明示的指定(省略可能)
set_dataset :users
# プラグインの読み込み
plugin :timestamps, update_on_create: true
plugin :validation_helpers
plugin :json_serializer
# アソシエーション
one_to_many :posts
one_to_many :comments
# バリデーション
def validate
super
validates_presence [:name, :email]
validates_unique :email
validates_format /\A[^@\s]+@[^@\s]+\z/, :email
validates_integer :age, message: '年齢は数値で入力してください'
validates_min_length 2, :name
end
# インスタンスメソッド
def full_profile
"#{name} (#{age}歳) - #{email}"
end
# クラスメソッド
def self.adults
where(Sequel[:age] >= 20)
end
def self.by_age_group(min_age, max_age)
where(age: min_age..max_age)
end
end
class Post < Sequel::Model
plugin :timestamps
plugin :validation_helpers
many_to_one :user
one_to_many :comments
def validate
super
validates_presence [:title, :content, :user_id]
validates_min_length 5, :title
validates_min_length 10, :content
end
def published?
!published_at.nil?
end
def publish!
update(published_at: Time.now)
end
# スコープ的なクラスメソッド
def self.published
exclude(published_at: nil)
end
def self.recent(days = 7)
where(Sequel[:created_at] >= Date.today - days)
end
end
class Comment < Sequel::Model
plugin :timestamps
plugin :validation_helpers
many_to_one :post
many_to_one :user
def validate
super
validates_presence [:content, :post_id, :user_id]
validates_min_length 3, :content
end
end
# モデルの使用例
# ユーザー作成
user = User.create(
name: '山田太郎',
email: '[email protected]',
age: 28
)
# 投稿作成
post = user.add_post(
title: 'Sequelの使い方',
content: 'Sequelは素晴らしいRuby ORMです...'
)
# コメント追加
comment = post.add_comment(
content: 'とても参考になりました!',
user: user
)
# アソシエーション経由でのデータ取得
user_posts = user.posts
post_comments = post.comments
comment_author = comment.user
# 条件付きアソシエーション
published_posts = user.posts_dataset.published.all
recent_comments = post.comments_dataset.where(Sequel[:created_at] >= Date.today - 7).all
トランザクション管理とバッチ操作
# 基本的なトランザクション
DB.transaction do
user = User.create(name: '取引ユーザー', email: '[email protected]', age: 30)
post = user.add_post(title: 'トランザクション投稿', content: '内容...')
post.add_comment(content: 'コメント', user: user)
end
# 例外時の自動ロールバック
begin
DB.transaction do
user = User.create(name: 'テストユーザー', email: '[email protected]', age: 25)
# 意図的にエラーを発生させる
invalid_post = user.add_post(title: '', content: '') # バリデーションエラー
end
rescue Sequel::ValidationFailed => e
puts "トランザクションがロールバックされました: #{e.message}"
end
# 手動ロールバック
DB.transaction(rollback: :reraise) do |conn|
user = User.create(name: '条件付きユーザー', email: '[email protected]', age: 30)
if some_condition_fails?
raise Sequel::Rollback, "条件が満たされませんでした"
end
user.add_post(title: '条件付き投稿', content: '内容...')
end
# バッチ操作
class BatchOperations
def self.bulk_insert_users(user_data_array)
DB.transaction do
user_data_array.each_slice(1000) do |batch|
User.multi_insert(batch)
end
end
end
def self.bulk_update_users(updates)
DB.transaction do
updates.each do |user_id, attributes|
User[user_id].update(attributes)
end
end
end
def self.efficient_data_processing
# メモリ効率的な大量データ処理
User.paged_each(rows_per_fetch: 1000) do |user|
# ユーザーごとの処理
process_user(user)
end
end
# プリペアドステートメントの活用
def self.optimized_batch_insert(data_array)
insert_statement = DB[:users].prepare(:insert, :insert_user,
name: :$name, email: :$email, age: :$age)
DB.transaction do
data_array.each do |data|
insert_statement.call(data)
end
end
end
private
def self.process_user(user)
# 実際の処理ロジック
puts "Processing user: #{user.name}"
end
def self.some_condition_fails?
# 何らかの条件チェック
false
end
end
マイグレーションとスキーマ管理
# マイグレーション例
Sequel.migration do
up do
create_table :categories do
primary_key :id
String :name, null: false, unique: true
String :description, text: true
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
end
alter_table :posts do
add_foreign_key :category_id, :categories
add_index :category_id
end
end
down do
alter_table :posts do
drop_column :category_id
end
drop_table :categories
end
end
# 複雑なスキーマ変更
Sequel.migration do
change do
create_table :user_profiles do
primary_key :id
foreign_key :user_id, :users, null: false, unique: true
String :bio, text: true
String :website
String :location
TrueClass :public_profile, default: true
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
DateTime :updated_at
index :user_id
index :public_profile
end
# 既存テーブルの変更
alter_table :users do
add_column :is_active, TrueClass, default: true
add_column :last_login_at, DateTime
add_index :is_active
end
end
end
# カスタムスキーマメソッド
class SchemaManager
def self.setup_initial_schema
DB.create_table! :users do
primary_key :id, type: :Bignum
String :name, size: 100, null: false
String :email, size: 255, null: false, unique: true
Integer :age
TrueClass :is_active, default: true
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
DateTime :updated_at
index :email, unique: true
index [:is_active, :created_at]
end
DB.create_table! :posts do
primary_key :id, type: :Bignum
String :title, size: 200, null: false
String :content, text: true
foreign_key :user_id, :users, type: :Bignum, null: false
DateTime :published_at
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
DateTime :updated_at
index :user_id
index :published_at
index [:user_id, :published_at]
end
end
def self.add_full_text_search
DB.run <<~SQL
CREATE INDEX posts_content_fts ON posts
USING gin(to_tsvector('english', title || ' ' || content));
SQL
end
end
プラグインシステムとカスタマイズ
# カスタムプラグインの作成
module Sequel
module Plugins
module Sluggable
module InstanceMethods
def generate_slug
self.slug = name.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/-+/, '-').chomp('-')
end
def to_param
slug || id.to_s
end
end
module ClassMethods
def find_by_slug(slug)
where(slug: slug).first
end
end
end
end
end
# プラグインの使用
class Category < Sequel::Model
plugin :sluggable
plugin :timestamps
plugin :validation_helpers
def validate
super
validates_presence :name
validates_unique :name
generate_slug if name_changed?
end
end
# 豊富な組み込みプラグインの活用
class Article < Sequel::Model
# よく使われるプラグイン
plugin :timestamps # created_at, updated_at自動管理
plugin :validation_helpers # バリデーションヘルパー
plugin :json_serializer # JSON変換
plugin :dirty # 変更追跡
plugin :caching # キャッシング
plugin :pagination # ページネーション
plugin :tree # 階層構造
plugin :nested_attributes # ネストした属性
# アソシエーション
many_to_one :user
many_to_one :category
one_to_many :comments
many_to_many :tags
def validate
super
validates_presence [:title, :content, :user_id]
validates_min_length 5, :title
end
# dirty プラグインの活用
def track_changes
if column_changed?(:title)
puts "Title changed from '#{initial_value(:title)}' to '#{title}'"
end
end
# pagination プラグインの活用
def self.paginated_articles(page = 1, per_page = 10)
paginate(page, per_page)
end
end
# 接続プーリングとパフォーマンス最適化
class DatabaseOptimizer
def self.configure_connection_pool
DB.pool.after_connect = proc do |conn|
# 接続後の設定
case DB.database_type
when :postgres
conn.exec("SET statement_timeout = '30s'")
conn.exec("SET lock_timeout = '10s'")
when :mysql
conn.query("SET sql_mode = 'STRICT_TRANS_TABLES'")
end
end
end
def self.connection_pool_stats
{
size: DB.pool.size,
max_size: DB.pool.max_size,
allocated: DB.pool.allocated,
available: DB.pool.available
}
end
# クエリログとパフォーマンス監視
def self.enable_query_logging
DB.loggers << Logger.new($stdout)
# 遅いクエリの監視
DB.extension :query_literals
DB.log_warn_duration = 1.0 # 1秒以上のクエリを警告
end
end