Sequel

Sequelは「Ruby用のシンプルで柔軟かつ強力なSQL Toolkit」として開発された、Rubyエコシステムで高く評価されているデータベース抽象化ライブラリです。「データベースとの効率的な対話」をコンセプトに、直感的なDSLとSQLの柔軟性を兼ね備え、PostgreSQL、MySQL、SQLite等主要データベースをサポート。軽量設計でありながらモデル機能、マイグレーション、接続プーリング、トランザクション管理等、本格的なアプリケーション開発に必要な機能を包括的に提供しています。

SQL toolkitRubyDatabaseDSLORMPostgreSQLMySQL

GitHub概要

jeremyevans/sequel

Sequel: The Database Toolkit for Ruby

スター5,038
ウォッチ112
フォーク1,077
作成日:2008年3月31日
言語:Ruby
ライセンス:Other

トピックス

なし

スター履歴

jeremyevans/sequel Star History
データ取得日時: 2025/7/19 09:30

ライブラリ

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