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.
- data/.gitignore +5 -0
- data/Gemfile +8 -0
- data/README.md +87 -0
- data/Rakefile +1 -0
- data/lib/mongo_sequence.rb +67 -0
- data/mongo_sequence.gemspec +22 -0
- data/spec/mongo_sequence_spec.rb +122 -0
- metadata +128 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|