もりきゅうです。
今回は Ruby on Rails (以下 RoR) を構成するライブラリ群のうち ActiveRecord について掘り下げていきます。
ActiveRecord (以下 AR) は RLR 第 4 回 に取り上げられたように、Ruby での O/R マッピングライブラリのひとつです。 AR を使えば、簡単かつ効率的にリレーショナルデータベース上の情報を Ruby オブジェクトとして扱うことができます。その理論的なところは P of EAA: Active Record を見ていただくことにして、ここではその実際的な使い方を見ていきます。
さて、RoR は非常に早く進化してきましたが、コアはそれほど変わっていません。 AR も使い方はそうそう変わらないだろうと思いますので、一通りその機能を見ていきたいと思います。
今回は、チュートリアルではなく主にリファレンスとして、AR に関する情報を日本語で提供できるように書いてみました。
はじめに、RoR と切り離して AR を使うための簡単なサンプルを紹介します。 次に、AR のソース構造を概観し、AR の test を読んでいきます。
後半はマニュアルの翻訳を試みました。 関連のうち、特に has_many と belongs_to について、生成されるメソッドを概観します。 そして callback, validation といったよく使う機能を見ていきます。 最後に、AR クラス・オブジェクトのリファレンスを載せました。
なお、今回は activerecord-1.10.1 を対象としています。
AR オブジェクトの基本的な扱い方を簡単におさらいしておきましょう。
RoR は全て含めると非常に大きいので、なかなか把握しづらいかもしれません。 そこで、今回は AR を単体で使ってみることにします。 AR を RoR から独立したひとつのライブラリとして扱います。
ここでは RDBMS として MySQL を使うことにします。
rubima という database に rails という user で接続するとします。
CREATE DATABASE rubima;
grant all on rubima.* to rails@localhost;
users table を作ります。
use rubima;
CREATE TABLE users (
id int unsigned not null auto_increment,
name varchar(50),
occupation varchar(50),
primary key (id)
);
DB の準備ができました。AR で接続してみましょう。
# gem を使わない場合
# require 'active_record'
# gem を使う場合
require 'rubygems'
require_gem 'activerecord'
ActiveRecord::Base.establish_connection(
:adapter => 'mysql',
:host => 'localhost',
:username => 'rails',
:password => '',
:database => 'rubima'
)
class User < ActiveRecord::Base
end
AR オブジェクトを作るには new メソッドを使います。
create メソッドを使うこともできます。create は new したあと save (DB に格納) します。
引数として、カラム名と値をハッシュにして与えます。
user = User.new(:name => "David", :occupation => "Code Artist")
p user.name # => "David"
また、ブロックを使うこともできます。
user = User.new do |u|
u.name = "David"
u.occupation = "Code Artist"
end
もちろん、作ってから値を設定してもいいです。
user = User.new
user.name = "David"
user.occupation = "Code Artist"
なお、new で作った AR オブジェクトは
user.save
のように save するまでは DB に格納されません。
検索・抽出を行うには find メソッドを使います。
find のほか find_first, find_all, find_by_sql といったメソッドがあります。
base.rb から例を引用しておきます。 オプションの意味を理解するには若干 SQL の知識が必要です。 詳細はマニュアルを読んでください。
id で検索する例:
Person.find(1) # returns the object for ID = 1
Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
Person.find([1]) # returns an array for objects the object with ID = 1
Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
find first の例:
Person.find(:first) # returns the first object fetched by SELECT * FROM people
Person.find(:first, :conditions => [ "user_name = ?", user_name])
Person.find(:first, :order => "created_on DESC", :offset => 5)
find all の例:
Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
Person.find(:all, :offset => 10, :limit => 10)
Person.find(:all, :include => [ :account, :friends ])
クエリに変数を適用するときには、直接 #{} で文字列の中に埋め込むのではなく、パラメータとして渡したほうが安全です。 例えば (base.rb 参照) user_name と password 変数を埋め込むとします。
User < ActiveRecord::Base
def self.authenticate_unsafely(user_name, password)
find_first("user_name = '#{user_name}' AND password = '#{password}'")
end
def self.authenticate_safely(user_name, password)
find_first([ "user_name = ? AND password = ?", user_name, password ])
end
end
authenticate_unsafely のように書いたとき、user_name や password にエスケープされていない引用符を含めることができれば、任意のSQLを実行させることができてしまいます。 このようなときは authenticate_safely のように ? を用いて値を渡しましょう。 ? に渡った値はサニタイズされます (文字列ならエスケープされて引用符で囲まれます)。
? がたくさん付くようになると、パラメータの順序を覚えるのがうっとおしくなります。 このようなときは ? の代わりに :名前 を使ってパラメータに名前を付けると良いでしょう。 このときは対応する値をハッシュで与えます。
Company.find(:first, [
"id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
{ :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
])
AR を理解する第一歩として、まず AR の構成を知るところから始めましょう。
まず、ディレクトリ構造を見てみましょう。
ソースツリーは次のようになっています。
activerecord-1.10.1/
examples/
lib/
test/
lib/ を見てください。
lib/
active_record/
acts/
associations/
connection_adapters/
vendor/
wrappers/
acts/ は list, tree といったデータ構造を AR で実現するための拡張です。 詳細はマニュアルをご覧ください。
associations/ は lib/associations.rb が読み込みます。 ここでは外部キーに基づく関連 (relationship) を実装しています。 belongs_to, has_one, has_many, has_and_belongs_to_many といった関連の定義はここにあります。
connection_adapters/ では DB アダプタを実装しています。
vendor/ には RoR の外部から提供されているライブラリが置かれています。
wrappers/ にはカラム値の変換プラグインが置かれています。今のところ YAML だけです。
さて、require ‘active_record’ あるいは require_gem ‘activerecord’ したときに読み込まれるのは lib/active_record.rb です。このファイルを見てみましょう。 すると、fixtures.rb 以外のファイルはすべてここで require され、各 module が ActiveRecord::Base.class_eval の中で include されていることが分かります。
そして、各 module には次のような仕掛けがあります。 例えば validations.rb:
module Validations
...
def self.append_features(base) # :nodoc:
super
base.extend ClassMethods
base.class_eval do
alias_method :save_without_validation, :save
alias_method :save, :save_with_validation
alias_method :update_attribute_without_validation_skipping, :update_attribute
alias_method :update_attribute, :update_attribute_with_validation_skipping
end
end
...
module ClassMethods
self.append_features(base) は include 時に呼ばれるのでしたね。 そして module ClassMethods は base.extend されているので、ここで定義されたメソッドは base (= ActiveRecord::Base) のクラスメソッドとして扱われます。ライブラリを分割する手法として非常に参考になります。
また、validations.rb では alias_method を使ってメソッドを置き換えています。
結局、module の多くは ActiveRecord::Base クラスに対する定義になっています。
AR を単体として見るために、AR 自身の test を取り上げます。
AR には RDoc で生成された良くできたマニュアルが付いていますが、それでもソースを読む価値は常にあります。 AR はテスト駆動で作られているため、test に AR の仕様が具体的にコードの形で表われます。 test を読むと、仕様の微妙な箇所や実装の妥協点がよく分かります。
test を読むとっかかりとして、本稿では connection.rb と Topic, Reply クラスだけ解説します。
cd test; ruby -I "connections/native_mysql" base_test.rb
-I “connections/native_mysql” は $: の設定であり、require ‘connection’ が読み込む connection.rb のパスを指定しています。
ここでは MySQL アダプタを用いる例を見ていきますが、どのアダプタでも基本は同じです。
print "Using native MySQL\n"
require 'fixtures/course'
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
db1 = 'activerecord_unittest'
db2 = 'activerecord_unittest2'
ActiveRecord::Base.establish_connection(
:adapter => "mysql",
:host => "localhost",
:username => "rails",
:password => "",
:database => db1
)
Course.establish_connection(
:adapter => "mysql",
:host => "localhost",
:username => "rails",
:password => "",
:database => db2
)
ここで require ‘fixtures/course’; db2 = ‘activerecord_unittest2’; Course.establish_connection といった部分は、複数のDBを関連付けて扱う multiple_db_test.rb で使われます。この機能については今回取り上げません。詳細は multiple_db_test.rb を読んでください。
なので、今回扱う範囲では、connection.rb は次のもので十分です。
print "Using native MySQL\n"
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
db1 = 'activerecord_unittest'
ActiveRecord::Base.establish_connection(
:adapter => "mysql",
:host => "localhost",
:username => "rails",
:password => "",
:database => db1
)
ここで logger を除いてみると、ここで行っているのは ActiveRecord::Base.establish_connection の呼び出しのみということになります。 establish_connection は引数を見て分かるとおり、DBに接続して、その接続を維持します。これ以降、この接続は ActiveRecord::Base.connection で参照できます。
次に base_test.rb を見ると、頭に require ‘abstract_unit’ があります。
$:.unshift(File.dirname(__FILE__) + '/../lib')
$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib')
require 'test/unit'
require 'active_record'
require 'active_record/fixtures'
require 'active_support/binding_of_caller'
require 'active_support/breakpoint'
require 'connection'
class Test::Unit::TestCase #:nodoc:
def create_fixtures(*table_names)
if block_given?
Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names) { yield }
else
Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names)
end
end
end
Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
ここではライブラリの require を行っています。require ‘connection’ もここにあります。
require 'test/unit'
これはみなさんお馴染みの Test::Unit ライブラリです。
require 'active_record'
require 'active_record/fixtures'
ここで AR ライブラリを読み込んでいます。 fixtures だけ別になっています。fixtures は test のみで使われるからです。
require 'active_support/binding_of_caller'
require 'active_support/breakpoint'
これはデバッグ用のライブラリです。 binding_of_caller, breakpoint の詳細は複雑かつ興味深いものですが、今回は避けます。
require 'connection'
ここで先の connection.rb を読み込みます。
class Test::Unit::TestCase #:nodoc:
def create_fixtures(*table_names)
create_fixtures を定義していますが、現在は create_fixtures よりも fixtures を使うのが普通です。
Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
fixtures のパスはこのように指定します。
ここまで特に難しくはないですね。
base_test.rb に戻ります。
require 'fixtures/topic'
require 'fixtures/reply'
class Topic < ActiveRecord::Base
has_many :replies, :dependent => true, :foreign_key => "parent_id"
serialize :content
before_create :default_written_on
before_destroy :destroy_children
def parent
self.class.find(parent_id)
end
protected
def default_written_on
self.written_on = Time.now unless attribute_present?("written_on")
end
def destroy_children
self.class.delete_all "parent_id = #{id}"
end
end
これも、もうみなさんは見慣れたであろう ActiveRecord::Base から継承した AR クラスの定義です。 普段は、script/generator model で作っておくファイルですね。
この Topic クラスでは、いくつかARの機能が見て取れます。
has_many :replies, :dependent => true, :foreign_key => "parent_id"
has_many は1対多の関連付けに用います。
普通はふたつのテーブルを使いますが、Topic は Reply と共に Single table inheritance を構成します。
親 (Topic) を削除するときは子 (Reply) も削除するという指定です。
外部キーを parent_id にします。:foreign_key を省略したときは、外部キーの名前は相手のテーブル名から作られます。 逆に言うと、キー名を自由に決めていいときは、AR の仕様に合わせて名前を決めたほうがいいでしょう。
serialize :content
content を YAML 形式で格納します。
before_create :default_written_on
before_destroy :destroy_children
コールバックです。create, destroy 時に実行するメソッドを指定しています。
def parent
これは Topic から継承する Reply クラスで使うことを想定しているものと考えられます。 Reply ではない Topic では parent_id は nil のはずですから、find は常に失敗します。
ここで Topic に対応するDBスキーマを見ておきましょう。 MySQL のものは以下にあります。
CREATE TABLE `topics` (
`id` int(11) NOT NULL auto_increment,
`title` varchar(255) default NULL,
`author_name` varchar(255) default NULL,
`author_email_address` varchar(255) default NULL,
`written_on` datetime default NULL,
`bonus_time` time default NULL,
`last_read` date default NULL,
`content` text,
`approved` tinyint(1) default 1,
`replies_count` int(11) default 0,
`parent_id` int(11) default NULL,
`type` varchar(50) default NULL,
PRIMARY KEY (`id`)
) TYPE=InnoDB;
注目したいのは replies_count, parent_id, type カラムです。これらは Reply との関係で使われます。 type カラムはクラス名を格納します。これは Single table inheritance の肝です。 id カラムは auto_increment な primary key です。 そのほかのカラムは単純な値の入れ物です。
topic.rb で何度も Reply について言及しましたので、reply.rb も合わせて見ておきましょう。
class Reply < Topic
belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true
has_many :silly_replies, :dependent => true, :foreign_key => "parent_id"
validate :errors_on_empty_content
validate_on_create :title_is_wrong_create
attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read
def validate
errors.add("title", "Empty") unless attribute_present? "title"
end
def errors_on_empty_content
errors.add("content", "Empty") unless attribute_present? "content"
end
def validate_on_create
if attribute_present?("title") && attribute_present?("content") && content == "Mismatch"
errors.add("title", "is Content Mismatch")
end
end
def title_is_wrong_create
errors.add("title", "is Wrong Create") if attribute_present?("title") && title == "Wrong Create"
end
def validate_on_update
errors.add("title", "is Wrong Update") if attribute_present?("title") && title == "Wrong Update"
end
end
class SillyReply < Reply
end
belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true
belongs_to は関連するふたつのテーブルのうち association id を持つテーブル側に指定する関連付けでしたね。
topics table は parent_id を association id として、ひとつのテーブルで継承関係を作ります。
この指定により replies_count カラムを自動的に更新します。
validate :errors_on_empty_content
validate_on_create :title_is_wrong_create
def validate
def validate_on_create
def validate_on_update
これらは validation と呼ばれる機能です。値のチェックを行います。
ActiveRecord::Base#attribute_present?(カラム名) は、カラムが存在してかつ nil? でも empty? でもない、ようするに、値を持っていることを確認します。
attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read
attr_accessible は、アクセスできるカラム名を (緩やかに) 制限します。attr_protected と対になります。
has_many :silly_replies, :dependent => true, :foreign_key => "parent_id"
class SillyReply < Reply
end
Reply からさらに継承しています。Topic から見ると二段階の has_many になります。
参考: 特に一般化したリスト構造やツリー構造を扱う際には acts mixin が使えます。
base_test.rb ではほかにも多くのテーブルを扱っていますが、とりあえず Topic と Reply で留めておきます。 base_test.rb の test を読むと、AR の仕様が見えてくると思います。
以降は、関連、コールバック、validation について、マニュアルを読みつつ見ていきます。
Topic, Reply を元に、AR の関連 (relationship) についてまとめてみます。
Topic, Reply は AR クラスです。よって new, find など AR クラスのメソッドは全て使えます。
さらに has_many, belongs_to の指定によってメソッドが追加されます。
Topic クラスで has_many :replies を宣言すると、次のメソッドが追加されます。
関連付けられた reply の配列を返します。 もし reply がなければ空配列を返します。
Reply を使えば
Reply.find :all, :conditions => "parent_id = #{topic.id}"
と書けます。
Topic#replies の結果はキャッシュされます。 同じ topic で再度 topic.replies を呼び出したときはキャッシュされた値が返ります。 force_reload = true にすると、リロードします (キャッシュを削除して DB から読み直します)。
指定された reply を追加します。
DB 上は外部キーに topic.id を設定します。
指定された reply を取り除きます。
DB 上は外部キーに NULL を設定します。
もし :dependent => true であれば reply は destroy されます。
全ての reply を取り除きます。ただし reply は destroy されません。
reply がなければ真を返します。
これは
Topic#replies.size == 0
と同等です。
reply の個数を返します。
Reply を使えば
Reply.count("parent_id = #{topic.id}")
と書けます。
counter_cache が有効であればキャッシュの値が使われます。
reply を検索・抽出します。 引数は Base.find と同じ規則です。
Reply を使えば
Reply.find(id, :conditions => "parent_id = #{topic.id}")
と書けます。
新たな reply オブジェクトを生成します。 引数は Base.new と同じ規則です。
new と同様、すぐに save はされません。
Reply を使えば
Reply.new("parent_id" => topic.id)
と書けます。
新たな reply オブジェクトを生成し、save します (validation チェックは行います)。
Reply を使えば
reply = Reply.new("parent_id" => topic.id)
reply.save
reply
あるいは
Reply.create("parent_id" => topic.id)
と書けます。
has_many はオプションのハッシュをとることができます。
has_many :replies, :dependent => true, :foreign_key => "parent_id"
全て書く余白がないので ;)、オプションについては割愛します。
Reply クラスで belongs_to :topic を宣言すると、次のメソッドが追加されます。
関連付けられた topic を返します。なければ nil を返します。
topic を関連付けます。DB 上は外部キーに topic.id を設定します。
新たな topic を生成し、そして、この reply を関連付けます。 save しません。
新たな topic を生成し、そして、この reply を関連付けます。 save します。
belongs_to はオプションのハッシュをとることができます。
belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true
詳しくはマニュアルをご覧ください。
(callbacks.rb の部分的な翻訳です)
(new_record な) AR オブジェクトを save するときに呼ばれるコールバックは次の通りです。
9 つのコールバックがありますね。 コールバックは、Active Record ライフサイクルのそれぞれの状態に反応し準備するための、とても大きな力となります。
コールバックの例:
class CreditCard < ActiveRecord::Base
# Strip everything but digits, so the user can specify "555 234 34" or
# "5552-3434" or both will mean "55523434"
def before_validation_on_create
self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
end
end
class Subscription < ActiveRecord::Base
before_create :record_signup
private
def record_signup
self.signed_up_on = Date.today
end
end
class Firm < ActiveRecord::Base
# Destroys the associated clients and people when the firm is destroyed
before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
end
上書き可能なコールバックメソッドに加えて、コールバックマクロでコールバックを登録できます。 その主な利点は、マクロは継承階層の影響を受けずにコールバックキューに振る舞いを追加できることです。
例えば:
class Topic < ActiveRecord::Base
before_destroy :destroy_author
end
class Reply < Topic
before_destroy :destroy_readers
end
さて、 Topic#destroy を実行すると、destroy_author だけ呼ばれます。 Reply#destroy を実行すると、destroy_author と destroy_readers が呼ばれます。
同じ振る舞いを、上書き可能なメソッドで実装した場合と比較してみましょう。
class Topic < ActiveRecord::Base
def before_destroy() destroy_author end
end
class Reply < Topic
def before_destroy() destroy_readers end
end
こうすると、Reply#destroy は destroy_readers だけ実行し、destroy_author は実行しません。
なので、 あるコールバックが全階層で呼ばれることを保証したいときには、コールバックマクロを使い、 それぞれの派生先で留めたいときは、上書きメソッドを使えばいいでしょう。 上書きメソッドは、super を呼んで派生元のコールバックを行うか決めることができます。
重要: 継承関係がコールバックキューに対して正しく働くためには、 関連を指定する前にコールバックを指定する必要があります。 そうしないと、コールバックを登録している親の前に子を load したときに、継承されません。
コールバックには 4 つのタイプがあります。
メソッドリファレンスとコールバックオブジェクトは、推奨されるアプローチです。 proc を使ったインラインメソッドは、ときには適しています (例えば mix-in するとき)。 インライン eval メソッドは推奨されません。
after_find と after_initialize は、パフォーマンスの制約から、(def after_find のように) 明確に定義されたときだけ実行されます。
bafore_* の戻り値を false にすると、それ以降のコールバック全てと、そのコールバックに関連付けられたアクションがキャンセルされます。 after_* の戻り値を false にすると、それ以降のコールバック全てがキャンセルされます。
コールバックは定義された順に呼ばれますが、モデル上にメソッドとして定義されたコールバックは最後に呼ばれます。
(validations.rb の部分的な翻訳です)
AR オブジェクトは Base#validate (あるいは validate_on_create validate_on_update) を上書きすることで validation を実装できます。 これらのメソッドは、オブジェクトの状態を検査でき、カラムがある値を持つこと (empty でない、範囲内にある、ある正規表現にマッチするといったようなこと) を保証できます。
validation の例:
class Person < ActiveRecord::Base
protected
def validate
errors.add_on_empty %w( first_name last_name )
errors.add("phone_number", "has invalid format") unless phone_number =~ /[0-9]*/
end
def validate_on_create # is only run the first time a new object is saved
unless valid_discount?(membership_discount)
errors.add("membership_discount", "has expired")
end
end
def validate_on_update
errors.add_to_base("No changes have occurred") if unchanged_attributes?
end
end
person = Person.new("first_name" => "David", "phone_number" => "what?")
person.save # => false (and doesn't do the save)
person.errors.empty? # => false
person.count # => 2
person.errors.on "last_name" # => "can't be empty"
person.errors.on "phone_number" # => "has invalid format"
person.each_full { |msg| puts msg } # => "Last name can't be empty\n" +
"Phone number has invalid format"
person.attributes = { "last_name" => "Heinemeier", "phone_number" => "555-555" }
person.save # => true (and person is now saved in the database)
errors オブジェクトは、それぞれの AR オブジェクトに対して自動的に生成されます。 より高度な validation については ActiveRecord::Validations::ClassMethods をご覧ください。
ActiveRecord は ActionPack から独立していますが、連携させて用いることが多いです。 ここでは ActionPack の ActiveRecord 対応機能をいくつか取り上げます。
ActionController の create, update action で AR オブジェクトを save するときに、値をチェックしたいことがあります。
値をチェックするには、モデルクラスに validation メソッドを定義して、 望ましくない値を持つ場合に errors.add(カラム名, メッセージ) でエラーを追加します。
def validate
errors.add("code", "重複しています") if ...
end
errors が存在すると (errors.empty? でないと) save に失敗することになります (ActiveRecord::Base#save が false を返します)。 ActionController 側では、普通、save に成功すれば redirect_to し、失敗すれば render で元のページを表示します。
if @customer.save
redirect_to :action => "show", :id => @customer.id
else
render_action "edit"
end
errors.on(カラム名) は対応するメッセージを返します。そのカラム名に対応するエラーがないときは nil を返します。 これを編集用テンプレートに埋め込むと、save に失敗した理由を表示することができます。
<tr>
<th>会員NO</th>
<td><%= text_field "customer", "code" %><div><%=h @customer.errors.on("code") %></div></td>
</tr>
また、text_field といった helper メソッドは、カラム名に対応するエラーが存在するときにはタグを <div class=”fieldWithErrors”>…</div> で囲みます。 これを利用すると、スタイルシートを使ってエラーの原因となった入力フォーム要素を目立たせることができます。
div.fieldWithErrors {
background-color: red;
}
RoR の session 機能は CGI::Session を用いていて、標準では PStore を用いてファイルとして格納しています。 この格納方法は取り替えることができ、そのひとつに ActiveRecordStore があります。 これは session を AR オブジェクトとして扱い、DB に格納します。
date_helper を使うとひとつのカラムに対して複数の値を渡すことになります。 この複数の値をひとつにまとめているのは ActionPack でしょうか。それとも ActiveRecord でしょうか。 これは ActiveRecord です。 ActiveRecord::Base#attributes= がそれを実現しています。
RoR は cattr_accessor という accessor 宣言を用意しています。 これはクラス変数用の attr_accessor です。 例えば、
module ActiveRecord
class Base
cattr_accessor :primary_key_prefix_type
とすれば、@@primary_key_prefix_type は
ActiveRecord::Base.primary_key_prefix_type
ActiveRecord::Base.primary_key_prefix_type=
で読み書きできるようになります。
ここでは特に名前の設定に関するクラス変数をまとめておきます。
全てのプライマリキーカラム名の頭に付加する prefix のタイプを指定します。
:table_name を指定すると、Product クラスはプライマリキーカラムとして “id” の代わりに “productid” を探します。
:table_name_with_underscore を指定すると、Product クラスはプライマリキーカラムとして “id” の代わりに “product_id” を探します。
これは全 AR クラスに共通の設定になります。
全てのテーブル名の頭に付加する prefix 文字列を指定します。
“basecamp_” を設定すると、テーブル名は “basecamp_projects”, “basecamp_people” などとなります。 これは共有される DB の名前空間を作るのに便利です。
デフォルトは空文字列です。
table_name_prefix と同様に働きますが、頭ではなく後ろに付加されます (“_basecamp” を設定すると “projects_basecamp”, “people_basecamp” となります)。
デフォルトは空文字列です。
テーブル名をクラス名の複数形にするかを指定します。
true なら、Product クラスのデフォルトテーブル名は products になります。false なら、product になります。
table/class 名前付けの規則の詳細については、table_name を見てください。
デフォルトは true です。
AR のクラスメソッドをまとめてみます。 ただし、ここでは base.rb で定義されているものだけ扱います。 すでに書いた new, create, find, find_* は除きます。
データ操作を行うメソッドは、おおまかにみて、SQL を直接扱うものと AR オブジェクトを通すものに区別できます。
destroy と delete は、DB から該当行を削除します。 削除という意味ではどちらも同じですが、実行の仕方に違いがあります。
destroy は find で得た AR オブジェクトを destroy します。このとき、AR のコールバックは全て働きます。 例えば、:dependent による子の削除は、コールバック (before_destroy) によって実現されているので、destroy したときに行われます。
delete は AR オブジェクトを生成せずに直接 SQL (delete from …) を発行します。 そのため、コールバックは働きません。:dependent による削除は行われません。
destroy_all, delete_all は、条件に合う行を削除します。 条件の書き方は find と同じです。
destroy_all, delete_all は destroy, delete と同じ関係にあります。
update は、複数のカラムを更新し、save します。
update は find(id) して得られる AR オブジェクトに対して update_attributes(attributes) を行います。 update_attributes(attributes) は self.attributes = attributes して save します。戻り値は save の戻り値です。
update_all は、直接 SQL を使った更新を行います。updates は SQL 文です。 delete_all と同様、直接 SQL (update …) を発行します。
count は、条件に合う行数を返します。 直接 SQL (select count(*) from …) を発行します。joins は追加 SQL 文です。
count_by_sql は直接 SQL を発行します。sql は SQL 文で、select count(*) from … で書き始めます。 戻り値は、最初のカラムを to_i した値です。
increment_counter, decrement_counter は、カウンタとして扱うカラムの値を +1, -1 します。
update_all を用いて実現されています。直接 SQL を発行します。
上に書いたような SQL を用いたデータ操作のためのメソッドとは別に、Ruby レベルでの動作を設定するためのクラスメソッドがあります。 これらは AR のクラス定義で関数的に使います。
なお、Ruby 側の属性 (attribute) と DB 側のカラム (column) は区別されますが、ここでは全てカラムと書いています。
attr_protected は、カラムへの代入を制限します。制限するのは new や attributes= のようにオブジェクトのカラムを一括して扱うメソッドに対してであって、カラムに対応するメソッドを使って代入することは制限しません。
attr_protected で指定されたカラム名の配列を返します。
attr_accessible は、attr_protected とは逆に、(一括した) 代入を許すカラム名を指定します。
attr_accessible で指定されたカラム名の配列を返します。
serialize は、シリアライズするカラムを指定します。 カラムへの格納する値を YAML で変換します。これにより、Ruby の配列やハッシュをそのまま読み書きできるようになります。
class_name を指定すると、カラムに格納するオブジェクトのクラスを制限できます。
serialize で指定されたカラム名をキーとし、クラスを値とするハッシュを返します。
DB テーブルやキーの名前を扱うクラスメソッドです。
AR クラスに対応する DB テーブル名を返します。 これは AR クラスの継承に対応しています。 例えば、Reply < Topic < ActiveRecord ならば、Reply 上でも Topic の table_name が返ります。
プライマリキーを返します。標準では “id” ですが、オプションによってはテーブル名が付加されたりします。
AR クラスの継承を行う際、DB 側にクラス名を保存します。 inheritance_column はクラス名の保存先となるカラム名です。
これらは、対応する getter メソッドを再定義します。 以前の getter メソッドは original_* に alias されるので (table_name なら original_table_name)、block の中で original_* メソッドを使うと、以前の規則を元に名前を作ることができます。
DB テーブル名に対応する AR クラス名を返します。 table_name メソッドの逆です。
カラムオブジェクト (ActiveRecord::Base::Column) を扱うクラスメソッドです。
カラムオブジェクトの配列を返します。
カラム名をキーとし、カラムオブジェクトを値とするハッシュを返します。
カラム名の配列を返します。
カラムオブジェクトのうち、プライマリキーや継承クラス名キーそして _count, _id で終わる名前のカラムに対応するものを除いた配列を返します。
カラム名をキーとし、カラムに対応するメソッド名 (attr, attr=, attr?, attr_before_type_cast) のシンボル値を値とするハッシュの配列を返します。
サニタイズした値を返します。connection.quote の委譲です。
Benchmark.measure を用いたベンチマークを行います。
logger によるログをとらないブロックを提供します。
AR のインスタンスメソッドをまとめます。
id の値を返します。 id は DB のプライマリキーに対応します。
キャストする前の id の値を返します。
quote した id の値を返します。
id の値を設定します。
save されていない (まだ DB 上に格納されていない) オブジェクトであるか。
DB に格納します。true を返します。
new_record であれば insert し、そうでなければ update します。
ただし、validation チェックに引っかかった場合は格納しません。このときは false を返します。
save されていれば (new_record? でなければ) DB から行を削除します (SQL 文 (delete from …) を発行します)。
また、どちらにしても freeze します。
AR オブジェクトのクローンを作成します。この際 id を未設定にするので、new_record として扱います。
ひとつのカラムを更新し、save します。 これは特に、既存 record の boolean フラグの更新に役立ちます。
戻り値は save の戻り値です。 ただし、validation チェックは回避されます。
複数のカラムを更新し、save します。
self.attributes = attributes して save します。戻り値は save の戻り値です。
0 で初期化したあと、+1, -1 します。数値を扱うカラムでのみ動作します。
increment, decrement したあと、save します。
真偽を入れ替えます。self を返します。
toggle したあと、save します。
リロードします (キャッシュを破棄し、DB から値を取り込みます)。self を返します。
カラム値を取得します。
得られるのはキャスト後の値です。例えば DATE カラムの “2004-12-12” は Date.new(2004, 12, 12) として得られます。
read_attribute メソッド (protected) の alias です。
カラム値を設定します。
write_attribute メソッド (protected) の alias です。
ハッシュで一度に複数のカラム値を設定します。 attr_protected, attr_accessible の制限を受けます。
カラム名をキーとし、カラム値 (キャスト後) を値とするハッシュを返します。
attribute はカラム名です。 その名前のカラムが存在してかつ値が nil? でも empty? でもなければ true を、そうでなければ false を返します。
ソートしたカラム名の配列を返します。
name に対応するカラムオブジェクトを返します。
ごめんなさい。力尽きました。orz 今回もさまざまな形でご意見をいただきました。感謝いたします。
もりきゅうは ミッタシステム のプログラマです。
著者の連絡先は moriq@moriq.com です。