hekenga 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|