Rails3 対応 MongoDB ORM、Mongoid 詳解―エクステンション
FABRICATION
Paul Eliott さんの Fabrication Gem は、オブジェクト生成ライブラリです。Mongoid を最初からサポートしており、テストの簡便のために、オブジェクトを生成する素敵な構文を提供しています。
Fabricator(:person) do title "Grand Poobah" addresses(:count => 2) do |address, i| Fabricate(:address, :streeet => "#{i} Bond St.") end end
MONGOID-RSPEC
Evan Sagge さんの mongoid-rspec は、Mongoid 用の RSpec のマッチャーを提供します。マッチャーには、関連、オプション、バリデーション、フィールドが含まれます。
describe Person do it { should reference_one :account } it { should reference_many :posts } it { should be_referenced_in :organization } it { should validate_presence_of(:name) } it { should have_field(:age).of_type(Integer) } end describe Address do it { should be_embedded_in(:person).as_inverse_of(:addresses) } end
REMARKABLE
Brian Cardarella さんの remarkable-mongoid Gem は、Mongoid 用の RSpec マッチャーの素敵な代替を提供します。マッチャーは以下の例の他に、Remarkable::ActiveModel を元に、全てのバリデーションを含みます。
describe Person do it { should reference_one :account } it { should reference_many :posts } it { should be_referenced_in :organization } it { should embed_one :name } it { should embed_many :addresses } it { should be_embedded_in :group } it { should validate_uniqueness_of :dob } end
RIOT
Riot-Mongoid Gem は、Mongoid 用の riot アサーションを提供します。フィールド、キー、関連、バリデーションのアサーションが含まれます。
context "Person Model" do setup { Person.new } asserts_topic.has_field :title, :type => String asserts_topic.has_association :references_one, :account asserts_topic.has_association :embeds_many, :addresses asserts_topic.has_validation :validates_presence_of, :title end
エクステンションは以上です。
Rails3 対応 MongoDB ORM、Mongoid 詳解―インテグレーション
CARRIERWAVE
ファイルアップロードを扱う Carrierwave は Mongoid をサポートしています。現在のところ、ファイルと S3 に格納することを指定できます。:mount_on オプションがアップローダークラスに定義されてない場合は、ファイル名が アップローダー名_filename をフィールド名として、格納されます。
class User include Mongoid::Document mount_uploader :avatar, AvatarUploader #field is avatar_filename end
CUCUMBER
MongoDB はトランザクションをサポートしていないので、それぞれのフィーチャーが実行される前に、データベースがクリーンになるように、Cucumber にフックを追加したいと思います。
features/support/hooks.rb:
Mongoid.master.collections.select do |collection| collection.name !~ /system/ end.each(&:drop)
もし、database_cleaner という Gem を使っているなら、代わりに、features/support/database_cleaner.rb を作成してください。
require 'database_cleaner' DatabaseCleaner.strategy = :truncation DatabaseCleaner.orm = "mongoid" Before { DatabaseCleaner.clean }
RSPEC
Cucumber と同じように、RSpec の use_transactional_fixtures は Mongoid では影響しません。スイートを実行した後、データベースをクリーンにすることができます。
spec/spec_helper.rb:
Rspec.configure do |config| config.after :suite do Mongoid.master.collections.select do |collection| collection.name !~ /system/ end.each(&:drop) end end
おまけに :each の後何かすることもできますが、たくさんのインテグレーションスペックがある場合、遅くなるので注意してください。
もし、database_cleaner という Gem を使っているなら、代わりに、spec/spec_helper.rb の RSpec コンフィグブロックで以下の行を付け足してください。
Rspec.configure do |config| require 'database_cleaner' config.before(:suite) do DatabaseCleaner.strategy = :truncation DatabaseCleaner.orm = "mongoid" end config.before(:each) do DatabaseCleaner.clean end end
DEVISE
Mongoid で動くように Devise をセットアップするためにやることは、ActiveRecord を呼び出しているところを削除して、Mongoid に置き換えることです。
config/initializers/devise.rb:
# ==> ORM configuration # Load and configure the ORM. Supports :active_record (default), # :mongoid (bson_ext recommended) and :data_mapper (experimental). require "devise/orm/mongoid"
PASSENGER
MongoDB ウィキでは、Passenger の Smart spawning が有効なとき(デフォルトでは Conservative spawning です)、イニシャライザでインクルードする必要があると警告してますが、Mongoid では、特に何かする必要はありません。Passenger がこのモードで起動しているとき、ワーカーがフォークされたら、Mongoid はそれを検知し、再接続するようになっています。
Rails3 対応 MongoDB ORM、Mongoid 詳解―Rakeタスク
Mongoid は Rails 3 環境で以下の Rake タスクを提供します。
db:create | 依存関係のために存在します。実際には何もしません。 | |
db:create_indexes | モデルから全てのインデックス定義を読み取り、データベースにそれらを作成します。 | |
db:drop | システム用コレクションを除いて、全てのコレクションをデータベースから削除します。 | |
db:migrate | 依存関係のために存在します。実際には何もしません。 | |
db:schema:load | 依存関係のために存在します。実際には何もしません。 | |
db:seed | db/seeds.rb からデータベースに初期データを作成します。 | |
db:setup | インデックスを作成し、データベースに初期データを作成します。 | |
db:test:prepare | 依存関係のために存在します。実際には何もしません。 |
Rake タスクは以上です。
Rails3 対応 MongoDB ORM、Mongoid 詳解―その他
Mongoid にはアプリケーションで使える、いくつかの役に立つ機能があります。
マスター/スレイブ サポート
mongoid.yml でスレイブデータベースを設定しているなら、スレイブを有効化した読み取りクエリを、ラウンドロビンでスレイブデータベースを参照します。また、クエリ単位でも制御することができます。
モデルの全ての読み取りを、スレイブ間でラウンドロビンします:
class Person include Mongoid::Document enslave end
enslave Criteria を使って、クエリ単位で、スレイブから読み取ります:
Person.where(:first_name => "Durran").enslave
キャッシュ
Mongoid は、何も設定していないそのままの状態で、大きなクエリとデータセットでメモリが効率的に使われるように、MongoDB の Ruby ドライバーのカーソルをラップしています。しかしながら、メモリの中に全てのヒットしたドキュメントをロードしたい場合や、それらをデータベースに複数回問い合わせることなく返したい場合に、cashe マクロと Criteria を使うことができます。
モデルに対する全てのクエリがキャッシュされます:
class Person include Mongoid::Document cache end
クエリ単位でのキャッシュ:
Person.where(:first_name => "Durran").cache
ダーティー・アトリビュート
Mongoid には、ActiveModel スタイルのダーティー・アトリビュート・モジュールが実装されています。これらは、ActiveModel の API に厳密に従います:
class Person include Mongoid::Document field :first_name end person = Person.new(:first_name => "Leroy") person.first_name = "Lauren" person.changed? # true person.first_name_changed? # true person.first_name_was # Leroy person.first_name_change # [ "Leroy", "Lauren" ] person.changes # { "first_name" => [ "Leroy", "Lauren" ] }
パラノイド ドキュメント
ドキュメントを削除するとき、削除フラグをつけて、実際にはデータベースから削除したくない場合があると思います。Mongoid はそういう時のために Pranoia モジュールを提供しています。
class Person include Mongoid::Document include Mongoid::Paranoia end person.delete # deleted_at フィールドに現在時刻をセットします person.delete! # ドキュメントを完全に削除します person.destroy! # コールバックとともにドキュメントを完全に削除します person.restore # 削除されたドキュメントを復帰します
バージョン管理
Mongoid は、Mongoid::Versioning モジュールをインクルードすることにより、シンプルなバージョン管理をサポートします。このモジュールをインクルードすれば、保存の時に、version フィールドを作成します。version 番号は整数で、保存のたびに更新されます。
class Person include Mongoid::Document include Mongoid::Versioning end
max_versions を設定することができます。Mongoid は最新のバージョンが設定された最大値を超えないようにします。
class Person include Mongoid::Document include Mongoid::Versioning # 最大5バージョンだけ記録する max_versions 5 end
タイムスタンプ
Mongoid は Mongoid::Timestamps モジュールで、タイムスタンプをサポートします。created_at と updated_at フィールドが、それぞれ自動的に付与されます。
class Person include Mongoid::Document include Mongoid::Timestamps end
キー
key マクロを使うことで、デフォルトの id の代わりに、複合キーを作成することができます。
class Person include Mongoid::Document field :first_name field :last_name key :first_name, :last_name end person = Person.new(:first_name => "Syd", :last_name => "Vicious") person.id # "syd-vicious" を返します。
違うコレクションに保存する
store_in マクロを使えば簡単です。
class Person include Mongoid::Document store_in :students end
その他は以上です。
Rails3 対応 MongoDB ORM、Mongoid 詳解―インデックス
index マクロを使うことにより、ドキュメントにインデックスを定義することができます。:unique オプションをつけると、ユニークなインデックスを構築できます。オプションは必須ではありません。
class Person include Mongoid::Document field :ssn index :ssn, :unique => true end
以下のようにして、エンベッドされたドキュメントにもインデックスを定義することができます。
class Person include Mongoid::Document embeds_many :addresses index "addresses.street" end
複数フィールドに対してもインデックスを定義することができます。また、ソート順も定義できます。
include Mongoid::Document field :first_name field :last_name index( [ [ :first_name, Mongo::ASCENDING ], [ :last_name, Mongo::ASCENDING ] ], :unique => true ) end
インデックスは、ある程度時間がかかる場合、バックグラウンドで実行することもできます:
class Person include Mongoid::Document field :ssn index :ssn, :unique => true, :background => true end
位置空間インデックスを定義する場合は、必ず、フィールドを配列としてください。
class Person include Mongoid::Document field :location, :type => Array index [[ :location, Mongo::GEO2D ]], :min => 200, :max => 200 end
リレーショナルな関連で、外部キーに対して、インデックスを定義することができます。
class Comment include Mongoid::Document referenced_in :post, :inverse_of => :comments, :index => true references_many \ :users, :stored_as => :array, :inverse_of => :comments, :index => true end
データベースにインデックスを作成したい時は、Rake タスクを呼び出してください:
rake db:create_indexes
モデルクラスがロードされたときに、自動的にインデックスを作成したい場合は、mongoid.yml のコンフィギュレーションオプションで設定することができます。ただし、プロダクション環境では推奨しません。
defaults: &defaults autocreate_indexes: true
インデックスは以上です。
Rails3 対応 MongoDB ORM、Mongoid 詳解―継承
Mongoid はドキュメントとエンベッドされたドキュメントの継承をサポートしています。以下のドメインモデルを考えてみます。
class Canvas include Mongoid::Document field :name embeds_many :shapes def render shapes.each { |shape| shape.render } end end class Browser < Canvas field :version, :type => Integer end class Firefox < Browser field :useragent end class Shape include Mongoid::Document field :x, :type => Integer field :y, :type => Integer embedded_in :canvas, :inverse_of => :shapes def render #... end end class Circle < Shape field :radius, :type => Float end class Rectangle < Shape field :width, :type => Float field :height, :type => Float end
上記の例で、Canvas、Browser、Firefox は全て canvases コレクションの中に保存されます。データベースから正しいドキュメントが帰ってくるように、追加のアトリビュート _type が保存されます。また、これは、Circle、Rectangle、Shape といった、エンベッドされたドキュメントにも適用されます。フィールドとバリデーションも子に継承されますが、親には適用されません。子クラスは親クラスの全てのフィールドとバリデーションを含みますが、逆はありません。
子クラスへのクエリ
子クラスへのクエリは通常のクエリと変わりません。ドキュメントはすべて同じコレクションですが、クエリは、正しい型のドキュメントのみ返します。
Canvas.where(:name => "Paper") # Canvas ドキュメントと子クラスを返します Firefox.where(:name => "Window 1") # Firefox ドキュメントだけ返します
関連
通常の代入を通してか、関連の build や create メソッドを通して、has_one または has_many 関連の、どの型の子クラスも追加できます:
firefox = Firefox.new firefox.shapes.build({ :x => 0, :y => 0 }) # Shape オブジェクトを作成します firefox.shapes.build({ :x => 0, :y => 0 }, Circle) # Circle オブジェクトを作成します firefox.shapes.create({ :x => 0, :y => 0 }, Rectangle) # Rectangle オブジェクトを作成します rect = Rectangle.new(:width => 100, :height => 200) firefox.shapes
継承は以上です。
Rails3 対応 MongoDB ORM、Mongoid 詳解―バリデーション
Mongoid は、基本的なバリデーションを提供するために ActiveModel::Validations を含んでおり、さらに関連と一意性バリデーションに手を加えています。
さらに詳しい情報は、ActiveModel::Validations のドキュメントを参照してください。
全てのバリデーションには以下の共通のオプションがあります。
- :allow_nil アトリビュートが nil を許すかどうかを指定します。
- :if 与えられた値が true と評価された場合だけ実行されます。
- :on 指定された :create や :update の時だけ実行されます。
- :unless 与えられた値が false と評価された場合だけ実行されます。
ブール値のフィールドが受け入れ可能(デフォルト: true)かどうかを検証します:
validates_acceptance_of :terms
オプション:
- :accept 受け入れる値を指定します。
- :message カスタムエラーメッセージを定義します。
関連付けられているドキュメントも一緒に検証します:
validates_associated :addresses, :employers
オプション:
- :message カスタムエラーメッセージを定義します。
末尾に "_confirmation" がついたアクセサと一致するかを検証します:
validates_confirmation_of :password
オプション:
- :message カスタムエラーメッセージを定義します。
与えられた値が含まれないかを検証します:
validates_exclusion_of :employers, :in => ["Hashrocket"]
オプション:
- :in 含まれない配列か範囲。
- :message カスタムエラーメッセージを定義します。
フィールドのフォーマットを検証します:
validates_format_of :title, :with => /[A-Za-z]/
オプション:
与えられた値が含まれるかを検証します。
validates_inclusion_of :employers, :in => ["Hashrocket"]
オプション:
- :allow_blank 空の値を許すかどうか指定します。
- :in 含まれる配列か範囲。
- :message カスタムエラーメッセージを定義します。
フィールドの長さを検証します:
validates_length_of :password, :minimum => 8, :maximum => 16
オプション:
- :allow_blank 空の値を許すかどうか指定します。
- :in 長さの範囲を指定します。
- :within 長さの範囲を指定します。
- :maximum アトリビュートの最大の長さを指定します。
- :minimum アトリビュートの最小の長さを指定します。
- :tokenizer ブロックで文字のカウント方法を定義します。
- :message カスタムエラーメッセージを定義します。
- :too_long 長すぎた時のカスタムエラーメッセージを定義します。
- :too_short 短すぎた時のカスタムエラーメッセージを定義します。
- :wrong_length 間違った長さの時のカスタムエラーメッセージを定義します。
フィールドの数値を検証します:
validates_numericality_of :age, :even => true
オプション:
- :equal_to フィールドが指定した値と一致するか検証します。Specify a value the field must be exactly.
- :greater_than フィールドが指定した値より大きいか検証します。
- :greater_than_or_equal_to フィールドが指定した値より大きいか等しいかを検証します。
- :less_than フィールドが指定した値より小さいか検証します。
- :less_than_or_equal_to フィールドが指定した値より小さいか等しいかを検証します。
- :even 偶数かどうかを指定します。
- :odd 奇数かどうかを指定します。
- :only_integer 整数かどうかを指定します。
- :message カスタムエラーメッセージを定義します。
フィールドが存在するかを懸賞します:
validates_presence_of :first_name
オプション:
- :message カスタムエラーメッセージを定義します。
フィールドがデータベースで一意かどうかを検証します:
※注意:エンベッドされたドキュメントに使用した場合、親ドキュメントのコンテキストの範囲内で一意かどうかをチェックします。データベース全体ではありません。
validates_uniqueness_of :ssn
オプション:
- :message カスタムエラーメッセージを定義します。
バリデーションは以上です。