mongo_sequence 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source :rubygems
2
+
3
+ MONGO_VERS = '1.6.1'
4
+
5
+ gem 'mongo', MONGO_VERS
6
+ gem 'bson_ext', MONGO_VERS
7
+
8
+ gemspec
@@ -0,0 +1,87 @@
1
+ # MongoSequence
2
+
3
+ MongoSequence provides light-weight, robust sequences in MongoDB. They are useful for auto-incrementing or counting. Compatible with any Mongo ODM.
4
+
5
+ MongoSequence creates named sequences in a "sequences" collection in your database and atomically increments and returns the counter on them using Mongo's findAndModify command. You won't have collisions--two processes trying to increment at the same time will alway get different numbers.
6
+
7
+ ## Usage
8
+
9
+ Install with [Bundler](http://gembundler.com/):
10
+
11
+ ``` ruby
12
+ gem "mongo_sequence"
13
+ ```
14
+
15
+ Install without Bundler:
16
+
17
+ gem install mongo_sequence --no-ri --no-rdoc
18
+
19
+ If you're _not_ using MongoMapper or Mongoid, you'll have to tell MongoSequence what database to use:
20
+
21
+ ``` ruby
22
+ MongoSequence.database = Mongo::Connection.new.db('my_app_development')
23
+ ```
24
+
25
+ Now, increment some sequences:
26
+
27
+ ``` ruby
28
+ MongoSequence[:global].next # => 1
29
+ MongoSequence[:global].next # => 2
30
+
31
+ # get the current value...
32
+ # no guarantees of course on how long it's valid
33
+ # usually you use the return value of #next
34
+ MongoSequence[:global].current # => 2
35
+
36
+ # can also reset sequences if you need to
37
+ MongoSequence[:global] = 100
38
+ MongoSequence[:global].next # => 101
39
+
40
+ # sequences with different names are independent
41
+ MongoSequence[:bluejay].next # => 1
42
+ MongoSequence[:bluejay].next # => 2
43
+ ```
44
+
45
+ Here's how the sequences look in Mongo:
46
+
47
+ ``` ruby
48
+ MongoSequence.collection.find_one(:_id => 'bluejay')
49
+ # =>
50
+ # {
51
+ # "_id" => "bluejay",
52
+ # "current" => 2
53
+ # }
54
+ ```
55
+
56
+ ## Why?
57
+
58
+ Why would anyone need atomically incrementing sequences with unique return values? Well, the most common case is for auto-incrementing id's in Mongo. Here's a MongoMapper example:
59
+
60
+ ``` ruby
61
+ class Peregrine
62
+ include MongoMapper::Document
63
+ key :_id, Integer, :default => lambda { nil }
64
+
65
+ before_create do
66
+ self.id ||= MongoSequence[:peregrine_id].next # for id's unique among Peregrines
67
+ # or
68
+ self.id ||= MongoSequence[:mongo_id].next # for id's unique across the database
69
+ end
70
+ end
71
+ ```
72
+
73
+ ## Help make it better!
74
+
75
+ Need something added? Please [open an issue](https://github.com/brianhempel/mongo_sequence/issues)! Or, even better, code it yourself and send a pull request:
76
+
77
+ # fork it on github, then clone:
78
+ git clone git@github.com:your_username/mongo_sequence.git
79
+ bundle install
80
+ rspec
81
+ # hack away
82
+ git push
83
+ # then make a pull request
84
+
85
+ ## License
86
+
87
+ Authored by Brian Hempel. Public domain, no restrictions.
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,67 @@
1
+ require "mongo"
2
+
3
+ class MongoSequence
4
+
5
+ class << self
6
+ attr_writer :database
7
+
8
+ def database
9
+ return @database if @database
10
+ return MongoMapper.database if defined?(MongoMapper) && MongoMapper.database
11
+ return Mongoid.database if defined?(Mongoid) && Mongoid.database
12
+ end
13
+
14
+ def collection
15
+ database['sequences']
16
+ end
17
+
18
+ def [](name)
19
+ new(name)
20
+ end
21
+
22
+ def []=(name, integer)
23
+ self[name].current = integer
24
+ end
25
+ end
26
+
27
+ attr_reader :name
28
+
29
+ def initialize(name)
30
+ @name = name.to_s
31
+ end
32
+
33
+ def collection
34
+ MongoSequence.collection
35
+ end
36
+
37
+ def next
38
+ current_after_update(:$inc => { :current => 1 })
39
+ end
40
+
41
+ def current
42
+ current_after_update(:$set => {}) # noop that works
43
+ end
44
+
45
+ def current=(integer)
46
+ current_after_update(:current => integer)
47
+ end
48
+
49
+ private
50
+
51
+ def current_after_update(update)
52
+ options = {
53
+ :query => { :_id => name },
54
+ :new => true, # return the modified doc
55
+ :update => update
56
+ }
57
+ collection.find_and_modify(options)['current']
58
+ rescue Mongo::OperationFailure => e
59
+ raise unless e.message =~ /No matching object found/
60
+ init_in_database
61
+ current_after_update(update)
62
+ end
63
+
64
+ def init_in_database
65
+ collection.save({:_id => name, :current => 0}, :safe => true)
66
+ end
67
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "mongo_sequence"
5
+ s.version = '1.0.0'
6
+ s.authors = ["Brian Hempel"]
7
+ s.email = ["plasticchicken@gmail.com"]
8
+ s.homepage = "https://github.com/brianhempel/mongo_sequence"
9
+ s.summary = %q{Light-weight sequences for MongoDB, useful for auto-incrementing or counting. Works with any ODM.}
10
+ s.description = s.summary
11
+
12
+ s.files = `git ls-files`.split("\n")
13
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
15
+ s.require_paths = ["lib"]
16
+
17
+ s.add_dependency "mongo", ">1.1"
18
+
19
+ s.add_development_dependency "rspec"
20
+ s.add_development_dependency "mongo_mapper"
21
+ s.add_development_dependency "mongoid"
22
+ end
@@ -0,0 +1,122 @@
1
+ require 'bundler/setup'
2
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
3
+ require 'mongo_sequence'
4
+ require 'mongo_mapper'
5
+ require 'mongoid'
6
+
7
+ describe MongoSequence do
8
+ before :each do
9
+ MongoMapper.database = nil
10
+ # can't get rid of Mongoid's db :/
11
+ MongoSequence.database = nil
12
+ end
13
+
14
+ context "no ODM" do
15
+ before :each do
16
+ @db = Mongo::Connection.new.db('mongo_sequence_test')
17
+ @db.collections.each(&:remove)
18
+ MongoSequence.database = @db
19
+ end
20
+
21
+ it "returns MongoSequence objects" do
22
+ MongoSequence[:test].class.should == MongoSequence
23
+ end
24
+
25
+ it "starts at 1 for a new sequence" do
26
+ MongoSequence[:test].next.should == 1
27
+ end
28
+
29
+ it "increments every time .next is called" do
30
+ MongoSequence[:test].next
31
+ MongoSequence[:test].next.should == 2
32
+ MongoSequence[:test].next.should == 3
33
+ end
34
+
35
+ it "lets us get the current count" do
36
+ MongoSequence[:test].current.should == 0
37
+ MongoSequence[:test].next
38
+ MongoSequence[:test].current.should == 1
39
+ MongoSequence[:test].current.should == 1 # current should not increment
40
+ end
41
+
42
+ it "pulls current from the database" do
43
+ MongoSequence[:test].current.should == 0
44
+ @db['sequences'].update({ :_id => "test" }, { :current => 100 })
45
+ MongoSequence[:test].current.should == 100
46
+ end
47
+
48
+ it "increments different sequences separately" do
49
+ MongoSequence[:seq1].next
50
+ MongoSequence[:seq1].next.should == 2
51
+ MongoSequence[:seq2].next.should == 1
52
+ MongoSequence[:seq2].next.should == 2
53
+ end
54
+
55
+ it "lets us set the current count" do
56
+ MongoSequence[:test].current = 100
57
+ MongoSequence[:test].current.should == 100
58
+ MongoSequence[:test].next.should == 101
59
+ end
60
+
61
+ it "lets us set the current count with shorthand syntax" do
62
+ MongoSequence[:test] = 100
63
+ MongoSequence[:test].current.should == 100
64
+ MongoSequence[:test].next.should == 101
65
+ end
66
+
67
+ it "accepts strings or symbols for the sequence name" do
68
+ MongoSequence[:test] = 100
69
+ MongoSequence['test'].current.should == 100
70
+ MongoSequence['test'] = 200
71
+ MongoSequence[:test].current.should == 200
72
+ end
73
+
74
+ it "uses the 'sequences' collection" do
75
+ MongoSequence[:seq1].current
76
+ MongoSequence[:seq2].current
77
+ MongoSequence[:seq3].current
78
+ @db['sequences'].count.should == 3
79
+ end
80
+
81
+ it "sets the _id to the sequence name" do
82
+ MongoSequence[:foo].current
83
+ @db['sequences'].find.first['_id'].should == "foo"
84
+ end
85
+
86
+ it "should actually use the database" do
87
+ MongoSequence.collection.save(:_id => "test", :current => 200)
88
+ MongoSequence[:test].current.should == 200
89
+ end
90
+ end
91
+
92
+ context "MongoMapper" do
93
+ before :each do
94
+ MongoMapper.database = "mongo_sequence_test_mm"
95
+ end
96
+
97
+ it "uses MongoMapper's database by default" do
98
+ MongoSequence.database.name.should == "mongo_sequence_test_mm"
99
+ end
100
+
101
+ it "allows overriding the database" do
102
+ MongoSequence.database = Mongo::Connection.new.db('mongo_sequence_test')
103
+ MongoSequence.database.name.should == "mongo_sequence_test"
104
+ end
105
+ end
106
+
107
+ context "Mongoid" do
108
+ before :each do
109
+ # get around Mongoid complaining that my MongoDB is too old :P
110
+ Mongoid.config.from_hash('database' => "mongo_sequence_test_mongoid", 'logger' => false)
111
+ end
112
+
113
+ it "uses Mongoid's database by default" do
114
+ MongoSequence.database.name.should == "mongo_sequence_test_mongoid"
115
+ end
116
+
117
+ it "allows overriding the database" do
118
+ MongoSequence.database = Mongo::Connection.new.db('mongo_sequence_test')
119
+ MongoSequence.database.name.should == "mongo_sequence_test"
120
+ end
121
+ end
122
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongo_sequence
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Brian Hempel
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-04-06 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: mongo
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">"
27
+ - !ruby/object:Gem::Version
28
+ hash: 13
29
+ segments:
30
+ - 1
31
+ - 1
32
+ version: "1.1"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rspec
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: mongo_mapper
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: mongoid
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ type: :development
76
+ version_requirements: *id004
77
+ description: Light-weight sequences for MongoDB, useful for auto-incrementing or counting. Works with any ODM.
78
+ email:
79
+ - plasticchicken@gmail.com
80
+ executables: []
81
+
82
+ extensions: []
83
+
84
+ extra_rdoc_files: []
85
+
86
+ files:
87
+ - .gitignore
88
+ - Gemfile
89
+ - README.md
90
+ - Rakefile
91
+ - lib/mongo_sequence.rb
92
+ - mongo_sequence.gemspec
93
+ - spec/mongo_sequence_spec.rb
94
+ homepage: https://github.com/brianhempel/mongo_sequence
95
+ licenses: []
96
+
97
+ post_install_message:
98
+ rdoc_options: []
99
+
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 3
108
+ segments:
109
+ - 0
110
+ version: "0"
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ hash: 3
117
+ segments:
118
+ - 0
119
+ version: "0"
120
+ requirements: []
121
+
122
+ rubyforge_project:
123
+ rubygems_version: 1.8.21
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: Light-weight sequences for MongoDB, useful for auto-incrementing or counting. Works with any ODM.
127
+ test_files:
128
+ - spec/mongo_sequence_spec.rb