Sequel はシンプルで柔軟でパワフルな、Ruby の SQL データベースアクセスツールキットです。
-
Sequel はスレッド安全、コネクションプーリング、SQLクエリを構築するための簡潔な DSL、テーブルスキーマを提供します。
-
Sequel は、レコードを Ruby オブジェクトにマッピングし、関連レコードを操作するための包括的な ORM 層を含んでいます。
-
Sequel は次のような先進的なデータベース機能をサポートします: プリペアドステートメント、bound variables,、ストアドプロシジャ、セーブポイント、ツーフェーズコミット、トランザクション分離、マスター/スレーブ構成、データベースシャーディング
-
Sequel は現在次のアダプタを持っています: ADO, Amalgalite, CUBRID, DataObjects, DB2, DBI, Firebird, IBM_DB, Informix, JDBC, MySQL, Mysql2, ODBC, OpenBase, Oracle, PostgreSQL, SQLAnywhere, SQLite3, Swift, TinyTDS
ソースコードをチェックアウトするには:
git clone git://github.com/jeremyevans/sequel.git
コメントや提案があれば、Google グループに投稿してください。
sudo gem install sequel
require 'sequel' DB = Sequel.sqlite # メモリーデータベース DB.create_table :items do primary_key :id String :name Float :price end items = DB[:items] # データセットの作成 # Populate the table items.insert(:name => 'abc', :price => rand * 100) items.insert(:name => 'def', :price => rand * 100) items.insert(:name => 'ghi', :price => rand * 100) # レコード数の出力 puts "Item count: #{items.count}" # 平均価格の出力 puts "The average price is: #{items.avg(:price)}"
Sequel はデータベースに素早くアクセスするための IRB コンソールを含んでいます(通常は bin/sequel として参照できます)。次のようにして使用できます:
sequel sqlite://test.db # カレントディレクトリの test.db
DB に格納されたデータベースオブジェクトでIRB セッションを開始します。
IRB シェル(デフォルトの振る舞い)に加え、bin/sequel はデータベースのマイグレート、スキーママイグレーションのダンプ、データベースのコピーもサポートします。詳細は bin/sequel ガイド を見てください。
Sequel はデータベースの接続と操作に手間がかからないようにデザインされています。Sequel は、アプリケーションに集中できるように、接続の維持、SQL を正しく整形、レコードの取り出しのようなすべての退屈なことを処理します。
Sequel はデータを取り出すためにデータセットの概念を使用します。データセットオブジェクトは SQL クエリをカプセル化し、連鎖性をサポートし、簡潔で柔軟な便利な Ruby DSL を使用してデータを取り出せるようにします。
例えば、次のワンライナーは中東の国の GDP 平均を返します:
DB[:countries].filter(:region => 'Middle East').avg(:GDP)
これは次と同等です:
SELECT avg(GDP) FROM countries WHERE region = 'Middle East'
データセットは必要な時だけレコードを取り出し、それは保存されて後で再利用されます。レコードはハッシュ(またはカスタムモデルオブジェクト)として取り出され、Enumerable インタフェースを使用してアクセスできます:
middle_east = DB[:countries].filter(:region => 'Middle East') middle_east.order(:name).each{|r| puts r[:name]}
Sequel は map メソッドのような、データセットからデータを引き出すための便利なメソッドも提供します:
middle_east.map(:name) #=> ['Egypt', 'Turkey', 'Israel', ...]
また、 to_hash でハッシュとして結果を得ることができます。一つのカラムはキー、他は値として:
middle_east.to_hash(:name, :area) #=> {'Israel' => 20000, 'Turkey' => 120000, ...}
データベースに接続するには、単純に Sequel.connect に URL を与えます:
require 'sequel' DB = Sequel.connect('sqlite://blog.db')
接続 URL はユーザー名、パスワード、ポートのようなものも含みます:
DB = Sequel.connect('postgres://user:password@host:port/database_name')
コネクションプールの大きさや SQL クエリを記録するためのロガーのような、オプションのパラメータを記述することもできます:
DB = Sequel.connect("postgres://user:password@host:port/database_name", :max_connections => 10, :logger => Logger.new('log/db.log'))
接続にブロックを記述できます。ブロックが終了するとデータベースから切断されます:
Sequel.connect('postgres://user:password@host:port/database_name'){|db| db[:posts].delete}
Sequel のドキュメント全体にわたって、生成された Sequel::Database インスタンスを参照するために使用される DB 定数を見ることでしょう。これは app でひとつの Sequel::Database インスタンスを推奨することを反映しています。Sequel の慣習は DB 定数にインスタンスを格納することです。これは慣習で必須ではありませんが、推奨します。
注意: Sequel を使用するいくつかのフレームワークは、あなたのために Sequel::Database インスタンスを作成し、それにアクセスする方法をあなたは知らないかもしれません。多くの場合、<tt>Sequel::Model.db<tt> を通して Sequel::Database インスタンスにアクセスできます。
Database#run を使用して自由に SQL コードを実行できます:
DB.run("create table t (a text, b text)") DB.run("insert into t values ('a', 'b')")
生 SQL を元にデータセットを生成することもできます:
dataset = DB['select id from items'] dataset.count # 結果セットのレコード数を返します dataset.map(:id) # 結果セット中の全 id カラムの値から成る配列を返します
データセットを通して生 SQL でレコードを取り出すこともできます:
DB['select * from items'].each do |row| p row end
さらに SQL 文字列中でプレースホルダを使用できます:
name = 'Jim' DB['select * from items where name = ?', name].each do |row| p row end
データセットはレコードを取り出して操作する主要な方法です。これは通常は Database#from または Database#[] メソッド経由で生成されます:
posts = DB.from(:posts) posts = DB[:posts] # same
データセットはあなたが伝えた時だけレコードを取り出します。レコードのフィルタ、順序の変更、テーブルの結合等の操作が可能です。
all メソッドを使用して全レコードを取り出すことができます:
posts.all # SELECT * FROM posts
all メソッドはハッシュの配列を返します。各ハッシュはレコードに対応しています。
each を使用して1レコードずつ繰り返すこともできます:
posts.each{|row| p row}
さらに高度な方法を行えます:
names_and_dates = posts.map([:name, :date]) old_posts, recent_posts = posts.partition{|r| r[:date] < Date.today - 7}
データセットの最初のレコードを取り出すこともできます:
posts.first # SELECT * FROM posts LIMIT 1
特定の値を持つ1レコードを取り出すこともできます:
posts[:id => 1] # SELECT * FROM posts WHERE id = 1 LIMIT 1
データセットに順序がある場合、最後のレコードを問い合わせることができます:
posts.order(:stamp).last # SELECT * FROM posts ORDER BY stamp DESC LIMIT 1
レコードをフィルタする簡単な方法は、適合する値のハッシュを where に与えることです:
my_posts = posts.where(:category => 'ruby', :author => 'david') # WHERE category = 'ruby' AND author = 'david'
範囲も指定できます:
my_posts = posts.where(:stamp => (Date.today - 14)..(Date.today - 7)) # WHERE stamp >= '2010-06-30' AND stamp <= '2010-07-07'
値の配列も指定できます:
my_posts = posts.where(:category => ['ruby', 'postgres', 'linux']) # WHERE category IN ('ruby', 'postgres', 'linux')
Sequel は式も受け付けます:
my_posts = posts.where{stamp > Date.today << 1} # WHERE stamp > '2010-06-14'
アダプタによっては正規表現も指定することができます:
my_posts = posts.where(:category => /ruby/i) # WHERE category ~* 'ruby'
exclude でフィルタを反転できます:
my_posts = posts.exclude(:category => ['ruby', 'postgres', 'linux']) # WHERE category NOT IN ('ruby', 'postgres', 'linux')
文字列でカスタム WHERE 節も指定できます:
posts.where('stamp IS NOT NULL') # WHERE stamp IS NOT NULL
文字列でパラメータを使用できます:
author_name = 'JKR' posts.where('(stamp < ?) AND (author != ?)', Date.today - 3, author_name) # WHERE (stamp < '2010-07-11') AND (author != 'JKR')
データセットはサブクエリとして使用することもできます:
DB[:items].where('price > ?', DB[:items].select{avg(price) + 100}) # WHERE price > (SELECT avg(price) + 100 FROM items)
フィルタ後、任意の取り出しメソッドを使用して適合したレコードを取り出すことができます:
my_posts.each{|row| p row}
詳細は Dataset Filtering を見てください。
セキュリティを念頭においてアプリケーションを設計することはベストプラクティスです。Sequel 使用時に注意すべきセキュリティ事項の詳細については Security Guide を読んでください。
count を使用して簡単にレコードを数えることができます:
posts.where(:category.like('%ruby%')).count # SELECT COUNT(*) FROM posts WHERE category LIKE '%ruby%'
最大/最小値は max と min で問い合わせできます:
max = DB[:history].max(:value) # SELECT max(value) FROM history min = DB[:history].min(:value) # SELECT min(value) FROM history
合計や平均も sum と avg で計算できます:
sum = DB[:items].sum(:price) # SELECT sum(price) FROM items avg = DB[:items].avg(:price) # SELECT avg(price) FROM items
データセットを並び替えるには単純に order を使用します:
posts.order(:stamp) # ORDER BY stamp posts.order(:stamp, :name) # ORDER BY stamp, name
order の連結は where と同じようには働きません:
posts.order(:stamp).order(:name) # ORDER BY name
order_append メソッドを連結してこれを行えます:
posts.order(:stamp).order_append(:name) # ORDER BY stamp, name
order_prepend メソッドも使用できます:
posts.order(:stamp).order_prepend(:name) # ORDER BY name, stamp
降順を指定することもできます:
posts.reverse_order(:stamp) # ORDER BY stamp DESC posts.order(Sequel.desc(:stamp)) # ORDER BY stamp DESC
上の例の Sequel.desc(:stamp) の使用に着目してください。Sequel の DSL の多くはこのスタイル(SQL 式オブジェクトを返す Sequel モジュールのメソッド呼び出し)を使用します。Sequel は core_extensions extension) も提供します。これは Sequel の DSL をよりよく Ruby 言語に統合します。次のように書けます:
:stamp.desc
次の代わりに:
Sequel.desc(:stamp)
select を使用して簡単に返すカラムを選択できます:
posts.select(:stamp) # SELECT stamp FROM posts posts.select(:stamp, :name) # SELECT stamp, name FROM posts
select の連結は where ではなく order のように働きます:
posts.select(:stamp).select(:name) # SELECT name FROM posts
あなたの予想通り、order_append と同様の select は select_append です:
posts.select(:stamp).select_append(:name) # SELECT stamp, name FROM posts
テーブルからのレコードの削除は delete で行います:
posts.where('stamp < ?', Date.today - 3).delete # DELETE FROM posts WHERE stamp < '2010-07-11'
削除はとても注意してください。delete はデータセット内の全ての行に影響します。まず where それから delete を呼んでください:
# DO THIS: posts.where('stamp < ?', Date.today - 7).delete # NOT THIS: posts.delete.where('stamp < ?', Date.today - 7)
テーブルへのレコードの挿入は insert で行います:
posts.insert(:category => 'ruby', :author => 'david') # INSERT INTO posts (category, author) VALUES ('ruby', 'david')
テーブル内のレコードの更新は update で行います:
posts.where('stamp < ?', Date.today - 7).update(:state => 'archived') # UPDATE posts SET state = 'archived' WHERE stamp < '2010-07-07'
設定する値の選択にテーブルのカラムを参照できます:
posts.where{|o| o.stamp < Date.today - 7}.update(:backup_number => Sequel.+(:backup_number, 1)) # UPDATE posts SET backup_number = backup_number + 1 WHERE stamp < '2010-07-07'
delete と同様に update はデータセットの全ての行に影響するため、まず where そして update を行なってください:
# DO THIS: posts.where('stamp < ?', Date.today - 7).update(:state => 'archived') # NOT THIS: posts.update(:state => 'archived').where('stamp < ?', Date.today - 7)
Database#transaction メソッドを使用してデータベーストランザクションでコードをラップすることができます:
DB.transaction do posts.insert(:category => 'ruby', :author => 'david') posts.where('stamp < ?', Date.today - 7).update(:state => 'archived') end
ブロックが例外を上げなければトランザクションはコミットされます。ブロックが例外を上げた場合はトランザクションはロールバックされ、例外は再送出されます。トランザクションをロールバックしたいけどブロックの外に例外を上げたくない場合は、ブロック内で Sequel::Rollback 例外を上げればよいです:
DB.transaction do posts.insert(:category => 'ruby', :author => 'david') if posts.filter('stamp < ?', Date.today - 7).update(:state => 'archived') == 0 raise Sequel::Rollback end end
Sequel はテーブルを簡単に結合できます:
order_items = DB[:items].join(:order_items, :item_id => :id). where(:order_id => 1234) # SELECT * FROM items INNER JOIN order_items # ON order_items.item_id = items.id # WHERE order_id = 1234
ここで重要なことは、item_id は結合されるテーブルで自動的に修飾され、id は結合される最後のテーブルで自動的に修飾されることです。
それからデータセットと同様のことができます:
order_total = order_items.sum(:price) # SELECT sum(price) FROM items INNER JOIN order_items # ON order_items.item_id = items.id # WHERE order_items.order_id = 1234
Sequel はカラム名がシンボルで記述されることを期待します。さらに、戻り値のハッシュのキーは常にシンボルです。これは多くの場合、リテラル値とカラム参照を自由に混ぜられます。たとえば、次の2行は同等の SQL を提供します:
items.where(:x => 1) # SELECT * FROM items WHERE (x = 1) items.where(1 => :x) # SELECT * FROM items WHERE (1 = x)"
Ruby 文字列は通常 SQL 文字列として扱われます:
items.where(:x => 'x') # SELECT * FROM items WHERE (x = 'x')
SQL の識別子はカラム、テーブル、スキーマの名前です。識別子は二重のアンダースコアを使用した特殊な表記法 :table__column で修飾できます:
items.literal(:items__price) # items.price
カラムを修飾する他の方法は、Sequel.qualify メソッドを使用することです:
items.literal(Sequel.qualify(:items, :price)) # items.price
テーブル識別子でカラム識別子を修飾するのをさらに一般的にして、修飾されたテーブルから SELECT するために、スキーマ識別子でテーブル識別子を修飾できます:
posts = DB[:some_schema__posts] # SELECT * FROM some_schema.posts
三重アンダースコアを使用した特殊な表記法 :column___alias または :table__column___alias で識別子をエイリアスできます:
items.literal(:price___p) # price AS p items.literal(:items__price___p) # items.price AS p
カラムをエイリアスする別の方法は、Sequel.as メソッドを使用することです:
items.literal(Sequel.as(:price, :p)) # price AS p
Sequel.as メソッドを使用すると、識別子と異なり、任意の式をエイリアスできます:
items.literal(Sequel.as(DB[:posts].select{max(id)}, :p)) # (SELECT max(id) FROM posts) AS p
モデルクラスはデータセットをラップし、クラスのインスタンスはデータセットの1レコードをラップします。
モデルクラスは Sequel::Model を継承した通常の Ruby クラスとして定義されています:
DB = Sequel.connect('sqlite://blog.db') class Post < Sequel::Model end
モデルクラスが生成される時、データベースからテーブルのスキーマをパースし、自動的にテーブル内のすべてのカラムにアクセスできるメソッドをセットアップします(Sequel::Model はアクティブレコードパターンを実装します)。
Sequel モデルクラスは、クラス名をアンダースコアした複数形がテーブル名であるとみなします:
Post.table_name #=> :posts
テーブル名を明示的に設定できます(使用しているデータセットでも):
class Post < Sequel::Model(:my_posts) end # or: Post.set_dataset :my_posts
シンボルを引数として set_dataset を呼び出すと、同じ名前のテーブルを参照しているとみなします。データセットを引数として呼び出すと、そのモデルに対するすべての検索のデフォルトとして設定できます:
Post.set_dataset DB[:my_posts].where(:category => 'ruby') Post.set_dataset DB[:my_posts].select(:id, :name).order(:date)
モデルインスタンスはプライマリキーによって識別されます。多くの場合、Sequel はプライマリキーを決定するためにデータベースに問い合わせしますが、できない場合は :id をデフォルトとして使用します。Model.[] メソッドでプライマリキーを指定してレコードを取り出すことができます:
post = Post[123]
pk メソッドでレコードのプライマリキー値を取り出すことができます:
post.pk #=> 123
Sequel モデルはプライマリキーとして任意のカラムを使用できます。複数カラムから複合キーも作成できます:
class Post < Sequel::Model set_primary_key [:category, :title] end post = Post['ruby', 'hello world'] post.pk #=> ['ruby', 'hello world']
no_primary_key でプライマリキーを持たないモデルクラスも定義できます。しかし、簡単にレコードを更新したり削除する機能は失われます:
Post.no_primary_key
モデルインスタンスは条件を指定して取り出すこともできます:
post = Post[:title => 'hello world'] post = Post.first{num_comments < 10}
モデルクラスは多くのメソッドを下位のデータセットに転送します。これはほとんどの Dataset API を使用して、モデルインスタンスを返すカスタマイズされたクエリを生成できることを意味します。例えば:
Post.where(:category => 'ruby').each{|post| p post}
データセット内のレコードの操作もできます:
Post.where{num_comments < 7}.delete Post.where(Sequel.like(:title, /ruby/)).update(:category => 'ruby')
モデルインスタンスはその値をカラムシンボルキーのハッシュとして格納します。ハッシュには values メソッドで直接アクセスできます:
post.values #=> {:id => 123, :category => 'ruby', :title => 'hello world'}
レコード値はオブジェクトの属性として読み込むことができます。属性名は、モデルのデータセット内の正当なカラム名とみなされます:
post.id #=> 123 post.title #=> 'hello world'
レコードの属性名がモデルのデータセット内の正当なカラムでない場合(計算された値のカラムを select_append で追加した場合等)、Model#[] で値にアクセスできます:
post[:id] #=> 123 post[:title] #=> 'hello world'
属性セッターまたは []= メソッドを使用してレコードの値を変更することもできます:
post.title = 'hey there' post[:title] = 'hey there'
それはオブジェクトの値を変更するだけで、データベース内の行の更新は行いません。データベース行を更新するには、save メソッドを呼び出します:
post.save
mass-assignment メソッドのひとつを利用して、ひとつのメソッド呼び出しで複数カラムに値を設定することも可能です。詳細は mass assignment guide を見てください。たとえば、set はモデルのカラム値を保存せずに更新します:
post.set(:title=>'hey there', :updated_by=>'foo')
そして update はモデルのカラム値を更新し、それからデータベースに変更を保存します:
post.update(:title => 'hey there', :updated_by=>'foo')
新しいレコードは Model.create を呼び出すことで生成できます:
post = Post.create(:title => 'hello world')
他の方法は、新しいインスタンスを構成してあとで保存することです:
post = Post.new post.title = 'hello world' post.save
Model.new と Model.create にブロックを与えることもできます:
post = Post.new do |p| p.title = 'hello world' end post = Post.create{|p| p.title = 'hello world'}
レコードの生成、更新、削除時にフックメソッドでカスタムコードを実行することができます。before_create と after_create フックメソッドはレコード生成をラップします。before_update と after_update フックメソッドはレコード更新をラップします。before_save と after_save フックメソッドはレコードの生成と更新をラップします。before_destroy と after_destroy フックメソッドは破壊をラップします。before_validation と after_validation フックメソッドはバリデーションをラップします。例:
class Post < Sequel::Model def after_create super author.increase_post_count end def after_destroy super author.decrease_post_count end end
自前のフックメソッドを定義する場合は、super の使用に注意してください。ほとんど全ての Sequel::Model クラスメソッドとインスタンスメソッド(フックメソッド以外)は、安全に上書きできますが、それを行う際に super を確実に呼び出す必要があります。そうしないと、何かを壊すリスクがあります。
上の例では、可能であればデータベースのトリガーを使用すべきです。フックはデータの完全性のために使用できますが、モデルインスタンスを通じてデータベースを変更する時にその完全性を強制するだけです。それらはしばしば競合状態の影響を受けます。データベースのトリガーと制約を使って、データの完全性を強制するのがベストです。
delete または destroy を呼び出して個々のレコードを削除できます。この2つのメソッドの違いは、destroy が before_destroy と after_destroy フックメソッドを呼び出すことだけです。delete は行いません:
post.delete # => bypasses hooks post.destroy # => runs hooks
レコードはモデルのデータセットで delete と destroy を呼び出すことで大量に削除することもできます。上述したように削除されるレコード用にフィルタを記述することもできます:
Post.where(:category => 32).delete # => bypasses hooks Post.where(:category => 32).destroy # => runs hooks
destroy が呼び出された場合は各レコードが別々に削除されることに注意してください。delete はすべての適合したレコードを一つの SQL クエリで削除します。
関連はモデルクラス間の関係を指定するために使用されます。これはデータベース内のテーブル間の関係を反映します。通常は外部キーを使用して指定されます。many_to_one, one_to_one, one_to_many, many_to_many クラスメソッドでモデルの関連を指定します:
class Post < Sequel::Model many_to_one :author one_to_many :comments many_to_many :tags end
many_to_one と one_to_one はモデルオブジェクトにゲッターとセッターを生成します:
post = Post.create(:name => 'hi!') post.author = Author[:name => 'Sharon'] post.author
one_to_many と many_to_many はゲッターメソッドと、関連にオブジェクトを追加するメソッドと、関連からオブジェクトを削除するメソッドと、関連からすべての関連オブジェクトを削除するメソッドを生成します:
post = Post.create(:name => 'hi!') post.comments comment = Comment.create(:text=>'hi') post.add_comment(comment) post.remove_comment(comment) post.remove_all_comments tag = Tag.create(:tag=>'interesting') post.add_tag(tag) post.remove_tag(tag) post.remove_all_tags
remove_* と remove_all_* メソッドはデータベースからオブジェクトを削除しないことに注意してください。これらはレシーバーから関連オブジェクトの関連を外すだけです。
すべての関連は dataset メソッドを追加します。これらは返されるオブジェクトのフィルタまたは並び替え、またはそれらすべてを変更するのに使用されます:
# この post のすべてのコメントをデータベースから削除します post.comments_dataset.destroy # この post に関連したすべてのタグで subscribers がないものを、タグの名前の順に返します post.tags_dataset.where(:subscribers=>0).order(:name).all
関連は eager と :eager 関連オプションで eagerly にロードされます。Eager ローディングはオブジェクトのグループをロードする時に使用されます。これはすべてのカレントオブジェクトのすべての関連オブジェクトを1クエリでロードします。各カレントオブジェクトの関連オブジェクトを得るために別々のクエリを使用するのではありません。Eager ローディングは all ですべてのモデルオブジェクトを一度に取り出します(each で個々にするのではなく)。Eager ローディングはカスケードして関連の関連オブジェクトをロードできます。
class Person < Sequel::Model one_to_many :posts, :eager=>[:tags] end class Post < Sequel::Model many_to_one :person one_to_many :replies many_to_many :tags end class Tag < Sequel::Model many_to_many :posts many_to_many :replies end class Reply < Sequel::Model many_to_one :person many_to_one :post many_to_many :tags end # .eager で Eager ローディングします Post.eager(:person).all # eager はデータセットメソッドなので、フィルタ/並び替え/件数制限等でも働きます Post.where{topic > 'M'}.order(:date).limit(5).eager(:person).all person = Person.first # :eager で Eager ローディングします(この person の post から tag を eagerly ロードします) person.posts # この2つは同等です Post.eager(:person, :tags).all Post.eager(:person).eager(:tags).all # .eager でカスケードしています Tag.eager(:posts=>:replies).all # すべての関連 post のタグを全取得します (:eager があるので) Reply.eager(:person=>:posts).all # 深さの制限なしで(メモリ/スタック以外には) post の tag を全取得します。 # すべての people、それらの post、それらの post の tag、それらの post への reply、 # 各 reply の person、各 reply の tag、それらの tag を持つすべての post と reply をロードします。 # 合計 8 クエリを使用します。 Person.eager(:posts=>{:replies=>[:person, {:tags=>[:posts, :replies]}]}).all
eager に加え eager_graph も使用できます。これはオブジェクトとすべての関連オブジェクトを得るために1クエリを使用します。これは関連テーブルのカラムを元にフィルタまたは並び替えをしたい場合に必要となります。これはカスケーディングでも働き、API はとても似ています。複数の *_to_many 関連を eagerly ロードするために eager_graph を使用すると、結果セットが cartesian product になることに注意してください。そのためこのケースでそれを使用する時はフィルタに充分注意するべきです。
proc を使用して eagerly ロードされたデータセットを動的にカスタマイズすることもできます。この proc は eager ローディングのために使用されるデータセットに渡され、データセットの変更された複製を返すべきです:
# Eagerly load only replies containing 'foo' Post.eager(:replies=>proc{|ds| ds.where(Sequel.like(text, '%foo%'))}).all
これは eager_graph 使用時にも働きます。この場合 proc はデータセットを引数として、現在のデータセットに graph するために呼び出されます:
Post.eager_graph(:replies=>proc{|ds| ds.where(Sequel.like(text, '%foo%'))}).all
カスケード中でも eager と eager_graph 用に eager ロードを動的にカスタマイズできます。この場合は、proc をキー、カスケードされた関連を値とした1つのエントリのハッシュを値とします:
# 'foo' を含んでいる reply と、これらの reply の person と tag だけを Eagerly ロードします Post.eager(:replies=>{proc{|ds| ds.where(Sequel.like(text, '%foo%'))}=>[:person, :tags]}).all
テーブル共通のロジックを追加する推奨方法は、データセットで dataset_module を使用してメソッドを定義することです:
class Post < Sequel::Model dataset_module do def posts_with_few_comments where{num_comments < 30} end def clean_posts_with_few_comments posts_with_few_comments.delete end end end
これはフィルタされたデータセットからあなたのモデル API にアクセスすることができます:
Post.where(:category => 'ruby').clean_posts_with_few_comments
Sequel モデルは subset クラスメソッドも提供します。これは簡単なフィルタを引数としたデータセットメソッドを生成します:
class Post < Sequel::Model subset(:posts_with_few_comments){num_comments < 30} subset :invisible, Sequel.~(:visible) end
あなたのモデルに validate メソッドを定義することで、データベースにモデルを保存しようとする前に save がチェックします。モデルの属性が正当でない場合は、モデルオブジェクトの errors にその属性のエラーメッセージを追加すべきです。オブジェクトが validate メソッドによって追加された何かのエラーを持っている場合、save はエラーを発生するか、false を返します。この動きは raise_on_save_failure フラグで設定できます。
class Post < Sequel::Model def validate super errors.add(:name, "can't be empty") if name.empty? errors.add(:written_on, "should be in the past") if written_on >= Time.now end end