RailsでMongoDBを使ったモデリング

Ruby MongoDB Driver — Ruby Driver Manual 2.8

  • JOIN ができないので、キャッシュでうまいことやる?

Caching to Avoid N+1

When we display our list of stories, we'll need to show the name of the user who posted the story. If we were using a relational database, we could perform a join on users and stores, and get all our objects in a single query. But MongoDB does not support joins and so, at times, requires bit of denormalization. Here, this means caching the 'username' attribute.

  • N 対 N のデータに対して、RDBだと中間テーブルを作ったりするが、ドキュメント(like a オブジェクト) 方式だとより直感的にデータにアクセスできる

Fields as arrays

With a relational database, even trivial relationships are blown out into multiple tables. Consider the votes a story receives. We need a way of recording which users have voted on which stories. The standard way of handling this would involve creating a table, 'votes', with each row referencing user_id and story_id.

With a document database, it makes more sense to store those votes as an array of user ids, as we do here with the 'voters' key.

To find all the stories voted on by a given user:

  Story.all(:conditions => {:voters => @user.id})
  • アトミックなアップデートの実現方法
#「stories」コレクションにアップデート
db.stories.update(
  # voters 配列にuser_id を含まない記事をくれ
  {_id: story_id, voters: {'$ne': user_id}}, 
    # もしあれば、votes を +1 して、voters 配列にuser_id を追加しろ
    {'$inc': {votes: 1}, '$push': {voters: user_id}});
  • 気をつけなきゃいけないのは、update は基本「実行したらすぐ忘れる」。レスポンスは返さない。まぁほとんどの場合で問題ないだろうけどね。とのこと

The one caveat is that, because update operations are "fire and forget," you won't get a response from the server. But in most cases, this should be a non-issue.

  • 基本的な 1 対 N の関係にあるデータは配列で保持されるべき

Essentially, comments are represented as an array of objects within a story document. This simple structure should be used for any one-to-many relationship where the many items are linear.

  • コメントのネスト。うまく使えばおもしろそうだな。
  @stories  = @db.collection('stories')
  @document = {:title => "MongoDB on Rails",
               :comments => [{:body     => "Revelatory! Loved it!",
                              :username => "Matz",
                              :comments => [{:body     => "Agreed.",
                                             :username => "rubydev29"
                                            }
                                           ]
                             }
                            ]
              }
  @stories.save(@document)