RailsでMongoDBを操作するためにMongoidを使用しています。
私が探しているのはアクティブレコードのようなものですinclude
。現在、私はそのような方法をモンゴイドormで見つけることができませんでした。
誰もがこの問題をmongoidまたはおそらくmongomapperで解決する方法を知っています。これは、別の優れた代替手段として知られています。
RailsでMongoDBを操作するためにMongoidを使用しています。
私が探しているのはアクティブレコードのようなものですinclude
。現在、私はそのような方法をモンゴイドormで見つけることができませんでした。
誰もがこの問題をmongoidまたはおそらくmongomapperで解決する方法を知っています。これは、別の優れた代替手段として知られています。
しばらく経ちましたが、Mongoidは確かにこれに対するサポートを追加しました。こちらの「EagerLoading」セクションをご覧ください:http:
//docs.mongodb.org/ecosystem/tutorial/ruby-mongoid-tutorial/#eager-loading
Band.includes(:albums).each do |band|
p band.albums.first.name # Does not hit the database again.
end
私が指摘したいのは:
:include
は参加しません。
<% @posts.each do |post| %>
<% post.comments.each do |comment| %>
<%= comment.title %>
<% end %>
<% end %>
@amrntが投稿したリンクがMongoidにマージされたようです。
更新:この回答を投稿してから2年が経ち、状況が変わりました。詳細については、 tybro0103の回答を参照してください。
両方のドライバーのドキュメントに基づいて、どちらもあなたが探しているものをサポートしていません。おそらくそれは何も解決しないからです。
ActiveRecordの機能は、 SQLデータベース:include
のN+1の問題を解決します。含める関連テーブルをActiveRecordに指示することで、ステートメントを使用して単一のSQLクエリを作成できます。これにより、クエリするテーブルの量に関係なく、単一のデータベース呼び出しが発生します。JOIN
MongoDBでは、一度に1つのコレクションのみをクエリできます。のようなものはサポートしていませんJOIN
。したがって、Mongoidに含める必要のある他のコレクションを指定できたとしても、追加のコレクションごとに個別のクエリを実行する必要があります。
他の答えは正しいですが、Mongoidの現在のバージョンでは、includesメソッドが目的の結果を達成するための最良の方法です。インクルードが利用できなかった以前のバージョンでは、n + 1の問題を取り除く方法を見つけ、言及する価値があると思いました。
私の場合、それはn+2の問題でした。
class Judge
include Mongoid::Document
belongs_to :user
belongs_to :photo
def as_json(options={})
{
id: _id,
photo: photo,
user: user
}
end
end
class User
include Mongoid::Document
has_one :judge
end
class Photo
include Mongoid::Document
has_one :judge
end
コントローラのアクション:
def index
@judges = Judge.where(:user_id.exists => true)
respond_with @judges
end
このas_json応答により、Judgeレコードからn+2クエリの問題が発生します。私の場合、開発サーバーに次の応答時間を与えます。
816msで200OKを完了(ビュー:785.2ms)
この問題を解決するための鍵は、審査員ごとに1つずつではなく、1つのクエリでユーザーと写真を読み込むことです。
これは、 Mongoids IdentityMapMongoid2およびMongoid3がこの機能をサポートしていることを利用して行うことができます。
まず、mongoid.yml構成ファイルでIDマップをオンにします。
development:
host: localhost
database: awesome_app
identity_map_enabled: true
次に、コントローラーのアクションを変更して、ユーザーと写真を手動でロードします。注:Mongoid :: Relationshipレコードはクエリを遅延評価するため、to_aを呼び出して実際にレコードをクエリし、IdentityMapに保存する必要があります。
def index
@judges ||= Awards::Api::Judge.where(:user_id.exists => true)
@users = User.where(:_id.in => @judges.map(&:user_id)).to_a
@photos = Awards::Api::Judges::Photo.where(:_id.in => @judges.map(&:photo_id)).to_a
respond_with @judges
end
これにより、合計3つのクエリのみになります。審査員用に1つ、ユーザー用に1つ、写真用に1つ。
559msで200OKを完了(ビュー:87.7ms)
これはどのように作動しますか?IdentityMapとは何ですか?
IdentityMapは、どのオブジェクトまたはレコードがすでにロードされているかを追跡するのに役立ちます。したがって、最初のユーザーレコードをフェッチすると、IdentityMapはそれを保存します。次に、同じユーザーを再度フェッチしようとすると、Mongoidはデータベースに再度クエリを実行する前にユーザーのIdentityMapにクエリを実行します。これにより、データベースに1つのクエリが保存されます。
したがって、すべてのユーザーと写真をロードすることで、手動クエリで裁判官jsonに必要なことがわかっているので、データを一度にIdentityMapにプリロードします。次に、裁判官がユーザーと写真を要求すると、IdentityMapをチェックし、データベースにクエリを実行する必要はありません。
ActiveRecord:include
は通常、Rubyオブジェクトにデータを取り込むために完全な結合を行いません。2回の呼び出しを行います。最初に親オブジェクト(たとえば投稿)を取得し、次に関連オブジェクト(投稿に属するコメント)をプルするための2番目の呼び出し。
Mongoidは、参照される関連付けに対して基本的に同じように機能します。
def Post
references_many :comments
end
def Comment
referenced_in :post
end
コントローラでは、次の投稿を取得します。
@post = Post.find(params[:id])
あなたの見解では、コメントを繰り返します。
<%- @post.comments.each do |comment| -%>
VIEW CODE
<%- end -%>
Mongoidはコレクション内の投稿を検索します。コメントイテレータを押すと、コメントを取得するために単一のクエリが実行されます。Mongoidはクエリをカーソルでラップするため、真のイテレータであり、メモリが過負荷になることはありません。
Mongoid lazyはすべてのクエリをロードして、デフォルトでこの動作を許可します。:include
タグは不要です。
このN+1を回避するには、スキーマを更新する必要があります。MongoDBには、いくつかの結合を行うためのソリューションがありません。
詳細レコード/ドキュメントをマスターレコード/ドキュメントに埋め込みます。
私の場合、私はコレクション全体を持っていませんでしたが、n + 1を引き起こしたそのオブジェクトを持っていました(箇条書きはそれを言います)。
したがって、n+1を引き起こす以下に書くのではなく
quote.providers.officialname
私が書いた
Quote.includes(:provider).find(quote._id).provider.officialname
それは問題にはなりませんでしたが、私が自分自身を繰り返すか、n+1をチェックすることはmongoidには不要であるかどうかを考えさせられました。