hekenga 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/README.md +41 -0
- data/Rakefile +11 -0
- data/bin/console +13 -0
- data/bin/setup +8 -0
- data/examples/simple_example.rb +52 -0
- data/exe/hekenga +139 -0
- data/hekenga.gemspec +32 -0
- data/lib/hekenga/base_error.rb +4 -0
- data/lib/hekenga/config.rb +13 -0
- data/lib/hekenga/context.rb +14 -0
- data/lib/hekenga/document_task.rb +38 -0
- data/lib/hekenga/dsl/document_task.rb +53 -0
- data/lib/hekenga/dsl/migration.rb +32 -0
- data/lib/hekenga/dsl/simple_task.rb +14 -0
- data/lib/hekenga/dsl.rb +24 -0
- data/lib/hekenga/failure/cancelled.rb +8 -0
- data/lib/hekenga/failure/error.rb +11 -0
- data/lib/hekenga/failure/validation.rb +9 -0
- data/lib/hekenga/failure/write.rb +11 -0
- data/lib/hekenga/failure.rb +23 -0
- data/lib/hekenga/invalid.rb +8 -0
- data/lib/hekenga/irreversible.rb +8 -0
- data/lib/hekenga/log.rb +56 -0
- data/lib/hekenga/master_process.rb +149 -0
- data/lib/hekenga/migration.rb +464 -0
- data/lib/hekenga/parallel_job.rb +11 -0
- data/lib/hekenga/simple_task.rb +32 -0
- data/lib/hekenga/version.rb +3 -0
- data/lib/hekenga/virtual_method.rb +8 -0
- data/lib/hekenga.rb +59 -0
- metadata +205 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1f8ad94b462c9140e11f83e4f4ad0f0a16a9ab83
|
4
|
+
data.tar.gz: 6be1fcf20d2a9f1b76c826c6624828feba9c5ad5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5dfa4bb023c546776514ebf1cd5818688de1937513ba6d2b847b95475052b6f408fd33f3464fcb150d28808c4b0fb7b6282824f81323391514eedafdbe5e4171
|
7
|
+
data.tar.gz: 6d9fb7bed2f662c107773e17f109374a9767f69e6c2c99546fc90e9a74b5c22fbfde767c1fb47065ec38facd31559876b57fe7937c0fcf2b2352d66c7cf2fe27
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Hekenga
|
2
|
+
|
3
|
+
An attempt at a migration framework for MongoDB that supports parallel document
|
4
|
+
processing via ActiveJob, chained jobs and error recovery.
|
5
|
+
|
6
|
+
**Note that this gem is currently in pre-alpha - assume most things have a high
|
7
|
+
chance of being broken.**
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'hekenga'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install hekenga
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
CLI instructions:
|
28
|
+
|
29
|
+
$ hekenga help
|
30
|
+
|
31
|
+
Migration DSL documentation TBD, for now please look at spec/
|
32
|
+
|
33
|
+
## Development
|
34
|
+
|
35
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
36
|
+
|
37
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
38
|
+
|
39
|
+
## Contributing
|
40
|
+
|
41
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tzar/hekenga.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "hekenga"
|
5
|
+
require "pry"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
Pry.start
|
data/bin/setup
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Stub
|
2
|
+
class MyModel
|
3
|
+
def self.where(*args)
|
4
|
+
self
|
5
|
+
end
|
6
|
+
end
|
7
|
+
# You can stack multiple tasks within one overall logical migration
|
8
|
+
Hekenga.migration do
|
9
|
+
description "Example usage"
|
10
|
+
created "2016-04-03 14:00"
|
11
|
+
|
12
|
+
# Simple tasks have an up and a down and run in one go
|
13
|
+
task "Set foo->bar by default on MyModel" do
|
14
|
+
up do
|
15
|
+
MyModel.all.set(foo: 'bar')
|
16
|
+
end
|
17
|
+
down do
|
18
|
+
MyModel.all.unset(:foo)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Per document tasks run a block of code per document, with the ability to
|
23
|
+
# filter which documents are loaded by:
|
24
|
+
# - scope
|
25
|
+
# - arbitrary block
|
26
|
+
# Jobs can be run in parallel via ActiveJob.
|
27
|
+
# Callbacks can be disabled for the context of the job either globally via
|
28
|
+
# disable_callbacks or specifically via disable_callback, with multiple models
|
29
|
+
# optionally targetted via the `on` param.
|
30
|
+
# A setup block is also provided (this must be able to be run multiple times!)
|
31
|
+
# per_document migrations should be resumable/retryable..
|
32
|
+
# errors should never result in data loss, and should be logged to a migration
|
33
|
+
# output model
|
34
|
+
per_document "Set MyModel.zap to a random number if unset" do
|
35
|
+
scope MyModel.where(zap: nil)
|
36
|
+
parallel!
|
37
|
+
timeless!
|
38
|
+
disable_callback :reindex, on: MyModel
|
39
|
+
|
40
|
+
setup do
|
41
|
+
@max_rand = 100
|
42
|
+
end
|
43
|
+
|
44
|
+
filter do |document|
|
45
|
+
document.zap.nil?
|
46
|
+
end
|
47
|
+
|
48
|
+
up do |document|
|
49
|
+
document.zap = rand(@max_rand)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/exe/hekenga
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
|
5
|
+
if File.exists?(File.expand_path("config/environment.rb"))
|
6
|
+
require File.expand_path("config/environment.rb")
|
7
|
+
end
|
8
|
+
|
9
|
+
require "hekenga"
|
10
|
+
require "thor"
|
11
|
+
|
12
|
+
class HekengaCLI < Thor
|
13
|
+
desc "status", "Show which migrations have run and their status."
|
14
|
+
def status
|
15
|
+
Hekenga.load_all!
|
16
|
+
Hekenga.registry.sort_by {|x| x.stamp}.each do |migration|
|
17
|
+
status = case Hekenga.status(migration)
|
18
|
+
when :running
|
19
|
+
"ACTIVE"
|
20
|
+
when :failed
|
21
|
+
"FAILED"
|
22
|
+
when :complete
|
23
|
+
"COMPLT"
|
24
|
+
else
|
25
|
+
"UN-RUN"
|
26
|
+
end
|
27
|
+
puts "[#{status}] #{migration.to_key}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "run_all!", "Run all migrations that have not yet run, in date order."
|
32
|
+
def run_all!
|
33
|
+
bail_if_errors
|
34
|
+
Hekenga.load_all!
|
35
|
+
Hekenga.registry.sort_by {|x| x.stamp}.each do |migration|
|
36
|
+
migration.perform!
|
37
|
+
bail_if_errors
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "run! PATH_OR_PKEY --test", "Run a migration (optionally in test mode)."
|
42
|
+
option :test, default: false, type: :boolean
|
43
|
+
def run!(path_or_pkey)
|
44
|
+
bail_if_errors
|
45
|
+
migration = load_migration(path_or_pkey)
|
46
|
+
migration.test_mode! if options[:test]
|
47
|
+
migration.perform!
|
48
|
+
if options[:test]
|
49
|
+
if Hekenga::Failure.where(pkey: migration.to_key).any?
|
50
|
+
puts "Logs have been preserved for debugging. To reset migration state run:"
|
51
|
+
puts " hekenga clear! #{path_or_pkey}"
|
52
|
+
else
|
53
|
+
puts "Migration test run completed successfully."
|
54
|
+
clear!(path_or_pkey)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "cancel", "Cancel all active migrations."
|
60
|
+
def cancel
|
61
|
+
Hekenga::Log.where(done: false).set(cancel: true)
|
62
|
+
puts "Sent :cancel to all active hekenga jobs."
|
63
|
+
end
|
64
|
+
|
65
|
+
desc "cleanup", "Remove any failure logs."
|
66
|
+
def cleanup
|
67
|
+
Hekenga::Failure.all.delete_all
|
68
|
+
puts "Removed all failure logs."
|
69
|
+
end
|
70
|
+
|
71
|
+
desc "clear! PATH_OR_PKEY", "Clear the logs and failure for a migration. Dangerous!"
|
72
|
+
def clear!(path_or_pkey)
|
73
|
+
migration = load_migration(path_or_pkey)
|
74
|
+
puts "Clearing #{migration.to_key}.."
|
75
|
+
Hekenga::Log.where(pkey: migration.to_key).delete_all
|
76
|
+
Hekenga::Failure.where(pkey: migration.to_key).delete_all
|
77
|
+
puts "Done!"
|
78
|
+
end
|
79
|
+
|
80
|
+
desc "rollback", "Rollback a migration."
|
81
|
+
def rollback
|
82
|
+
todo "rollback"
|
83
|
+
end
|
84
|
+
|
85
|
+
desc "recover", "Attempt to resume a failed migration."
|
86
|
+
def recover
|
87
|
+
todo "recover"
|
88
|
+
end
|
89
|
+
|
90
|
+
desc "errors", "Print the errors associated with a failed migration."
|
91
|
+
def errors
|
92
|
+
todo "errors"
|
93
|
+
end
|
94
|
+
|
95
|
+
desc "skip PATH_OR_PKEY", "Skip a migration so that it won't run."
|
96
|
+
def skip(path_or_pkey)
|
97
|
+
migration = load_migration(path_or_pkey)
|
98
|
+
puts "Skipping #{migration.to_key}.."
|
99
|
+
migration.tasks.each.with_index do |task, idx|
|
100
|
+
log = Hekenga::Log.where(pkey: task.to_key, task_idx: idx).first ||
|
101
|
+
Hekenga::Log.new(migration: migration, task_idx: idx)
|
102
|
+
|
103
|
+
log.done = true
|
104
|
+
log.skip = true
|
105
|
+
|
106
|
+
log.save!
|
107
|
+
end
|
108
|
+
puts "Done!"
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def todo(op)
|
114
|
+
puts "#{op.capitalize} has not yet been implemented."
|
115
|
+
exit(99)
|
116
|
+
end
|
117
|
+
def bail_if_errors
|
118
|
+
if Hekenga.any_fatal?
|
119
|
+
puts "Refusing to run migrations while there is an existing cancelled migration."
|
120
|
+
exit(1)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
def load_migration(path_or_pkey)
|
124
|
+
if File.exists?(File.expand_path(path_or_pkey))
|
125
|
+
require File.expand_path(path_or_pkey)
|
126
|
+
migration = Hekenga.registry.last
|
127
|
+
else
|
128
|
+
Hekenga.load_all!
|
129
|
+
unless migration = Hekenga.find_migration(path_or_pkey)
|
130
|
+
puts "Can't find migration #{path_or_pkey}. Available migrations:"
|
131
|
+
puts Hekenga.registry.map {|x| "- #{x.to_key}"}
|
132
|
+
exit(2)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
migration
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
HekengaCLI.start
|
data/hekenga.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hekenga/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hekenga"
|
8
|
+
spec.version = Hekenga::VERSION
|
9
|
+
spec.authors = ["Tapio Saarinen"]
|
10
|
+
spec.email = ["admin@bitlong.org"]
|
11
|
+
|
12
|
+
spec.summary = %q{Sophisticated migration framework for mongoid, with the ability to parallelise via ActiveJob.}
|
13
|
+
spec.homepage = "https://github.com/tzar/hekenga"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
|
+
f.match(%r{^(test|spec|features)/})
|
17
|
+
end
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
25
|
+
spec.add_development_dependency "database_cleaner"
|
26
|
+
spec.add_development_dependency "pry"
|
27
|
+
spec.add_development_dependency "pry-byebug"
|
28
|
+
|
29
|
+
spec.add_runtime_dependency "mongoid", "~> 5"
|
30
|
+
spec.add_runtime_dependency "activejob", "~> 4"
|
31
|
+
spec.add_runtime_dependency "thor"
|
32
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'hekenga/irreversible'
|
2
|
+
module Hekenga
|
3
|
+
class DocumentTask
|
4
|
+
attr_reader :ups, :downs, :setups, :filters
|
5
|
+
attr_accessor :parallel, :disable_rules, :scope, :timeless
|
6
|
+
attr_accessor :description, :invalid_strategy, :skip_prepare
|
7
|
+
def initialize
|
8
|
+
@ups = []
|
9
|
+
@downs = []
|
10
|
+
@disable_rules = []
|
11
|
+
@setups = []
|
12
|
+
@filters = []
|
13
|
+
@invalid_strategy = :prompt
|
14
|
+
@skip_prepare = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate!
|
18
|
+
raise Hekenga::Invalid.new(self, :ups, "missing") unless ups.any?
|
19
|
+
end
|
20
|
+
|
21
|
+
def up!(context, document)
|
22
|
+
@ups.each do |block|
|
23
|
+
context.instance_exec(document, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def down!(context, document)
|
28
|
+
raise Hekenga::Irreversible.new(self) unless reversible?
|
29
|
+
@downs.each do |block|
|
30
|
+
context.instance_eval(document, &block)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def reversible?
|
35
|
+
downs.any?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'hekenga/document_task'
|
2
|
+
module Hekenga
|
3
|
+
class DSL
|
4
|
+
class DocumentTask < Hekenga::DSL
|
5
|
+
configures Hekenga::DocumentTask
|
6
|
+
|
7
|
+
INVALID_BEHAVIOR_STRATEGIES = [:prompt, :cancel, :stop, :continue]
|
8
|
+
|
9
|
+
def when_invalid(val)
|
10
|
+
unless INVALID_BEHAVIOR_STRATEGIES.include?(val)
|
11
|
+
raise "Invalid value #{val}. Valid values for invalid_behavior are: #{INVALID_BEHAVIOR_STRATEGIES.join(", ")}."
|
12
|
+
end
|
13
|
+
@object.invalid_strategy = val
|
14
|
+
end
|
15
|
+
|
16
|
+
def scope(scope)
|
17
|
+
@object.scope = scope
|
18
|
+
end
|
19
|
+
def parallel!
|
20
|
+
@object.parallel = true
|
21
|
+
end
|
22
|
+
def timeless!
|
23
|
+
@object.timeless = true
|
24
|
+
end
|
25
|
+
def skip_prepare!
|
26
|
+
@object.skip_prepare = true
|
27
|
+
end
|
28
|
+
def disable_callback(callback, args = {})
|
29
|
+
[args[:on]].flatten.compact.each do |model|
|
30
|
+
@object.disable_rules.push({
|
31
|
+
klass: model,
|
32
|
+
callback: callback
|
33
|
+
})
|
34
|
+
end
|
35
|
+
end
|
36
|
+
def setup(&block)
|
37
|
+
@object.setups.push block
|
38
|
+
end
|
39
|
+
|
40
|
+
def filter(&block)
|
41
|
+
@object.filters.push block
|
42
|
+
end
|
43
|
+
|
44
|
+
def up(&block)
|
45
|
+
@object.ups.push block
|
46
|
+
end
|
47
|
+
|
48
|
+
def down(&block)
|
49
|
+
@object.downs.push block
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'hekenga/migration'
|
2
|
+
require 'time'
|
3
|
+
module Hekenga
|
4
|
+
class DSL
|
5
|
+
class Migration < Hekenga::DSL
|
6
|
+
configures Hekenga::Migration
|
7
|
+
|
8
|
+
def batch_size(size)
|
9
|
+
unless size.is_a?(Fixnum) && size > 0
|
10
|
+
raise "Invalid batch size #{size.inspect}"
|
11
|
+
end
|
12
|
+
@object.batch_size = size
|
13
|
+
end
|
14
|
+
def created(stamp = nil)
|
15
|
+
@object.stamp = Time.parse(stamp)
|
16
|
+
end
|
17
|
+
def task(description = nil, &block)
|
18
|
+
@object.tasks.push Hekenga::DSL::SimpleTask.new(description, &block).object
|
19
|
+
end
|
20
|
+
def per_document(description = nil, &block)
|
21
|
+
@object.tasks.push Hekenga::DSL::DocumentTask.new(description, &block).object
|
22
|
+
end
|
23
|
+
|
24
|
+
def inspect
|
25
|
+
"<#{self.class} - #{@object.description} (#{@object.stamp.strftime("%Y-%m-%d %H:%M")})>"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'hekenga/dsl/simple_task'
|
32
|
+
require 'hekenga/dsl/document_task'
|
data/lib/hekenga/dsl.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Hekenga
|
2
|
+
class DSL
|
3
|
+
attr_reader :object
|
4
|
+
def initialize(description = nil, &block)
|
5
|
+
@object = self.class.build_klass&.new
|
6
|
+
description(description) if description
|
7
|
+
instance_exec(&block)
|
8
|
+
@object.validate! if @object.respond_to?(:validate!)
|
9
|
+
end
|
10
|
+
def description(desc = nil)
|
11
|
+
@object.description = desc if @object && desc
|
12
|
+
end
|
13
|
+
def inspect
|
14
|
+
"<#{self.class} - #{self.description}>"
|
15
|
+
end
|
16
|
+
def self.configures(klass)
|
17
|
+
@build_klass = klass
|
18
|
+
end
|
19
|
+
def self.build_klass
|
20
|
+
@build_klass
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
require 'hekenga/dsl/migration'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Hekenga
|
2
|
+
class Failure
|
3
|
+
include Mongoid::Document
|
4
|
+
belongs_to :log, class_name: "Hekenga::Log"
|
5
|
+
|
6
|
+
# Internal tracking
|
7
|
+
field :pkey
|
8
|
+
field :task_idx
|
9
|
+
|
10
|
+
validates_presence_of [:pkey, :task_idx, :log_id]
|
11
|
+
|
12
|
+
index({pkey: 1})
|
13
|
+
index({log_id: 1})
|
14
|
+
|
15
|
+
def self.lookup(log_id, task_idx)
|
16
|
+
where(log_id: log_id, task_idx: task_idx)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
require 'hekenga/failure/error'
|
21
|
+
require 'hekenga/failure/write'
|
22
|
+
require 'hekenga/failure/validation'
|
23
|
+
require 'hekenga/failure/cancelled'
|
data/lib/hekenga/log.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'hekenga/failure'
|
2
|
+
|
3
|
+
module Hekenga
|
4
|
+
class Log
|
5
|
+
include Mongoid::Document
|
6
|
+
# Internal tracking
|
7
|
+
field :pkey
|
8
|
+
field :description
|
9
|
+
field :stamp
|
10
|
+
field :task_idx
|
11
|
+
|
12
|
+
validates_presence_of [:pkey, :description, :stamp, :task_idx]
|
13
|
+
|
14
|
+
# Status flags
|
15
|
+
field :done, default: false
|
16
|
+
field :error, default: false
|
17
|
+
field :cancel, default: false
|
18
|
+
field :skip, default: false
|
19
|
+
|
20
|
+
# Used by document tasks
|
21
|
+
field :total
|
22
|
+
field :processed, default: 0
|
23
|
+
field :skipped, default: 0
|
24
|
+
field :unvalid, default: 0
|
25
|
+
field :started, default: ->{ Time.now }
|
26
|
+
field :finished, type: Time
|
27
|
+
|
28
|
+
has_many :failures, class_name: "Hekenga::Failure"
|
29
|
+
|
30
|
+
index({pkey: 1, task_idx: 1}, unique: true)
|
31
|
+
|
32
|
+
def migration=(migration)
|
33
|
+
self.pkey = migration.to_key
|
34
|
+
self.description = migration.description
|
35
|
+
self.stamp = migration.stamp
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_failure(attrs, klass)
|
39
|
+
self.failures.create({
|
40
|
+
pkey: self.pkey,
|
41
|
+
task_idx: self.task_idx
|
42
|
+
}.merge(attrs), klass)
|
43
|
+
end
|
44
|
+
|
45
|
+
def incr_and_return(fields)
|
46
|
+
doc = self.class.where(_id: self.id).find_one_and_update({
|
47
|
+
:$inc => fields
|
48
|
+
}, return_document: :after, projection: fields.keys.map {|x| [x, 1]}.to_h)
|
49
|
+
fields.map do |field, _|
|
50
|
+
value = doc.send(field)
|
51
|
+
send("#{field}=", value)
|
52
|
+
[field, value]
|
53
|
+
end.to_h
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|