mongoid-collection-snapshot 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +54 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +72 -0
- data/.travis.yml +27 -0
- data/CHANGELOG.md +35 -0
- data/Dangerfile +1 -0
- data/Gemfile +24 -0
- data/LICENSE.txt +20 -0
- data/README.md +223 -0
- data/Rakefile +27 -0
- data/UPGRADING.md +8 -0
- data/lib/mongoid-collection-snapshot.rb +100 -0
- data/lib/mongoid-collection-snapshot/version.rb +5 -0
- data/mongoid-collection-snapshot.gemspec +19 -0
- data/spec/models/artist.rb +7 -0
- data/spec/models/artwork.rb +9 -0
- data/spec/models/average_price.rb +107 -0
- data/spec/models/custom_connection_snapshot.rb +33 -0
- data/spec/models/multi_collection_snapshot.rb +34 -0
- data/spec/models/partner.rb +7 -0
- data/spec/mongoid/collection_snapshot_spec.rb +173 -0
- data/spec/spec_helper.rb +34 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fdda543f7183d461cf4b7068b97c7c9bba6523b9
|
4
|
+
data.tar.gz: a83c53f21498ad03d49089382dd9e519a9451d79
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: de91fdde1b8c8b6457cdcecb4e91eec67f4eb3fddf0f96702092ef3a8df9e4747401571d1475478e1f82e28b33133287df422fce178ac245c238c6474de2be93
|
7
|
+
data.tar.gz: c9682df34fa35b1e444f6f10ccbf28a15517ca141c397d689493aca9de664c0c524cebb11c1a58f7a3abafd15fca855ac233fa3c1d538e408c3fc494392558c4
|
data/.gitignore
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# rcov generated
|
2
|
+
coverage
|
3
|
+
|
4
|
+
# rdoc generated
|
5
|
+
rdoc
|
6
|
+
|
7
|
+
# yard generated
|
8
|
+
doc
|
9
|
+
.yardoc
|
10
|
+
|
11
|
+
# bundler
|
12
|
+
.bundle
|
13
|
+
|
14
|
+
# bundler
|
15
|
+
pkg
|
16
|
+
|
17
|
+
# Gemfile lock
|
18
|
+
Gemfile.lock
|
19
|
+
|
20
|
+
# rvm
|
21
|
+
.rvmrc
|
22
|
+
|
23
|
+
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
24
|
+
#
|
25
|
+
# * Create a file at ~/.gitignore
|
26
|
+
# * Include files you want ignored
|
27
|
+
# * Run: git config --global core.excludesfile ~/.gitignore
|
28
|
+
#
|
29
|
+
# After doing this, these files will be ignored in all your git projects,
|
30
|
+
# saving you from having to 'pollute' every project you touch with them
|
31
|
+
#
|
32
|
+
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
33
|
+
#
|
34
|
+
# For MacOS:
|
35
|
+
#
|
36
|
+
#.DS_Store
|
37
|
+
|
38
|
+
# For TextMate
|
39
|
+
*.tmproj
|
40
|
+
tmtags
|
41
|
+
|
42
|
+
# For emacs:
|
43
|
+
*~
|
44
|
+
\#*
|
45
|
+
.\#*
|
46
|
+
|
47
|
+
# For vim:
|
48
|
+
*.swp
|
49
|
+
|
50
|
+
# For redcar:
|
51
|
+
.redcar
|
52
|
+
|
53
|
+
# For rubinius:
|
54
|
+
*.rbc
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2017-01-20 11:17:06 -0500 using RuboCop version 0.47.1.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 4
|
10
|
+
# Configuration parameters: Include.
|
11
|
+
# Include: **/Gemfile, **/gems.rb
|
12
|
+
Bundler/DuplicatedGem:
|
13
|
+
Exclude:
|
14
|
+
- 'Gemfile'
|
15
|
+
|
16
|
+
# Offense count: 1
|
17
|
+
Lint/IneffectiveAccessModifier:
|
18
|
+
Exclude:
|
19
|
+
- 'spec/models/custom_connection_snapshot.rb'
|
20
|
+
|
21
|
+
# Offense count: 1
|
22
|
+
# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods.
|
23
|
+
Lint/UselessAccessModifier:
|
24
|
+
Exclude:
|
25
|
+
- 'spec/models/custom_connection_snapshot.rb'
|
26
|
+
|
27
|
+
# Offense count: 3
|
28
|
+
Metrics/AbcSize:
|
29
|
+
Max: 34
|
30
|
+
|
31
|
+
# Offense count: 5
|
32
|
+
# Configuration parameters: CountComments, ExcludedMethods.
|
33
|
+
Metrics/BlockLength:
|
34
|
+
Max: 145
|
35
|
+
|
36
|
+
# Offense count: 1
|
37
|
+
Metrics/CyclomaticComplexity:
|
38
|
+
Max: 8
|
39
|
+
|
40
|
+
# Offense count: 42
|
41
|
+
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
42
|
+
# URISchemes: http, https
|
43
|
+
Metrics/LineLength:
|
44
|
+
Max: 192
|
45
|
+
|
46
|
+
# Offense count: 3
|
47
|
+
# Configuration parameters: CountComments.
|
48
|
+
Metrics/MethodLength:
|
49
|
+
Max: 31
|
50
|
+
|
51
|
+
# Offense count: 1
|
52
|
+
# Configuration parameters: CountComments.
|
53
|
+
Metrics/ModuleLength:
|
54
|
+
Max: 147
|
55
|
+
|
56
|
+
# Offense count: 1
|
57
|
+
Metrics/PerceivedComplexity:
|
58
|
+
Max: 10
|
59
|
+
|
60
|
+
# Offense count: 2
|
61
|
+
Style/Documentation:
|
62
|
+
Exclude:
|
63
|
+
- 'spec/**/*'
|
64
|
+
- 'test/**/*'
|
65
|
+
- 'lib/mongoid-collection-snapshot.rb'
|
66
|
+
|
67
|
+
# Offense count: 1
|
68
|
+
# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms.
|
69
|
+
# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
|
70
|
+
Style/FileName:
|
71
|
+
Exclude:
|
72
|
+
- 'lib/mongoid-collection-snapshot.rb'
|
data/.travis.yml
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
sudo: false
|
2
|
+
|
3
|
+
matrix:
|
4
|
+
include:
|
5
|
+
- rvm: 2.3.1
|
6
|
+
env:
|
7
|
+
- MONGOID_VERSION=6
|
8
|
+
before_script:
|
9
|
+
- bundle exec danger
|
10
|
+
- rvm: 2.3.1
|
11
|
+
env:
|
12
|
+
- MONGOID_VERSION=5
|
13
|
+
- rvm: 2.2
|
14
|
+
env:
|
15
|
+
- MONGOID_VERSION=4
|
16
|
+
- rvm: 2.1
|
17
|
+
env:
|
18
|
+
- MONGOID_VERSION=3
|
19
|
+
|
20
|
+
services: mongodb
|
21
|
+
|
22
|
+
addons:
|
23
|
+
apt:
|
24
|
+
sources:
|
25
|
+
- mongodb-3.2-precise
|
26
|
+
packages:
|
27
|
+
- mongodb-org-server
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
### 1.3.0 (1/20/2017)
|
2
|
+
|
3
|
+
* [#2](https://github.com/mongoid/mongoid-collection-snapshot/pull/2): Support for Mongoid 6 - [@dblock](https://github.com/dblock).
|
4
|
+
* [#1](https://github.com/mongoid/mongoid-collection-snapshot/pull/1): Gem renamed to mongoid-collection-snapshot and forked to the [mongoid org](https://github.com/mongoid) - [@dblock](https://github.com/dblock).
|
5
|
+
* [#18](https://github.com/aaw/mongoid_collection_snapshot/pull/18): Fix: classes that include `Mongoid::CollectionSnapshot` can be inherited - [@dblock](https://github.com/dblock).
|
6
|
+
* [#3](https://github.com/mongoid/mongoid-collection-snapshot/pull/3): Added Danger, PR linter - [@dblock](https://github.com/dblock).
|
7
|
+
* [#4](https://github.com/mongoid/mongoid-collection-snapshot/pull/4): Added RuboCop - [@dblock](https://github.com/dblock).
|
8
|
+
|
9
|
+
### 1.2.0
|
10
|
+
|
11
|
+
* [#14](https://github.com/aaw/mongoid_collection_snapshot/pull/14): Compatibility with Mongoid 5 - [@dblock](https://github.com/dblock).
|
12
|
+
|
13
|
+
### 1.1.0
|
14
|
+
|
15
|
+
* [#11](https://github.com/aaw/mongoid_collection_snapshot/pull/10): Added support for accessing snapshot collection documents via Mongoid::CollectionSnapshot#documents - [@dblock](https://github.com/dblock).
|
16
|
+
* [#11](https://github.com/aaw/mongoid_collection_snapshot/pull/11): Upgraded RSpec - [@dblock](https://github.com/dblock).
|
17
|
+
|
18
|
+
### 1.0.1
|
19
|
+
|
20
|
+
* [#8](https://github.com/aaw/mongoid_collection_snapshot/pull/8): Fixed .gemspec for compatibility with Mongoid 4.x - [@dblock](https://github.com/dblock).
|
21
|
+
|
22
|
+
### 1.0.0
|
23
|
+
|
24
|
+
* Expose `snapshot_session` for custom snapshot storage - [@joeyAghion](https://github.com/joeyAghion).
|
25
|
+
* Compatibility with Mongoid 4.x - [@dblock](https://github.com/dblock).
|
26
|
+
|
27
|
+
### 0.2.0
|
28
|
+
|
29
|
+
* Added ability to maintain a snapshot of multiple collections atomically - [@aaw](https://github.com/aaw).
|
30
|
+
* Added support for [Mongoid 3.0](https://github.com/mongoid/mongoid) - [@dblock](https://github.com/dblock).
|
31
|
+
* Relaxed version limitations of [mongoid_slug](https://github.com/digitalplaywright/mongoid-slug) - [@dblock](https://github.com/dblock).
|
32
|
+
|
33
|
+
### 0.1.0
|
34
|
+
|
35
|
+
* Initial public release - [@aaw](https://github.com/aaw).
|
data/Dangerfile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
danger.import_dangerfile(gem: "mongoid-danger")
|
data/Gemfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
source 'http://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
case version = ENV['MONGOID_VERSION'] || '6.0'
|
6
|
+
when /^6/
|
7
|
+
gem 'mongoid', '~> 6.0'
|
8
|
+
when /^5/
|
9
|
+
gem 'mongoid', '~> 5.0'
|
10
|
+
when /^4/
|
11
|
+
gem 'mongoid', '~> 4.0'
|
12
|
+
when /^3/
|
13
|
+
gem 'mongoid', '~> 3.1'
|
14
|
+
else
|
15
|
+
gem 'mongoid', version
|
16
|
+
end
|
17
|
+
|
18
|
+
group :development, :test do
|
19
|
+
gem 'mongoid-danger', '~> 0.1.1'
|
20
|
+
gem 'rake'
|
21
|
+
gem 'rspec', '~> 3.1'
|
22
|
+
gem 'rubocop', '0.47.1'
|
23
|
+
gem 'timecop'
|
24
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011-2017 Art.sy Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
Mongoid Collection Snapshot
|
2
|
+
===========================
|
3
|
+
|
4
|
+
Easy maintenance of collections of processed data in MongoDB with Mongoid 3, 4, 5 and 6.
|
5
|
+
|
6
|
+
This is a forked, renamed, maintained and supported version of [mongoid_collection_snapshot](https://github.com/aaw/mongoid_collection_snapshot).
|
7
|
+
|
8
|
+
[![Gem Version](https://badge.fury.io/rb/mongoid-collection-snapshot.svg)](https://badge.fury.io/rb/mongoid-collection-snapshot)
|
9
|
+
[![Build Status](https://travis-ci.org/mongoid/mongoid-collection-snapshot.svg)](https://travis-ci.org/mongoid/mongoid-collection-snapshot)
|
10
|
+
|
11
|
+
Quick example:
|
12
|
+
--------------
|
13
|
+
|
14
|
+
Suppose that you have a Mongoid model called `Artwork`, stored in a MongoDB collection called `artworks` and the underlying documents look something like:
|
15
|
+
|
16
|
+
{ name: 'Flowers', artist: 'Andy Warhol', price: 3000000 }
|
17
|
+
|
18
|
+
From time to time, your system runs a map/reduce job to compute the average price of each artist's works, resulting in a collection called `artist_average_price` that contains documents that look like:
|
19
|
+
|
20
|
+
{ _id: { artist: 'Andy Warhol' }, value: { price: 1500000 } }
|
21
|
+
|
22
|
+
If your system wants to maintain and use this average price data, it has to do so at the level of raw MongoDB operations, since map/reduce result documents don't map well to models in Mongoid.
|
23
|
+
Furthermore, even though map/reduce jobs can take some time to run, you probably want the entire `artist_average_price` collection populated atomically from the point of view of your system, since otherwise you don't ever know the state of the data in the collection - you could access it in the middle of a map/reduce and get partial, incorrect results.
|
24
|
+
|
25
|
+
A mongoid-collection-snapshot solves this problem by providing an atomic view of collections of data like map/reduce results that live outside of Mongoid.
|
26
|
+
|
27
|
+
In the example above, we'd set up our average artist price collection like:
|
28
|
+
|
29
|
+
``` ruby
|
30
|
+
class AverageArtistPrice
|
31
|
+
include Mongoid::CollectionSnapshot
|
32
|
+
|
33
|
+
def build
|
34
|
+
|
35
|
+
map = <<-EOS
|
36
|
+
function() {
|
37
|
+
emit({ artist_id: this['artist_id']}, { count: 1, sum: this['price'] })
|
38
|
+
}
|
39
|
+
EOS
|
40
|
+
|
41
|
+
reduce = <<-EOS
|
42
|
+
function(key, values) {
|
43
|
+
var sum = 0;
|
44
|
+
var count = 0;
|
45
|
+
values.forEach(function(value) {
|
46
|
+
sum += value['sum'];
|
47
|
+
count += value['count'];
|
48
|
+
});
|
49
|
+
return({ count: count, sum: sum });
|
50
|
+
}
|
51
|
+
EOS
|
52
|
+
|
53
|
+
Artwork.map_reduce(map, reduce).out(inline: 1).each do |doc|
|
54
|
+
collection_snapshot.insert_one(
|
55
|
+
artist_id: doc['_id']['artist_id'],
|
56
|
+
count: doc['value']['count'],
|
57
|
+
sum: doc['value']['sum']
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
```
|
64
|
+
|
65
|
+
Now, if you want to schedule a recomputation, just call `AverageArtistPrice.create`. You can define other methods on collection snapshots.
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
class AverageArtistPrice
|
69
|
+
...
|
70
|
+
|
71
|
+
def average_price(artist_name)
|
72
|
+
artist = Artist.where(name: artist_name).first
|
73
|
+
doc = collection_snapshot.where(artist_id: artist.id).first
|
74
|
+
doc['sum'] / doc['count']
|
75
|
+
end
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
The latest snapshot is always available as `AverageArtistPrice.latest`, so you can write code like:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
warhol_expected_price = AverageArtistPrice.latest.average_price('Andy Warhol')
|
83
|
+
```
|
84
|
+
|
85
|
+
And always be sure that you'll never be looking at partial results. The only thing you need to do to hook into mongoid-collection-snapshot is implement the method `build`, which populates the collection snapshot and any indexes you need.
|
86
|
+
|
87
|
+
By default, mongoid-collection-snapshot maintains the most recent two snapshots computed any given time.
|
88
|
+
|
89
|
+
ery Snapshot Data with Mongoid
|
90
|
+
--------------------------------
|
91
|
+
|
92
|
+
You can do better than the average price example above and define first-class models for your collection snapshot data, then access them as any other Mongoid collection via collection snapshot's `.documents` method.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
class AverageArtistPrice
|
96
|
+
document do
|
97
|
+
belongs_to :artist, inverse_of: nil
|
98
|
+
field :sum, type: Integer
|
99
|
+
field :count, type: Integer
|
100
|
+
end
|
101
|
+
|
102
|
+
def average_price(artist_name)
|
103
|
+
artist = Artist.where(name: artist_name).first
|
104
|
+
doc = documents.where(artist: artist).first
|
105
|
+
doc.sum / doc.count
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
Another example iterates through all latest artist price averages.
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
AverageArtistPrice.latest.documents.each do |doc|
|
114
|
+
puts "#{doc.artist.name}: #{doc.sum / doc.count}"
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
Multi-collection snapshots
|
119
|
+
--------------------------
|
120
|
+
|
121
|
+
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:
|
122
|
+
|
123
|
+
``` ruby
|
124
|
+
class ArtistStats
|
125
|
+
include Mongoid::CollectionSnapshot
|
126
|
+
|
127
|
+
def build
|
128
|
+
# ...
|
129
|
+
# define map/reduce for average and max aggregations
|
130
|
+
# ...
|
131
|
+
Mongoid.default_session.command('mapreduce' => 'artworks', map: map_avg, reduce: reduce_avg, out: collection_snapshot('average'))
|
132
|
+
Mongoid.default_session.command('mapreduce' => 'artworks', map: map_max, reduce: reduce_max, out: collection_snapshot('max'))
|
133
|
+
end
|
134
|
+
|
135
|
+
def average_price(artist)
|
136
|
+
doc = collection_snapshot('average').find('_id.artist' => artist).first
|
137
|
+
doc['value']['sum'] / doc['value']['count']
|
138
|
+
end
|
139
|
+
|
140
|
+
def max_price(artist)
|
141
|
+
doc = collection_snapshot('max').find('_id.artist' => artist).first
|
142
|
+
doc['value']['max']
|
143
|
+
end
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
Specify the name of the collection to define first class Mongoid models.
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
class ArtistStats
|
151
|
+
document('average') do
|
152
|
+
field :value, type: Hash
|
153
|
+
end
|
154
|
+
|
155
|
+
document('max') do
|
156
|
+
field :value, type: Hash
|
157
|
+
end
|
158
|
+
end
|
159
|
+
```
|
160
|
+
|
161
|
+
Access these by name.
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
ArtistStats.latest.documents('average')
|
165
|
+
ArtistStats.latest.documents('max')
|
166
|
+
```
|
167
|
+
|
168
|
+
If fields across multiple collection snapshots are identical, a single default `document` is sufficient.
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
class ArtistStats
|
172
|
+
document do
|
173
|
+
field :value, type: Hash
|
174
|
+
end
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
Custom database connections
|
179
|
+
---------------------------
|
180
|
+
|
181
|
+
Your class can specify a custom database for storage of collection snapshots by overriding the `snapshot_session` instance method. In this example, we memoize the connection at the class level to avoid creating many separate connection instances.
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
class ArtistStats
|
185
|
+
include Mongoid::CollectionSnapshot
|
186
|
+
|
187
|
+
def build
|
188
|
+
# ...
|
189
|
+
end
|
190
|
+
|
191
|
+
def snapshot_session
|
192
|
+
self.class.snapshot_session
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.snapshot_session
|
196
|
+
@@snapshot_session ||= Mongo::Client.new('mongodb://localhost:27017').tap do |c|
|
197
|
+
c.use :alternate_db
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
Another common way of configuring this is through mongoid.yml.
|
204
|
+
|
205
|
+
```yaml
|
206
|
+
development:
|
207
|
+
sessions:
|
208
|
+
default:
|
209
|
+
database: dev_data
|
210
|
+
imports:
|
211
|
+
database: dev_imports
|
212
|
+
```
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
def snapshot_session
|
216
|
+
Mongoid.session('imports')
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
220
|
+
License
|
221
|
+
=======
|
222
|
+
|
223
|
+
MIT License, see [LICENSE.txt](LICENSE.txt) for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
require 'bundler'
|
6
|
+
require 'bundler/gem_tasks'
|
7
|
+
|
8
|
+
begin
|
9
|
+
Bundler.setup(:default, :development)
|
10
|
+
rescue Bundler::BundlerError => e
|
11
|
+
$stderr.puts e.message
|
12
|
+
$stderr.puts 'Run `bundle install` to install missing gems'
|
13
|
+
exit e.status_code
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'rake'
|
17
|
+
|
18
|
+
require 'rspec/core'
|
19
|
+
require 'rspec/core/rake_task'
|
20
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
21
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'rubocop/rake_task'
|
25
|
+
RuboCop::RakeTask.new(:rubocop)
|
26
|
+
|
27
|
+
task default: [:rubocop, :spec]
|
data/UPGRADING.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
### Upgrading from 0.1.0 to >= 0.2.0
|
2
|
+
|
3
|
+
When upgrading from 0.1.0 (pre-Mongoid 3.0) to 0.2.0 (Mongoid 3.x), you'll need to upgrade any existing snapshots created by mongoid_collection_snapshot 0.1.0 in your database before they're usable. You can do this by renaming the 'workspace_slug' attribute to 'slug' in MongoDB after upgrading. For example, to upgrade the snapshot class "MySnapshot", you'd enter the following at the mongo shell.
|
4
|
+
|
5
|
+
```
|
6
|
+
db.my_snapshot.rename({ 'workspace_slug' : { '$exists' : true } }, {'$rename' : {'workspace_slug' : 'slug' } })
|
7
|
+
```
|
8
|
+
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'mongoid-collection-snapshot/version'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module CollectionSnapshot
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
DEFAULT_COLLECTION_KEY_NAME = '*'.freeze
|
8
|
+
|
9
|
+
included do
|
10
|
+
require 'mongoid_slug'
|
11
|
+
|
12
|
+
include Mongoid::Document
|
13
|
+
include Mongoid::Timestamps::Created
|
14
|
+
include Mongoid::Slug
|
15
|
+
|
16
|
+
field :workspace_basename, default: 'snapshot'
|
17
|
+
slug :workspace_basename
|
18
|
+
|
19
|
+
field :max_collection_snapshot_instances, default: 2
|
20
|
+
|
21
|
+
before_create :build
|
22
|
+
after_create :ensure_at_most_two_instances_exist
|
23
|
+
before_destroy :drop_snapshot_collections
|
24
|
+
|
25
|
+
class_attribute :document_blocks
|
26
|
+
class_attribute :document_classes
|
27
|
+
|
28
|
+
# Mongoid documents on this snapshot.
|
29
|
+
def documents(name = nil)
|
30
|
+
self.class.document_classes ||= {}
|
31
|
+
class_name = "#{self.class.name}#{id}#{name}".underscore.camelize
|
32
|
+
key = "#{class_name}-#{name || DEFAULT_COLLECTION_KEY_NAME}"
|
33
|
+
self.class.document_classes[key] ||= begin
|
34
|
+
document_block = document_blocks[name || DEFAULT_COLLECTION_KEY_NAME] if document_blocks
|
35
|
+
collection_name = collection_snapshot(name).name
|
36
|
+
klass = Class.new do
|
37
|
+
include Mongoid::Document
|
38
|
+
if Mongoid::Compatibility::Version.mongoid5?
|
39
|
+
cattr_accessor :mongo_client
|
40
|
+
else
|
41
|
+
cattr_accessor :mongo_session
|
42
|
+
end
|
43
|
+
instance_eval(&document_block) if document_block
|
44
|
+
store_in collection: collection_name
|
45
|
+
end
|
46
|
+
if Mongoid::Compatibility::Version.mongoid6?
|
47
|
+
PersistenceContext.set(klass, database: snapshot_session.database.name)
|
48
|
+
elsif Mongoid::Compatibility::Version.mongoid5?
|
49
|
+
klass.mongo_client = snapshot_session
|
50
|
+
else
|
51
|
+
klass.mongo_session = snapshot_session
|
52
|
+
end
|
53
|
+
Object.const_set(class_name, klass)
|
54
|
+
klass
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module ClassMethods
|
60
|
+
def latest
|
61
|
+
order_by([[:created_at, :desc]]).first
|
62
|
+
end
|
63
|
+
|
64
|
+
def document(name = nil, &block)
|
65
|
+
self.document_blocks ||= {}
|
66
|
+
self.document_blocks[name || DEFAULT_COLLECTION_KEY_NAME] = block
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def collection_snapshot(name = nil)
|
71
|
+
if name
|
72
|
+
snapshot_session["#{collection.name}.#{name}.#{slug}"]
|
73
|
+
else
|
74
|
+
snapshot_session["#{collection.name}.#{slug}"]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def drop_snapshot_collections
|
79
|
+
collections = Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6? ? snapshot_session.database.collections : snapshot_session.collections
|
80
|
+
collections.each do |collection|
|
81
|
+
collection.drop if collection.name =~ /^#{self.collection.name}\.([^\.]+\.)?#{slug}$/
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Since we should always be using the latest instance of this class, this method is
|
86
|
+
# called after each save - making sure only at most two instances exists should be
|
87
|
+
# sufficient to ensure that this data can be rebuilt live without corrupting any
|
88
|
+
# existing computations that might have a handle to the previous "latest" instance.
|
89
|
+
def ensure_at_most_two_instances_exist
|
90
|
+
all_instances = self.class.order_by([[:created_at, :desc]]).to_a
|
91
|
+
return unless all_instances.length > max_collection_snapshot_instances
|
92
|
+
all_instances[max_collection_snapshot_instances..-1].each(&:destroy)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Override to supply custom database connection for snapshots
|
96
|
+
def snapshot_session
|
97
|
+
Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6? ? Mongoid.default_client : Mongoid.default_session
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
2
|
+
require 'mongoid-collection-snapshot/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'mongoid-collection-snapshot'
|
6
|
+
s.version = Mongoid::CollectionSnapshot::VERSION
|
7
|
+
s.authors = ['Aaron Windsor']
|
8
|
+
s.email = 'aaron.windsor@gmail.com'
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.required_rubygems_version = '>= 1.3.6'
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
s.require_paths = ['lib']
|
13
|
+
s.homepage = 'http://github.com/mongoid/mongoid-collection-snapshot'
|
14
|
+
s.licenses = ['MIT']
|
15
|
+
s.summary = 'Easy maintenence of collections of processed data in MongoDB with the Mongoid ODM.'
|
16
|
+
s.add_dependency 'mongoid', '>= 3.0'
|
17
|
+
s.add_dependency 'mongoid-compatibility'
|
18
|
+
s.add_dependency 'mongoid-slug'
|
19
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
class AveragePrice
|
2
|
+
include Mongoid::CollectionSnapshot
|
3
|
+
end
|
4
|
+
|
5
|
+
class AverageArtistPrice < AveragePrice
|
6
|
+
document do
|
7
|
+
belongs_to :artist, inverse_of: nil
|
8
|
+
field :sum, type: Integer
|
9
|
+
field :count, type: Integer
|
10
|
+
end
|
11
|
+
|
12
|
+
def build
|
13
|
+
map = <<-EOS
|
14
|
+
function() {
|
15
|
+
emit({ artist_id: this['artist_id']}, { count: 1, sum: this['price'] })
|
16
|
+
}
|
17
|
+
EOS
|
18
|
+
|
19
|
+
reduce = <<-EOS
|
20
|
+
function(key, values) {
|
21
|
+
var sum = 0;
|
22
|
+
var count = 0;
|
23
|
+
values.forEach(function(value) {
|
24
|
+
sum += value['sum'];
|
25
|
+
count += value['count'];
|
26
|
+
});
|
27
|
+
return({ count: count, sum: sum });
|
28
|
+
}
|
29
|
+
EOS
|
30
|
+
|
31
|
+
Artwork.map_reduce(map, reduce).out(inline: 1).each do |doc|
|
32
|
+
if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
|
33
|
+
collection_snapshot.insert_one(
|
34
|
+
artist_id: doc['_id']['artist_id'],
|
35
|
+
count: doc['value']['count'],
|
36
|
+
sum: doc['value']['sum']
|
37
|
+
)
|
38
|
+
else
|
39
|
+
collection_snapshot.insert(
|
40
|
+
artist_id: doc['_id']['artist_id'],
|
41
|
+
count: doc['value']['count'],
|
42
|
+
sum: doc['value']['sum']
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def average_price(artist_name)
|
49
|
+
artist = Artist.where(name: artist_name).first
|
50
|
+
raise 'missing artist' unless artist
|
51
|
+
doc = documents.where(artist: artist).first
|
52
|
+
raise 'missing record' unless doc
|
53
|
+
doc.sum / doc.count
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class AveragePartnerPrice < AveragePrice
|
58
|
+
document do
|
59
|
+
belongs_to :partner, inverse_of: nil
|
60
|
+
field :sum, type: Integer
|
61
|
+
field :count, type: Integer
|
62
|
+
end
|
63
|
+
|
64
|
+
def build
|
65
|
+
map = <<-EOS
|
66
|
+
function() {
|
67
|
+
emit({ partner_id: this['partner_id']}, { count: 1, sum: this['price'] })
|
68
|
+
}
|
69
|
+
EOS
|
70
|
+
|
71
|
+
reduce = <<-EOS
|
72
|
+
function(key, values) {
|
73
|
+
var sum = 0;
|
74
|
+
var count = 0;
|
75
|
+
values.forEach(function(value) {
|
76
|
+
sum += value['sum'];
|
77
|
+
count += value['count'];
|
78
|
+
});
|
79
|
+
return({ count: count, sum: sum });
|
80
|
+
}
|
81
|
+
EOS
|
82
|
+
|
83
|
+
Artwork.map_reduce(map, reduce).out(inline: 1).each do |doc|
|
84
|
+
if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
|
85
|
+
collection_snapshot.insert_one(
|
86
|
+
partner_id: doc['_id']['partner_id'],
|
87
|
+
count: doc['value']['count'],
|
88
|
+
sum: doc['value']['sum']
|
89
|
+
)
|
90
|
+
else
|
91
|
+
collection_snapshot.insert(
|
92
|
+
partner_id: doc['_id']['partner_id'],
|
93
|
+
count: doc['value']['count'],
|
94
|
+
sum: doc['value']['sum']
|
95
|
+
)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def average_price(partner_name)
|
101
|
+
partner = Partner.where(name: partner_name).first
|
102
|
+
raise 'missing partner' unless partner
|
103
|
+
doc = documents.where(partner: partner).first
|
104
|
+
raise 'missing record' unless doc
|
105
|
+
doc.sum / doc.count
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class CustomConnectionSnapshot
|
2
|
+
include Mongoid::CollectionSnapshot
|
3
|
+
|
4
|
+
def self.snapshot_session
|
5
|
+
@snapshot_session ||= new_snapshot_session
|
6
|
+
end
|
7
|
+
|
8
|
+
def snapshot_session
|
9
|
+
self.class.snapshot_session
|
10
|
+
end
|
11
|
+
|
12
|
+
def build
|
13
|
+
if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
|
14
|
+
collection_snapshot.insert_one('name' => 'foo')
|
15
|
+
collection_snapshot('foo').insert_one('name' => 'bar')
|
16
|
+
else
|
17
|
+
collection_snapshot.insert('name' => 'foo')
|
18
|
+
collection_snapshot('foo').insert('name' => 'bar')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def self.new_snapshot_session
|
25
|
+
if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
|
26
|
+
Mongo::Client.new('mongodb://localhost:27017/snapshot_test')
|
27
|
+
else
|
28
|
+
Moped::Session.new(['127.0.0.1:27017']).tap do |session|
|
29
|
+
session.use :snapshot_test
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class MultiCollectionSnapshot
|
2
|
+
include Mongoid::CollectionSnapshot
|
3
|
+
|
4
|
+
document('foo') do
|
5
|
+
field :name, type: String
|
6
|
+
field :count, type: Integer
|
7
|
+
end
|
8
|
+
|
9
|
+
document('bar') do
|
10
|
+
field :name, type: String
|
11
|
+
field :number, type: Integer
|
12
|
+
end
|
13
|
+
|
14
|
+
document('baz') do
|
15
|
+
field :name, type: String
|
16
|
+
field :digit, type: Integer
|
17
|
+
end
|
18
|
+
|
19
|
+
def build
|
20
|
+
if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
|
21
|
+
collection_snapshot('foo').insert_one('name' => 'foo!', count: 1)
|
22
|
+
collection_snapshot('bar').insert_one('name' => 'bar!', number: 2)
|
23
|
+
collection_snapshot('baz').insert_one('name' => 'baz!', digit: 3)
|
24
|
+
else
|
25
|
+
collection_snapshot('foo').insert('name' => 'foo!', count: 1)
|
26
|
+
collection_snapshot('bar').insert('name' => 'bar!', number: 2)
|
27
|
+
collection_snapshot('baz').insert('name' => 'baz!', digit: 3)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def names
|
32
|
+
%w(foo bar baz).map { |x| collection_snapshot(x).find.first['name'] }.join('')
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
describe CollectionSnapshot do
|
5
|
+
it 'has a version' do
|
6
|
+
expect(Mongoid::CollectionSnapshot::VERSION).not_to be_nil
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'creating a basic snapshot' do
|
10
|
+
let!(:gagosian) { Partner.create!(name: 'Gagosian') }
|
11
|
+
let!(:pace) { Partner.create!(name: 'Pace') }
|
12
|
+
let!(:andy_warhol) { Artist.create!(name: 'Andy Warhol') }
|
13
|
+
let!(:damien_hirst) { Artist.create!(name: 'Damien Hirst') }
|
14
|
+
let!(:flowers) { Artwork.create!(name: 'Flowers', partner: gagosian, artist: andy_warhol, price: 3_000_000) }
|
15
|
+
let!(:guns) { Artwork.create!(name: 'Guns', partner: pace, artist: andy_warhol, price: 1_000_000) }
|
16
|
+
let!(:vinblastine) { Artwork.create!(name: 'Vinblastine', partner: gagosian, artist: damien_hirst, price: 1_500_000) }
|
17
|
+
|
18
|
+
it 'returns nil if no snapshot has been created' do
|
19
|
+
expect(AverageArtistPrice.latest).to be_nil
|
20
|
+
expect(AveragePartnerPrice.latest).to be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'runs the build method on creation for average artist price' do
|
24
|
+
snapshot = AverageArtistPrice.create
|
25
|
+
expect(snapshot.average_price('Andy Warhol')).to eq(2_000_000)
|
26
|
+
expect(snapshot.average_price('Damien Hirst')).to eq(1_500_000)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'runs the build method on creation for average partner price' do
|
30
|
+
snapshot = AveragePartnerPrice.create
|
31
|
+
expect(snapshot.average_price('Gagosian')).to eq(2_250_000)
|
32
|
+
expect(snapshot.average_price('Pace')).to eq(1_000_000)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns the most recent snapshot through the latest methods' do
|
36
|
+
first = AverageArtistPrice.create
|
37
|
+
expect(first).to eq(AverageArtistPrice.latest)
|
38
|
+
# "latest" only works up to a resolution of 1 second since it relies on Mongoid::Timestamp. But this
|
39
|
+
# module is meant to snapshot long-running collection creation, so if you need a resolution of less
|
40
|
+
# than a second for "latest" then you're probably using the wrong gem. In tests, sleeping for a second
|
41
|
+
# makes sure we get what we expect.
|
42
|
+
Timecop.travel(1.second.from_now)
|
43
|
+
second = AverageArtistPrice.create
|
44
|
+
expect(AverageArtistPrice.latest).to eq(second)
|
45
|
+
Timecop.travel(1.second.from_now)
|
46
|
+
third = AverageArtistPrice.create
|
47
|
+
expect(AverageArtistPrice.latest).to eq(third)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'maintains at most two of the latest snapshots to support its calculations' do
|
51
|
+
AverageArtistPrice.create
|
52
|
+
10.times do
|
53
|
+
AverageArtistPrice.create
|
54
|
+
expect(AverageArtistPrice.count).to eq(2)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context '#documents' do
|
59
|
+
it 'provides access to a Mongoid collection' do
|
60
|
+
snapshot = AverageArtistPrice.create
|
61
|
+
expect(snapshot.documents.count).to eq 2
|
62
|
+
document = snapshot.documents.where(artist: andy_warhol).first
|
63
|
+
expect(document.artist).to eq andy_warhol
|
64
|
+
expect(document.count).to eq 2
|
65
|
+
expect(document.sum).to eq 4_000_000
|
66
|
+
|
67
|
+
snapshot = AveragePartnerPrice.create
|
68
|
+
expect(snapshot.documents.count).to eq 2
|
69
|
+
document = snapshot.documents.where(partner: pace).first
|
70
|
+
expect(document.partner).to eq pace
|
71
|
+
expect(document.count).to eq 1
|
72
|
+
expect(document.sum).to eq 1_000_000
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'only creates one global class reference' do
|
76
|
+
3.times do
|
77
|
+
index = AverageArtistPrice.create
|
78
|
+
2.times { expect(index.documents.count).to eq 2 }
|
79
|
+
index = AveragePartnerPrice.create
|
80
|
+
2.times { expect(index.documents.count).to eq 2 }
|
81
|
+
end
|
82
|
+
expect(AveragePrice.document_classes).to be nil
|
83
|
+
expect(AverageArtistPrice.document_classes.count).to be >= 3
|
84
|
+
expect(AveragePartnerPrice.document_classes.count).to be >= 3
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'creating a snapshot containing multiple collections' do
|
90
|
+
it 'populates several collections and allows them to be queried' do
|
91
|
+
expect(MultiCollectionSnapshot.latest).to be_nil
|
92
|
+
10.times { MultiCollectionSnapshot.create }
|
93
|
+
expect(MultiCollectionSnapshot.latest.names).to eq('foo!bar!baz!')
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'safely cleans up all collections used by the snapshot' do
|
97
|
+
# Create some collections with names close to the snapshots we'll create
|
98
|
+
if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
|
99
|
+
Mongoid.default_client["#{MultiCollectionSnapshot.collection.name}.do.not_delete"].insert_one('a' => 1)
|
100
|
+
Mongoid.default_client["#{MultiCollectionSnapshot.collection.name}.snapshorty"].insert_one('a' => 1)
|
101
|
+
Mongoid.default_client["#{MultiCollectionSnapshot.collection.name}.hello.1"].insert_one('a' => 1)
|
102
|
+
else
|
103
|
+
Mongoid.default_session["#{MultiCollectionSnapshot.collection.name}.do.not_delete"].insert('a' => 1)
|
104
|
+
Mongoid.default_session["#{MultiCollectionSnapshot.collection.name}.snapshorty"].insert('a' => 1)
|
105
|
+
Mongoid.default_session["#{MultiCollectionSnapshot.collection.name}.hello.1"].insert('a' => 1)
|
106
|
+
end
|
107
|
+
|
108
|
+
MultiCollectionSnapshot.create
|
109
|
+
collections = Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6? ? Mongoid.default_client.database.collections : Mongoid.default_session.collections
|
110
|
+
before_create = collections.map(&:name)
|
111
|
+
expect(before_create.length).to be > 0
|
112
|
+
|
113
|
+
Timecop.travel(1.second.from_now)
|
114
|
+
MultiCollectionSnapshot.create
|
115
|
+
collections = Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6? ? Mongoid.default_client.database.collections : Mongoid.default_session.collections
|
116
|
+
after_create = collections.map(&:name)
|
117
|
+
collections_created = (after_create - before_create).sort
|
118
|
+
expect(collections_created.length).to eq(3)
|
119
|
+
|
120
|
+
MultiCollectionSnapshot.latest.destroy
|
121
|
+
collections = Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6? ? Mongoid.default_client.database.collections : Mongoid.default_session.collections
|
122
|
+
after_destroy = collections.map(&:name)
|
123
|
+
collections_destroyed = (after_create - after_destroy).sort
|
124
|
+
expect(collections_created).to eq(collections_destroyed)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'with a custom snapshot connection' do
|
129
|
+
around(:each) do |example|
|
130
|
+
if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
|
131
|
+
CustomConnectionSnapshot.snapshot_session.database.drop
|
132
|
+
else
|
133
|
+
CustomConnectionSnapshot.snapshot_session.drop
|
134
|
+
end
|
135
|
+
example.run
|
136
|
+
if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
|
137
|
+
CustomConnectionSnapshot.snapshot_session.database.drop
|
138
|
+
else
|
139
|
+
CustomConnectionSnapshot.snapshot_session.drop
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'builds snapshot in custom database' do
|
144
|
+
snapshot = CustomConnectionSnapshot.create
|
145
|
+
[
|
146
|
+
"#{CustomConnectionSnapshot.collection.name}.foo.#{snapshot.slug}",
|
147
|
+
"#{CustomConnectionSnapshot.collection.name}.#{snapshot.slug}"
|
148
|
+
].each do |collection_name|
|
149
|
+
session = Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6? ? Mongoid.default_client : Mongoid.default_session
|
150
|
+
expect(session[collection_name].find.count).to eq(0)
|
151
|
+
expect(CustomConnectionSnapshot.snapshot_session[collection_name].find.count).to eq(1)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context '#documents' do
|
156
|
+
it 'uses the custom session' do
|
157
|
+
if Mongoid::Compatibility::Version.mongoid6?
|
158
|
+
expect(CustomConnectionSnapshot.new.documents.database_name).to eq :snapshot_test
|
159
|
+
elsif Mongoid::Compatibility::Version.mongoid5?
|
160
|
+
expect(CustomConnectionSnapshot.new.documents.mongo_client).to eq CustomConnectionSnapshot.snapshot_session
|
161
|
+
else
|
162
|
+
expect(CustomConnectionSnapshot.new.documents.mongo_session).to eq CustomConnectionSnapshot.snapshot_session
|
163
|
+
end
|
164
|
+
end
|
165
|
+
it 'provides access to a Mongoid collection' do
|
166
|
+
snapshot = CustomConnectionSnapshot.create
|
167
|
+
expect(snapshot.collection_snapshot.find.count).to eq 1
|
168
|
+
expect(snapshot.documents.count).to eq 1
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'rspec'
|
4
|
+
|
5
|
+
require 'mongoid'
|
6
|
+
require 'timecop'
|
7
|
+
|
8
|
+
Mongoid.configure do |config|
|
9
|
+
config.connect_to('mongoid-collection-snapshot_test')
|
10
|
+
end
|
11
|
+
|
12
|
+
require File.expand_path('../../lib/mongoid-collection-snapshot', __FILE__)
|
13
|
+
Dir["#{File.dirname(__FILE__)}/models/**/*.rb"].each { |f| require f }
|
14
|
+
|
15
|
+
require 'mongoid-compatibility'
|
16
|
+
|
17
|
+
RSpec.configure do |c|
|
18
|
+
c.before(:all) do
|
19
|
+
Mongoid.logger.level = Logger::INFO
|
20
|
+
Mongo::Logger.logger.level = Logger::INFO if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
|
21
|
+
end
|
22
|
+
c.before(:each) do
|
23
|
+
Mongoid.purge!
|
24
|
+
end
|
25
|
+
c.after(:all) do
|
26
|
+
if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
|
27
|
+
Mongoid.default_client.database.drop
|
28
|
+
else
|
29
|
+
Mongoid.default_session.drop
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec.configure(&:raise_errors_for_deprecations!)
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongoid-collection-snapshot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aaron Windsor
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mongoid
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mongoid-compatibility
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mongoid-slug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description:
|
56
|
+
email: aaron.windsor@gmail.com
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- ".gitignore"
|
62
|
+
- ".rspec"
|
63
|
+
- ".rubocop.yml"
|
64
|
+
- ".rubocop_todo.yml"
|
65
|
+
- ".travis.yml"
|
66
|
+
- CHANGELOG.md
|
67
|
+
- Dangerfile
|
68
|
+
- Gemfile
|
69
|
+
- LICENSE.txt
|
70
|
+
- README.md
|
71
|
+
- Rakefile
|
72
|
+
- UPGRADING.md
|
73
|
+
- lib/mongoid-collection-snapshot.rb
|
74
|
+
- lib/mongoid-collection-snapshot/version.rb
|
75
|
+
- mongoid-collection-snapshot.gemspec
|
76
|
+
- spec/models/artist.rb
|
77
|
+
- spec/models/artwork.rb
|
78
|
+
- spec/models/average_price.rb
|
79
|
+
- spec/models/custom_connection_snapshot.rb
|
80
|
+
- spec/models/multi_collection_snapshot.rb
|
81
|
+
- spec/models/partner.rb
|
82
|
+
- spec/mongoid/collection_snapshot_spec.rb
|
83
|
+
- spec/spec_helper.rb
|
84
|
+
homepage: http://github.com/mongoid/mongoid-collection-snapshot
|
85
|
+
licenses:
|
86
|
+
- MIT
|
87
|
+
metadata: {}
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 1.3.6
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 2.5.1
|
105
|
+
signing_key:
|
106
|
+
specification_version: 4
|
107
|
+
summary: Easy maintenence of collections of processed data in MongoDB with the Mongoid
|
108
|
+
ODM.
|
109
|
+
test_files: []
|