Skip to content

Commit

Permalink
adding ability to maintain a snapshot of multiple collections atomica…
Browse files Browse the repository at this point in the history
…lly, bumping version to 1.0
  • Loading branch information
aaw committed Jan 24, 2012
1 parent b9b88b3 commit 09bd151
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 35 deletions.
32 changes: 30 additions & 2 deletions README.md
Expand Up @@ -3,8 +3,8 @@ Mongoid Collection Snapshot

Easy maintenance of collections of processed data in MongoDB with the Mongoid ODM.

Example:
--------
Quick example:
--------------

Suppose that you have a Mongoid model called `Artwork`, stored
in a MongoDB collection called `artworks` and the underlying documents
Expand Down Expand Up @@ -78,3 +78,31 @@ method `build`, which populates the collection snapshot and any indexes you need
By default, mongoid_collection_snapshot maintains the most recent two snapshots
computed any given time.

Other features
--------------

You can maintain multiple collections atomically within the same snapshot by
passing unique collection identifiers to ``collection_snaphot`` when you call it
in your build or query methods:

class ArtistStats
include Mongoid::CollectionSnapshot

def build
# ...
# define map/reduce for average and max aggregations
# ...
Artwork.collection.map_reduce(map_avg, reduce_avg, :out => collection_snapshot('average'))
Artwork.collection.map_reduce(map_max, reduce_max, :out => collection_snapshot('max'))
end

def average_price(artist)
doc = collection_snapshot('average').findOne({'_id.artist': artist})
doc['value']['sum']/doc['value']['count']
end

def max_price(artist)
doc = collection_snapshot('max').findOne({'_id.artist': artist})
doc['value']['max']
end
end
2 changes: 1 addition & 1 deletion VERSION
@@ -1 +1 @@
0.0.1
0.1.0
16 changes: 11 additions & 5 deletions lib/mongoid_collection_snapshot.rb
Expand Up @@ -15,7 +15,7 @@ module Mongoid::CollectionSnapshot

before_save :build
after_save :ensure_at_most_two_instances_exist
before_destroy :drop_snapshot_collection
before_destroy :drop_snapshot_collections
end

module ClassMethods
Expand All @@ -24,12 +24,18 @@ def latest
end
end

def collection_snapshot
Mongoid.master.collection("#{self.collection.name}.#{workspace_slug}")
def collection_snapshot(name=nil)
if name
Mongoid.master.collection("#{self.collection.name}.#{name}.#{workspace_slug}")
else
Mongoid.master.collection("#{self.collection.name}.#{workspace_slug}")
end
end

def drop_snapshot_collection
collection_snapshot.drop
def drop_snapshot_collections
Mongoid.master.collections.each do |collection|
collection.drop if collection.name =~ /^#{self.collection.name}\.([^\.]+\.)?#{workspace_slug}$/
end
end

# Since we should always be using the latest instance of this class, this method is
Expand Down
18 changes: 10 additions & 8 deletions mongoid_collection_snapshot.gemspec
Expand Up @@ -4,14 +4,14 @@
# -*- encoding: utf-8 -*-

Gem::Specification.new do |s|
s.name = %q{mongoid_collection_snapshot}
s.version = "0.0.1"
s.name = "mongoid_collection_snapshot"
s.version = "0.1.0"

