在Mongo文档中缓存/存储聚合的模式(Ruby,mongoid)?

时间:2012-10-24 20:02:48

标签: ruby mongodb design-patterns mongoid

我正在收集一组帐户中的推文,并希望按照每个(给定)月份的推文或每个推文在给定月份的平均推文长度进行此类计算。

一旦这个计算在过去一段时间内完成一次(例如2010年5月用户X推文4.2次/天),则不需要重新计算(大量删除过去的推文是边缘情况)。 ..所以给出了这个条件,根据需要执行这个聚合的基本模式是什么,然后在Mongo文档中记忆它。

示例:

t = TwitterAccount.find_by(:screen_name=>'Bob')
puts t.tweet_rate(:month=>5, :year=>2010)
# ...

puts t.tweet_rate(:month=>5, :year=>2010)
# cached/memoized result is stored inside document

我可以猜到编写自己的类来处理这个问题的方法,但我认为这是NoSQL世界中常见的模式(我刚刚开始使用MongoDB),可以随意添加新属性。 / p>

(我使用Ruby 1.9和Mongoid作为我的ORM,但不认为这在概念上很重要)

1 个答案:

答案 0 :(得分:0)

是的,在数据​​库中记忆是一种常见的模式。使用MongoDB,您可以使用丰富的BSON存储嵌入式阵列,以便您可以通过一个数据库请求轻松高效地获取数据。

MongoDB中嵌入式文档数组有一些细微差别。以下测试显示了'$ elemMatch'数组查询选择器和'$'更新数组运算符的用法。它还有客户端和服务器端更新的示例。

http://docs.mongodb.org/manual/reference/operators/

虽然您的每月更新很少,但您可以预先分配以避免因增长文档而产生的开销。预分配需要一些额外的代码行,你也必须处理年代。

Ruby 1.9.3,Mongoid 3.0.15,Moped 1.3.1

class TwitterAccount
  include Mongoid::Document
  field :screen_name, type: String
  embeds_many :tweet_rates
end

class TweetRate
  include Mongoid::Document
  field :year, type: Integer
  field :month, type: Integer
  field :rate, type: Float
  embedded_in :twitter_account
end

测试/单元/ tweet_rate_test.rb

require 'test_helper'

class TweetRateTest < ActiveSupport::TestCase

  def setup
    TwitterAccount.delete_all
    TwitterAccount.create(:screen_name => 'Bob')
  end

  test "monthly append" do
    t = TwitterAccount.find_by(:screen_name => 'Bob')
    assert_equal('Bob', t.screen_name)
    t.tweet_rates.create(:year => 2010, :month =>5, :rate => 12.3)
    t.save!
    tr = TwitterAccount.find_by(:screen_name => 'Bob').tweet_rates
    assert_equal(1, tr.size)
  end

  test "prealloc for a year" do
    t = TwitterAccount.find_by(:screen_name => 'Bob')
    assert_equal('Bob', t.screen_name)

    # prealloc for a whole year
    year = 2012
    (1..12).each do |month|
      t.tweet_rates.create(:year => year, :month => month, :rate => -1.0)
    end
    t.save!
    t = TwitterAccount.find_by(:screen_name => 'Bob')
    assert_equal(12, t.tweet_rates.size)

    # update a rate using client-side Ruby
    month, rate  = 10, 12.3
    t.tweet_rates.detect{|tr| tr.year == year && tr.month == month}.rate = rate
    t.save!
    assert_equal(rate, TwitterAccount.find_by(:screen_name => 'Bob')
                                     .tweet_rates.detect{|tr| tr.year == year && tr.month == month}.rate) # used #detect NOT overloaded #find

    # update a rate using MongoDB server-side
    month, rate = 11, 4.56
    TwitterAccount.where({ :screen_name => 'Bob',
                           :tweet_rates => { '$elemMatch' => { :year => year, :month => 11 } } })
                  .update('$set' => { 'tweet_rates.$.rate' => rate })
    assert_equal(rate, TwitterAccount.where({ :screen_name => 'Bob'}).first
                                     .tweet_rates.detect{|tr| tr.year == year && tr.month == month}.rate) # use #detect and NOT overloaded #find
  end
end

希望这会有所帮助。