Active Record — railsにおけるO/Rマッピング
Active Recordは、ビジネスオブジェクトとデータベーステーブルとを接続し、ロジックとデータをひとまとまりとして表現する永続的なドメインモデル(振る舞いとデータをカプセル化した、ドメインのオブジェクトモデル)を生成する。これは、Martin Fowlerによって記述された同名のO/Rマッピング(ORM)パターンの実装である:
「データベースのテーブルやビューの列をラップし、データベースアクセスをカプセル化し、ドメインロジックを追加するオブジェクト」
アソシエーション(関連付け)とインヘリタンス(継承)の欠如という大きな問題を取り除いたことが、Active Recordsのオリジナルパターンへの大きな貢献である。Active Recordは、アソシエーションの欠如に対してはマクロのセットといったシンプルなドメイン言語を追加、インヘリタンスの欠如に対してはSingle Table Inheritance(クラスの継承ヒエラルキーを、様々なクラスの全てのフィールドのためのカラムを持つ1個のテーブルとして表現する)patternを統合し、data mapper(オブジェクト、データベース、mapper自身、それぞれの独立性を維持しながら、オブジェクト-データベース間でのデータの受け渡しをおこなうMappers層)とactive recordアプローチ間の機能ギャップを小さくする。
主要機能の簡単な紹介:
- クラス-テーブル間、属性-コラム間の自動マッピング
class Product < ActiveRecord::Base; end
とすると、以下のようなテーブル"products"に自動的にマッピングされる。
CREATE TABLE products (
id int(11) NOT NULL auto_increment,
name varchar(255),
PRIMARY KEY (id)
);
これにより、Product#nameとProduct#name=(new_name)が利用可能になる。
- シンプルなメタプログラミングマクロでコントロールされるオブジェクト間アソシエーション
class Firm < ActiveRecord::Base
has_many :clients
has_one :account
belongs_to :conglomorate
end
- シンプルなメタプログラミングマクロでコントロールされるvalue objectsのアグリゲーション
class Account < ActiveRecord::Base
composed_of :balance, :class_name => "Money",
:mapping => %w(balance amount)
composed_of :address,
:mapping => [%w(address_street street), %w(address_city city)]
end
- オブジェクトが新規であるのか既存であるのかに応じて変更可能な妥当性確認ルール
class Account < ActiveRecord::Base
validates_presence_of :subdomain, :name, :email_address, :password
validates_uniqueness_of :subdomain
validates_acceptance_of :terms_of_service, :on => :create
validates_confirmation_of :password, :email_address, :on => :create
end
- レコードをリストやツリーのように動作させる機能
class Item < ActiveRecord::Base
belongs_to :list
acts_as_list :scope => :list
end
item.move_higher
item.move_to_bottom
- 全ライフサイクルにおいて、メソッドやキューとして機能するコールバック(インスタンス化、保存、破壊、認証、等など)
class Person < ActiveRecord::Base
def before_destroy # is called just before Person#destroy
CreditCard.find(credit_card_id).destroy
end
end
class Account < ActiveRecord::Base
after_find :eager_load, 'self.class.announce(#{id})'
end
- 全ライフサイクルに対するオブザーバ
class CommentObserver < ActiveRecord::Observer
def after_create(comment) # is called just after Comment#save
NotificationService.send_email("[email protected]", comment)
end
end
- 継承階層
class Company < ActiveRecord::Base; end
class Firm < Company; end
class Client < Company; end
class PriorityClient < Client; end
- データベースレベルおよびオブジェクトレベルでのトランザクションのサポート。オブジェクトレベルのトランザクションサポートは、Transaction::Simpleを使って実装されている。
# Just database transaction
Account.transaction do
david.withdrawal(100)
mary.deposit(100)
end
# Database and object transaction
Account.transaction(david, mary) do
david.withdrawal(100)
mary.deposit(100)
end
- コラムへのリフレクション、アソシエーション、アグリゲーション
reflection = Firm.reflect_on_association(:clients)
reflection.klass # => Client (class)
Firm.columns # Returns an array of column descriptors for the firms table
- 直接操作(サービスを呼ぶかわりに)
So instead of (Hibernate example):
long pkId = 1234;
DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );
// something interesting involving a cat...
sess.save(cat);
sess.flush(); // force the SQL INSERT
Active Recordを使えば、次のようにできる。
pkId = 1234
cat = Cat.find(pkId)
# something even more interesting involving a the same cat...
cat.save
- 共有コネクタを使った、シンプルなアダプタ(100行以下)経由のデータベース抽象
ActiveRecord::Base.establish_connection(:adapter => "sqlite", :dbfile => "dbfile")
ActiveRecord::Base.establish_connection(
:adapter => "mysql",
:host => "localhost",
:username => "me",
:password => "secret",
:database => "activerecord"
)
- Log4rとLoggerでのロギングのサポート
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.logger = Log4r::Logger.new("Application Log")