はじめに
Ruby on Rails(以下Rails)でMongoDBを使う際のORマッパーとして現在はMongoidが標準的に使用されるようになりました。MongoidはActive Recordに似たAPIを備え、かつMongoDBのスキーマレス、ドキュメント指向、ダイナミッククエリ、アトミック操作といった特徴をRubyから容易に扱えるように設計されています。
本記事ではMongoid 4.0.0、Rails 4.1.8をベースに、RailsでMongoidを使う利点を述べ、実際にサンプルアプリケーションを動かして紹介したいと思います。
Mongoidを使う利点
これまでのプロジェクトではActive Recordを介して主にMySQLを使用していましたが、Mongoidを使用するようになって、以下の利点を実感しています。
Active Recordと共通もしくは拡張したクエリと永続化メソッド
Mongoidでは、Originが提供するDSLによって、MongoDBのクエリを実行します。Mongoidでは、Origin::Queryable
を拡張したMongoid::Criteria
が各種のクエリと永続化のメソッドを提供します。CRUD操作はほぼActive Recordと同じようなメソッド呼び出しで行えます。また、その他にMongoDBの各種クエリ演算に対応したメソッドもあり、Arelに似たリッチな文法を使用することが可能です。
たとえば、OR条件を記述できるor
メソッドや数値をアトミックにインクリメントして更新するinc
メソッドなどは実際に使ってみると便利です。
Active Modelでバリデーションが可能
バリデーションに関しては、Active Recordを使用した場合と同様に内部的にはActive Modelを使用しており、ほぼ同様の感覚で使用することができます。
マイグレーションが不要
MongoDBはスキーマレスなので、モデルを変更した際に属性を変更する場合はマイグレーションを実行する必要がありません。この特徴は、特にアプリケーションの初期段階で設計変更が多い場合などに大いに役に立ちます。
インデックスを作成する際も、モデル側に記述を行い、rake db:mongoid:create_indexes
コマンドを実行するだけで済みます。
Symbol、ArrayやHashを属性として使用することが可能
MongoidではSymbol、ArrayやHashなどのモデル属性を定義してそのまま永続化することができます。これは特にRubyのシンボルや配列、ハッシュをそのまま保存したいときに便利です。
位置情報など便利機能
MongoDBでは地理空間インデックスを使用できるので、緯度経度などの位置情報を登録しておけば、現在位置から近い順に場所を検索することができます。実際にFoursquareなどのWebサービスで使用されてきた実績もあります。
Mongoidでできないこと
ActiveRecordと異なり、MongoidではテーブルのJOIN、トランザクション処理などはできません。代わりにドキュメント指向のモデル設計を行い、アトミック処理を行うなどの工夫を行う必要があります。これらに関しては、機会があれば記事を書きたいと思います。
実際にMongoidを使ったRailsアプリを作ってみる
前節でRailsでMongoidを使う利点について述べましたが、以下では、実際にごく一部ですが、Mongoidの利点を生かした位置情報アプリケーションのtripperを途中まで作成します。
アプリケーションの作成
まず、railsでアプリケーションを新規作成します。
$ rails new tripper --skip-active-record --skip-bundle
通常と異なるのは、--skip-active-record
を指定することと、bundle
コマンドで不要なDB関係のGemをインストールしないことです。
tripperディレクトリに移動し、Gemfileに以下の記述を追加します。
gem 'mongoid', '~> 4.0.0'
bundle
コマンドを実行します。
Mongoidの設定ファイルを以下のコマンドで作成します。
$ rails g mongoid:config
Scaffold
rails g scaffold
を実行してオプションを確認すると、自動的にmongoidを使う設定になっています。
Mongoid options:
[--timestamps], [--no-timestamps] # Indicates when to generate timestamps
[--parent=PARENT] # The parent class for the generated model
[--collection=COLLECTION] # The collection for storing model's documents
-t, [--test-framework=NAME] # Test framework to be invoked
# Default: test_unit
旅行のプラン名を記述するPlan
モデルを作成します。create_at, updated_atを追加するために--timestamps
を追加します。
また、標準で文字列になるため、titleに型は指定していません。
$ rails g scaffold plan title --timestamps
ここで、モデルファイルは以下のようになっています。
app/models/plan.rb
class Plan
include Mongoid::Document
include Mongoid::Timestamps
field :title, type: String
end
ここで、Active Recordと異なりマイグレーションを実行する必要がありません。そのまま以下のコマンドで実際にアプリケーションを起動することができます。
rails s
コントローラapp/controllers/plans_controller.rb
のメソッドのCRUD操作もActive Recordと同じになっていることを確認してください。
Mongoidの独自機能(Array属性, 位置情報)を使う
ここで、旅行で訪れる場所を記録するために、Venue
モデルを作成します。
rails g scaffold venue name coordinates:Array --timestamps
Venueモデルは以下のようになります。
app/models/venue.rb
class Venue
include Mongoid::Document
include Mongoid::Timestamps
field :coordinates, type: Array
end
ここで、Scaffoldが生成したviewとcontrollerではそのままArrayのfieldを扱うことができないので、以下の部分のコードを
app/views/venues/_form.html.erb
(修正前)
<div class="field">
<%= f.label :coordinates %><br>
<%= f.text_field :coordinates %>
</div>
以下のように修正します。
app/views/venues/_form.html.erb
(修正後)
<div class="field">
<%= f.label :latitude %><br>
<%= f.text_field :latitude %>
</div>
<div class="field">
<%= f.label :longitude %><br>
<%= f.text_field :longitude %>
</div>
また、controllerの以下のコードを
app/controllers/venues_controller.rb
(修正前)
def venue_params
params.require(:venue).permit(:name, :coordinates)
end
以下のように変更します。
def venue_params
params.require(:venue).permit(:name, :latitude, :longitude)
end
Venueモデルにアクセッサとコールバック、インデックスの定義を行い、位置情報インデックスを使用できるようにします。
class Venue
...
attr_accessor :latitude, :longitude
index({ coordinates: '2dsphere' })
after_validation :set_coordinates
private
def set_coordinates
self.coordinates = [longitude.to_f, latitude.to_f]
end
...
ここでインデックスの作成を行います。
$ rake db:mongoid:create_indexes
I, [2014-12-15T19:33:26.240958 #2351] INFO -- : MONGOID: Created indexes on Venue:
I, [2014-12-15T19:33:26.241082 #2351] INFO -- : MONGOID: Index: {:coordinates=>"2dsphere"}, Options: {}
これで位置情報を登録する準備ができました。
rails s
でサーバを起動し、http://localhost:3000/venues にアクセスして、適当な位置情報を登録します。今回は以下のような京都の場所を登録しました。(今回は手動で入力しましたがgeocoderなどのgemを使うと地名から緯度経度を自動で入力できるはずです。)
位置情報からの検索・並べ替えを行う
上記の登録した場所を、ある場所から近い順に並べ替えを行い一番近い場所を検索したいと思います。上記でインデックスを作成したので、Mongoidの#geo_near
メソッドを使えば簡単に実装ができます。例えば、http://localhost:3000/venues/search から検索を行いたい場合、以下のようにコードを追加、修正します。
app/controllers/venues_controller.rb
def search
@venues = Venue.geo_near([params[:longitude].to_f, params[:latitude].to_f]).spherical
render 'index'
end
app/views/venues/index.html.erb
<%= form_tag search_venues_path do -%>
<%= label_tag 'longitude' %>
<%= text_field_tag 'longitude' %>
<%= label_tag 'latitude' %>
<%= text_field_tag 'latitude' %>
<%= submit_tag 'sort' %>
<% end -%>
config/routes.rb
resources :venues do
collection do
post :search
end
end
http://localhost:3000/venues からアクセスして、京都駅付近の緯度経度を入力すると、4つの場所の中で産寧坂が最も近いことがわかります。
アプリケーションの実装は途中ですが、PlanとVenueをembeds_many
で関連付けを行えば、行き先リストを作るのも可能だと思います。ドキュメント指向のモデル作成などここで取り上げなかったトピックについても試してみてください。
まとめ
本記事ではRailsでMongoidを使う利点について述べ、また実際に位置情報などのMongoidの特徴を生かした簡単なアプリケーションを実際に作ってみました。
本記事をきっかけにActiveRecordだけでなくMongoid、そしてMongoDBの世界に触れる方が増えると幸いです。