s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Aaron Windsor"]
s.date = %q{2011-09-02}
s.description = %q{Easy maintenence of collections of processed data in MongoDB with the Mongoid ODM}
s.email = %q{aaron.windsor@gmail.com}
s.date = "2012-01-24"
s.description = "Easy maintenence of collections of processed data in MongoDB with the Mongoid ODM"
s.email = "aaron.windsor@gmail.com"
s.extra_rdoc_files = [
"LICENSE.txt",
"README.md"
Expand All @@ -26,17 +26,19 @@ Gem::Specification.new do |s|
"mongoid_collection_snapshot.gemspec",
"spec/models/artwork.rb",
"spec/models/average_artist_price.rb",
"spec/models/multi_collection_snapshot.rb",
"spec/mongoid/collection_snapshot_spec.rb",
"spec/spec_helper.rb"
]
s.homepage = %q{http://github.com/aaw/mongoid_collection_snapshot}
s.homepage = "http://github.com/aaw/mongoid_collection_snapshot"
s.licenses = ["MIT"]
s.require_paths = ["lib"]
s.rubygems_version = %q{1.6.2}
s.summary = %q{Easy maintenence of collections of processed data in MongoDB with the Mongoid ODM}
s.rubygems_version = "1.8.10"
s.summary = "Easy maintenence of collections of processed data in MongoDB with the Mongoid ODM"
s.test_files = [
"spec/models/artwork.rb",
"spec/models/average_artist_price.rb",
"spec/models/multi_collection_snapshot.rb",
"spec/mongoid/collection_snapshot_spec.rb",
"spec/spec_helper.rb"
]
Expand Down
14 changes: 14 additions & 0 deletions spec/models/multi_collection_snapshot.rb
@@ -0,0 +1,14 @@
class MultiCollectionSnapshot
include Mongoid::CollectionSnapshot

def build
collection_snapshot('foo').insert({'name' => 'foo!'})
collection_snapshot('bar').insert({'name' => 'bar!'})
collection_snapshot('baz').insert({'name' => 'baz!'})
end

def get_names
['foo', 'bar', 'baz'].map{ |x| collection_snapshot(x).find_one['name'] }.join('')
end

end
41 changes: 39 additions & 2 deletions spec/mongoid/collection_snapshot_spec.rb
Expand Up @@ -3,7 +3,7 @@
module Mongoid
describe CollectionSnapshot do

context "creating a snapshot" do
context "creating a basic snapshot" do

let!(:flowers) { Artwork.create(:name => 'Flowers', :artist => 'Andy Warhol', :price => 3000000) }
let!(:guns) { Artwork.create(:name => 'Guns', :artist => 'Andy Warhol', :price => 1000000) }
Expand All @@ -12,11 +12,13 @@ module Mongoid
it "returns nil if no snapshot has been created" do
AverageArtistPrice.latest.should be_nil
end

it "runs the build method on creation" do
snapshot = AverageArtistPrice.create
snapshot.average_price('Andy Warhol').should == 2000000
snapshot.average_price('Damien Hirst').should == 1500000
end

it "returns the most recent snapshot through the latest methods" do
first = AverageArtistPrice.create
first.should == AverageArtistPrice.latest
Expand All @@ -31,12 +33,47 @@ module Mongoid
third = AverageArtistPrice.create
AverageArtistPrice.latest.should == third
end

it "should only maintain at most two of the latest snapshots to support its calculations" do
AverageArtistPrice.create
10.times do
AverageArtistPrice.create
AverageArtistPrice.count.should <= 2
AverageArtistPrice.count.should == 2
end
end

end

context "creating a snapshot containing multiple collections" do

it "populates several collections and allows them to be queried" do
MultiCollectionSnapshot.latest.should be_nil
10.times { MultiCollectionSnapshot.create }
MultiCollectionSnapshot.latest.get_names.should == "foo!bar!baz!"
end

it "safely cleans up all collections used by the snapshot" do
# Create some collections with names close to the snapshots we'll create
Mongoid.master["#{MultiCollectionSnapshot.collection.name}.do.not_delete"].insert({'a' => 1})
Mongoid.master["#{MultiCollectionSnapshot.collection.name}.snapshorty"].insert({'a' => 1})
Mongoid.master["#{MultiCollectionSnapshot.collection.name}.hello.1"].insert({'a' => 1})

MultiCollectionSnapshot.create
before_create = Mongoid.master.collections.map{ |c| c.name }
before_create.length.should > 0

sleep(1)
MultiCollectionSnapshot.create
after_create = Mongoid.master.collections.map{ |c| c.name }
collections_created = (after_create - before_create).sort
collections_created.length.should == 3

MultiCollectionSnapshot.latest.destroy
after_destroy = Mongoid.master.collections.map{ |c| c.name }
collections_destroyed = (after_create - after_destroy).sort
collections_created.should == collections_destroyed
end

end

end
Expand Down
17 changes: 0 additions & 17 deletions spec/mongoid/collection_snapshot_spec.rb~

This file was deleted.

0 comments on commit 09bd151

Please sign in to comment.