active_propagation 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3e00938b35501723e36afe6ec04011464588d594
4
+ data.tar.gz: 5c348effb95ccccceabf508bc49e979c88d9d526
5
+ SHA512:
6
+ metadata.gz: c4136786fc4c3360ab773609ea828aaa59efe957d683e6faa6d660e53cb4c2e1e5b1fcd1ea069b76a00854290fe1a8ea095611d80b3e5f770c1e17c59cc8c135
7
+ data.tar.gz: bee8f450fc60056c75bad9d0d7e0a0ddb0305f43689ab0a6d8f137f89730388b3014d5d0b41a8a8a78a0cc0420c8d52953af330f0378a786dc5ef4ba4f0821be
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /vendor
11
+ *.sqlite3
12
+ *.swp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.2
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in active_propagation.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,83 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ active_propagation (0.1.0)
5
+ activerecord (~> 4.0)
6
+ activesupport (~> 4.0)
7
+ sidekiq
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activemodel (4.2.1)
13
+ activesupport (= 4.2.1)
14
+ builder (~> 3.1)
15
+ activerecord (4.2.1)
16
+ activemodel (= 4.2.1)
17
+ activesupport (= 4.2.1)
18
+ arel (~> 6.0)
19
+ activesupport (4.2.1)
20
+ i18n (~> 0.7)
21
+ json (~> 1.7, >= 1.7.7)
22
+ minitest (~> 5.1)
23
+ thread_safe (~> 0.3, >= 0.3.4)
24
+ tzinfo (~> 1.1)
25
+ arel (6.0.0)
26
+ builder (3.2.2)
27
+ celluloid (0.16.0)
28
+ timers (~> 4.0.0)
29
+ coderay (1.1.0)
30
+ connection_pool (2.2.0)
31
+ database_cleaner (1.4.1)
32
+ diff-lcs (1.2.5)
33
+ hitimes (1.2.2)
34
+ i18n (0.7.0)
35
+ json (1.8.3)
36
+ method_source (0.8.2)
37
+ minitest (5.7.0)
38
+ pry (0.10.1)
39
+ coderay (~> 1.1.0)
40
+ method_source (~> 0.8.1)
41
+ slop (~> 3.4)
42
+ rake (10.4.2)
43
+ redis (3.2.1)
44
+ redis-namespace (1.5.2)
45
+ redis (~> 3.0, >= 3.0.4)
46
+ rspec (3.2.0)
47
+ rspec-core (~> 3.2.0)
48
+ rspec-expectations (~> 3.2.0)
49
+ rspec-mocks (~> 3.2.0)
50
+ rspec-core (3.2.3)
51
+ rspec-support (~> 3.2.0)
52
+ rspec-expectations (3.2.1)
53
+ diff-lcs (>= 1.2.0, < 2.0)
54
+ rspec-support (~> 3.2.0)
55
+ rspec-mocks (3.2.1)
56
+ diff-lcs (>= 1.2.0, < 2.0)
57
+ rspec-support (~> 3.2.0)
58
+ rspec-support (3.2.2)
59
+ sidekiq (3.3.4)
60
+ celluloid (>= 0.16.0)
61
+ connection_pool (>= 2.1.1)
62
+ json
63
+ redis (>= 3.0.6)
64
+ redis-namespace (>= 1.3.1)
65
+ slop (3.6.0)
66
+ sqlite3 (1.3.10)
67
+ thread_safe (0.3.5)
68
+ timers (4.0.1)
69
+ hitimes
70
+ tzinfo (1.2.2)
71
+ thread_safe (~> 0.1)
72
+
73
+ PLATFORMS
74
+ ruby
75
+
76
+ DEPENDENCIES
77
+ active_propagation!
78
+ bundler (~> 1.8)
79
+ database_cleaner
80
+ pry
81
+ rake (~> 10.0)
82
+ rspec
83
+ sqlite3
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # ActivePropagation
2
+
3
+ Provides an ActiveRecord extension for propagating changes amongst models.
4
+
5
+ Propagations are ran as callbacks and can be propagated synchronously in background jobs, or synchronously within the callback lifecycle. Furthermore, the individual updates on each associated record can be ran in jobs of their own.
6
+
7
+ # Usage Example
8
+
9
+ Say you have a posts model with an optional `post_id` such that a Post can have many posts and also belong to a post:
10
+
11
+ ```ruby
12
+ class Post < ActiveRecord::Base
13
+ belongs_to :post
14
+ has_many :sub_posts
15
+
16
+ propagates_changes_to :sub_posts, only: [:title], on: [:update], async: true
17
+ end
18
+ ```
19
+
20
+ This will register an `after_commit` callback that will fire a Sidekiq worker to set the titles of all of the sub_posts to be the same as their parent post.
21
+
22
+ # Why?
23
+
24
+ Many Rails codebases rely heavily on callbacks to keep the application's state consistent. While changes can be propagated performantly and easily using `update_all` or `destroy_all`, if there are callbacks that need to be ran then this isn't possible. active_propagation calls `update` or `destroy` on each instantiated activerecord object so all callbacks are ran. By having an asynch option, even if you expect lots of dependent records and long running callbacks, each update and destroy, as well as their callbacks, are parallelized.
25
+
26
+ Caveats:
27
+ - Currently the only supported job system is Sidekiq. The plan is to make this configurable for multiple backends(probably using ActiveJob).
28
+ - The callbacks are executed `after_commit`. This is to make sure it plays nicely with Sidekiq.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'active_propagation/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "active_propagation"
8
+ spec.version = ActivePropagation::VERSION
9
+ spec.authors = ["Yoshiki Schmitz"]
10
+ spec.email = ["yoshiki@masteryconnect.com"]
11
+
12
+ spec.summary = %q{propagates changes across models}
13
+ spec.description = %q{ActivePropagation provides classes and ActiveRecord extensions for propagating changes amongst models, optionally using Sidekiq to parallelize the process.}
14
+ spec.homepage = "https://bitbucket.org/doweber/active_propagation/overview"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.8"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "sqlite3"
25
+ spec.add_development_dependency "database_cleaner"
26
+ spec.add_development_dependency "pry"
27
+
28
+ spec.add_dependency "activerecord", "~> 4.0"
29
+ spec.add_dependency "activesupport", "~> 4.0"
30
+ spec.add_dependency "sidekiq"
31
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "active_propagation"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,106 @@
1
+ require 'active_record'
2
+ require 'sidekiq'
3
+ require "active_propagation/version"
4
+ require "active_propagation/class_extensions"
5
+ require "active_propagation/instance_extensions"
6
+ require "active_propagation/propagater_helper"
7
+
8
+ module ActivePropagation
9
+ class Propagater
10
+ def initialize(klass, association, id)
11
+ @klass, @association, @id = klass, association, id
12
+ end
13
+
14
+ def run
15
+ assocs.each do |a|
16
+ yield a
17
+ end
18
+ end
19
+
20
+ def assocs
21
+ assoc_klass.where(foreign_key => id)
22
+ end
23
+
24
+ def foreign_key
25
+ klass.reflections[association.to_s].foreign_key
26
+ end
27
+
28
+ def assoc_klass
29
+ klass.reflections[association.to_s].class_name.constantize
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :klass, :association, :id
35
+ end
36
+
37
+ class AbstractPropagaterWorker
38
+ include PropagaterHelper
39
+ include Sidekiq::Worker
40
+
41
+ def self.run(klass_str, model_id, assoc, only)
42
+ self.perform_async(klass_str, model_id, assoc, only)
43
+ end
44
+ end
45
+
46
+ class AsyncLoopDeletor
47
+ include PropagaterHelper
48
+ include Sidekiq::Worker
49
+ def perform(klass_str, assoc_id)
50
+ klass = klass_str.constantize
51
+ klass.find(assoc_id).destroy
52
+ end
53
+ end
54
+
55
+ class AsyncDeletor < AbstractPropagaterWorker
56
+ def perform(klass_str, model_id, assoc, only)
57
+ klass = klass_str.constantize
58
+ Propagater.new(klass, assoc, model_id).run do |a|
59
+ AsyncLoopDeletor.perform_async(klass.to_s, a.id)
60
+ end
61
+ end
62
+ end
63
+
64
+ class AsyncLoopUpdater
65
+ include Sidekiq::Worker
66
+ include PropagaterHelper
67
+ def perform(klass_str, model_id, assoc_id, only)
68
+ klass = klass_str.constantize
69
+ model = klass.find(model_id)
70
+ klass.find(assoc_id).update(propagated_attributes(model, only))
71
+ end
72
+ end
73
+
74
+ class AsyncUpdater < AbstractPropagaterWorker
75
+ def perform(klass_str, model_id, assoc, only)
76
+ klass = klass_str.constantize
77
+ Propagater.new(klass, assoc, model_id).run do |a|
78
+ AsyncLoopUpdater.perform_async(klass.to_s, model_id, a.id, only)
79
+ end
80
+ end
81
+ end
82
+
83
+ class Updater
84
+ extend PropagaterHelper
85
+ def self.run(klass_str, model_id, assoc, only)
86
+ klass = klass_str.constantize
87
+ model = klass.find(model_id)
88
+ Propagater.new(klass, assoc, model_id).run do |a|
89
+ a.update(propagated_attributes(model, only))
90
+ end
91
+ end
92
+ end
93
+
94
+ class Deletor
95
+ extend PropagaterHelper
96
+ def self.run(klass_str, model_id, assoc, only)
97
+ klass = klass_str.constantize
98
+ Propagater.new(klass, assoc, model_id).run do |a|
99
+ a.destroy
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ ActiveRecord::Base.send(:extend, ActivePropagation::ClassExtensions)
106
+ ActiveRecord::Base.send(:include, ActivePropagation::InstanceExtensions)
@@ -0,0 +1,7 @@
1
+ module ActivePropagation::ClassExtensions
2
+ def propagates_changes_to(association, only: [], async: false, nested_async: false, on: [:update, :destroy])
3
+ self.class_variable_set(:@@propagations, self.class_variable_get(:@@propagations) || {})
4
+ after_commit(:_run_active_propagation, on: on) unless _commit_callbacks.map(&:filter).include?(:_run_active_propagation)
5
+ self.class_variable_get(:@@propagations)[association] = {only: only, async: async, nested_async: nested_async}
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ module ActivePropagation::InstanceExtensions
2
+ def _run_active_propagation
3
+ klass = self.class
4
+ klass.class_variable_get(:@@propagations).each do |association, config|
5
+ args = [self.class.to_s, self.id, association.to_s, config[:only]]
6
+ if self.destroyed?
7
+ if config[:async]
8
+ ActivePropagation::AsyncDeletor.run(*args)
9
+ else
10
+ ActivePropagation::Deletor.run(*args)
11
+ end
12
+ else
13
+ if config[:async]
14
+ ActivePropagation::AsyncUpdater.run(*args)
15
+ else
16
+ ActivePropagation::Updater.run(*args)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ module PropagaterHelper
2
+ def propagated_attributes(model, only)
3
+ only.map{|x| [x, model.attributes[x.to_s]]}.to_h
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module ActivePropagation
2
+ VERSION = "0.2.1"
3
+ end
metadata ADDED
@@ -0,0 +1,187 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_propagation
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Yoshiki Schmitz
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-06-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: database_cleaner
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: activerecord
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '4.0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '4.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: activesupport
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '4.0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '4.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: sidekiq
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description: ActivePropagation provides classes and ActiveRecord extensions for propagating
140
+ changes amongst models, optionally using Sidekiq to parallelize the process.
141
+ email:
142
+ - yoshiki@masteryconnect.com
143
+ executables: []
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - ".gitignore"
148
+ - ".rspec"
149
+ - ".ruby-version"
150
+ - ".travis.yml"
151
+ - Gemfile
152
+ - Gemfile.lock
153
+ - README.md
154
+ - Rakefile
155
+ - active_propagation.gemspec
156
+ - bin/console
157
+ - bin/setup
158
+ - lib/active_propagation.rb
159
+ - lib/active_propagation/class_extensions.rb
160
+ - lib/active_propagation/instance_extensions.rb
161
+ - lib/active_propagation/propagater_helper.rb
162
+ - lib/active_propagation/version.rb
163
+ homepage: https://bitbucket.org/doweber/active_propagation/overview
164
+ licenses: []
165
+ metadata: {}
166
+ post_install_message:
167
+ rdoc_options: []
168
+ require_paths:
169
+ - lib
170
+ required_ruby_version: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ required_rubygems_version: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ requirements: []
181
+ rubyforge_project:
182
+ rubygems_version: 2.2.2
183
+ signing_key:
184
+ specification_version: 4
185
+ summary: propagates changes across models
186
+ test_files: []
187
+ has_rdoc: