prioritized_callbacks 0.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 67fa423205b68cba0faae8dbe2c7ae6e2d27d4e935fa8b9240368f1db1364833
4
+ data.tar.gz: 684ebfffafab4bc89ff968983970c98ca5a996174f3d50307bd7b1fab56e83fb
5
+ SHA512:
6
+ metadata.gz: 6150409a8dcf199c6aca6685d01b3fd8ef33b62a562ee95fcd92e578fdc05279a38f6f54b91b6ebb85b3e4e2230407acb46d2380c559120d4246668cf5f732f9
7
+ data.tar.gz: 8b39d7091e40da431c89f40389ebc8d5b703b454b3219de41d64094866a54149439e6971696368f76b02a591098ae276671887838df2dce3ad9147752cac166f
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --format documentation
3
+ --color
data/Appraisals ADDED
@@ -0,0 +1,24 @@
1
+ appraise 'rails-50' do
2
+ gem 'activerecord', '~>5.0.0'
3
+ gem 'sqlite3', '~>1.3.6'
4
+ end
5
+
6
+ appraise 'rails-51' do
7
+ gem 'activerecord', '~>5.1.0'
8
+ gem 'sqlite3'
9
+ end
10
+
11
+ appraise 'rails-52' do
12
+ gem 'activerecord', '~>5.2.0'
13
+ gem 'sqlite3'
14
+ end
15
+
16
+ appraise 'rails-60' do
17
+ gem 'activerecord', '~>6.0.0'
18
+ gem 'sqlite3'
19
+ end
20
+
21
+ appraise 'rails-61' do
22
+ gem 'activerecord', '~>6.1.0'
23
+ gem 'sqlite3'
24
+ end
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2019 Robin Roestenburg
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # Prioritized ActiveSupport Callbacks
2
+
3
+ Sometimes, when you build big system with a massive number of plugins, you can realize that you need to prioritize
4
+ callbacks in your models or controllers to have full control which callbacks are executed first,
5
+ which are executed last despite of the order in which they were initially added.
6
+
7
+ **WARNING!** Do not use this gem unless you know that's the only way.
8
+
9
+ ## Usage
10
+
11
+ ### Using pure ActiveSupport::Callbacks:
12
+
13
+ ```ruby
14
+ require 'active_support'
15
+ require 'prioritized_callbacks'
16
+
17
+ class MyClass
18
+ include ActiveSupport::Callbacks
19
+
20
+ define_callbacks :action, order: [:first, :default, :last]
21
+
22
+ set_callback :action, :before do
23
+ puts "I'm called second before action"
24
+ end
25
+
26
+ set_callback :action, :before, priority: :first do
27
+ puts "I'm called first before action"
28
+ end
29
+
30
+ set_callback :action, :before, priority: :last do
31
+ puts "I'm called third before action"
32
+ end
33
+ end
34
+
35
+ MyClass.new.run_callbacks :action do
36
+ puts "Action performed"
37
+ end
38
+ ```
39
+
40
+ Will output:
41
+
42
+ ```
43
+ I'm called first before action
44
+ I'm called second before action
45
+ I'm called third before action
46
+ Action performed
47
+ ```
48
+
49
+ Here are two parts to setup:
50
+
51
+ 1) Definition of callbacks priority names and its order:
52
+
53
+ ```ruby
54
+ define_callbacks :action, order: [:first, :default, :last]
55
+ ```
56
+
57
+ When you define callbacks, you specify the order of callback groups. You can give groups any name, but there is a
58
+ special name `:default` which is used for any callbacks whose priority is not specified.
59
+
60
+ If you do not specify `:order`, it gets default Rails behaviour and is implicitly set to `[:default]`
61
+
62
+ 2) Assigning priority name to callback itself:
63
+
64
+ ```ruby
65
+ set_callback :action, :before, priority: :first do ... end
66
+ ```
67
+
68
+ When you add new callback, you can specify its priority by using name you used in callback definition (see above).
69
+ If you don't specify `:priority`, `:default` is assumed.
70
+
71
+ Callbacks with the same priority are executed in the order how they were added.
72
+
73
+ ### Using ActiveRecord callbacks
74
+
75
+ ```ruby
76
+ class Record < ActiveRecord::Base
77
+
78
+ set_save_order :first, :default, :last
79
+
80
+ before_save priority: :last do
81
+ # Do something last
82
+ end
83
+
84
+ before_save :do_first, priority: :first
85
+
86
+ before_save :do_default
87
+ end
88
+ ```
89
+
90
+ 1) To set order of save callbacks in ActiveRecord, you should use:
91
+
92
+ ```ruby
93
+ set_save_order ..., :default, ...
94
+ ```
95
+
96
+ Or in common form:
97
+
98
+ ```ruby
99
+ set_callbacks_order :save, [..., :default, ...]
100
+ ```
101
+
102
+ The latter can be used outside of ActiveRecord, in ActionController or ActiveModel, where ActiveSupport::Callbacks
103
+ are included.
104
+
105
+ 2) To set priority for specific callback, you should add `:priority` option to `before_save` or `after_save`.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ desc 'Run the specs.'
9
+ task default: :spec
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~>5.0.0"
6
+ gem "sqlite3", "~>1.3.6"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,64 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ prioritized_callbacks (0.0.1)
5
+ activesupport (>= 5.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (5.0.7.2)
11
+ activesupport (= 5.0.7.2)
12
+ activerecord (5.0.7.2)
13
+ activemodel (= 5.0.7.2)
14
+ activesupport (= 5.0.7.2)
15
+ arel (~> 7.0)
16
+ activesupport (5.0.7.2)
17
+ concurrent-ruby (~> 1.0, >= 1.0.2)
18
+ i18n (>= 0.7, < 2)
19
+ minitest (~> 5.1)
20
+ tzinfo (~> 1.1)
21
+ appraisal (2.4.0)
22
+ bundler
23
+ rake
24
+ thor (>= 0.14.0)
25
+ arel (7.1.4)
26
+ concurrent-ruby (1.1.8)
27
+ diff-lcs (1.4.4)
28
+ i18n (1.8.10)
29
+ concurrent-ruby (~> 1.0)
30
+ minitest (5.14.4)
31
+ rake (13.0.3)
32
+ rspec (3.10.0)
33
+ rspec-core (~> 3.10.0)
34
+ rspec-expectations (~> 3.10.0)
35
+ rspec-mocks (~> 3.10.0)
36
+ rspec-core (3.10.1)
37
+ rspec-support (~> 3.10.0)
38
+ rspec-expectations (3.10.1)
39
+ diff-lcs (>= 1.2.0, < 2.0)
40
+ rspec-support (~> 3.10.0)
41
+ rspec-mocks (3.10.2)
42
+ diff-lcs (>= 1.2.0, < 2.0)
43
+ rspec-support (~> 3.10.0)
44
+ rspec-support (3.10.2)
45
+ sqlite3 (1.3.13)
46
+ thor (1.1.0)
47
+ thread_safe (0.3.6)
48
+ tzinfo (1.2.9)
49
+ thread_safe (~> 0.1)
50
+
51
+ PLATFORMS
52
+ ruby
53
+
54
+ DEPENDENCIES
55
+ activerecord (~> 5.0.0)
56
+ appraisal
57
+ bundler
58
+ prioritized_callbacks!
59
+ rake
60
+ rspec
61
+ sqlite3 (~> 1.3.6)
62
+
63
+ BUNDLED WITH
64
+ 2.1.4
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~>5.1.0"
6
+ gem "sqlite3"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,64 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ prioritized_callbacks (0.0.1)
5
+ activesupport (>= 5.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (5.1.7)
11
+ activesupport (= 5.1.7)
12
+ activerecord (5.1.7)
13
+ activemodel (= 5.1.7)
14
+ activesupport (= 5.1.7)
15
+ arel (~> 8.0)
16
+ activesupport (5.1.7)
17
+ concurrent-ruby (~> 1.0, >= 1.0.2)
18
+ i18n (>= 0.7, < 2)
19
+ minitest (~> 5.1)
20
+ tzinfo (~> 1.1)
21
+ appraisal (2.4.0)
22
+ bundler
23
+ rake
24
+ thor (>= 0.14.0)
25
+ arel (8.0.0)
26
+ concurrent-ruby (1.1.8)
27
+ diff-lcs (1.4.4)
28
+ i18n (1.8.10)
29
+ concurrent-ruby (~> 1.0)
30
+ minitest (5.14.4)
31
+ rake (13.0.3)
32
+ rspec (3.10.0)
33
+ rspec-core (~> 3.10.0)
34
+ rspec-expectations (~> 3.10.0)
35
+ rspec-mocks (~> 3.10.0)
36
+ rspec-core (3.10.1)
37
+ rspec-support (~> 3.10.0)
38
+ rspec-expectations (3.10.1)
39
+ diff-lcs (>= 1.2.0, < 2.0)
40
+ rspec-support (~> 3.10.0)
41
+ rspec-mocks (3.10.2)
42
+ diff-lcs (>= 1.2.0, < 2.0)
43
+ rspec-support (~> 3.10.0)
44
+ rspec-support (3.10.2)
45
+ sqlite3 (1.4.2)
46
+ thor (1.1.0)
47
+ thread_safe (0.3.6)
48
+ tzinfo (1.2.9)
49
+ thread_safe (~> 0.1)
50
+
51
+ PLATFORMS
52
+ ruby
53
+
54
+ DEPENDENCIES
55
+ activerecord (~> 5.1.0)
56
+ appraisal
57
+ bundler
58
+ prioritized_callbacks!
59
+ rake
60
+ rspec
61
+ sqlite3
62
+
63
+ BUNDLED WITH
64
+ 2.1.4
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~>5.2.0"
6
+ gem "sqlite3"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,64 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ prioritized_callbacks (0.0.1)
5
+ activesupport (>= 5.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (5.2.6)
11
+ activesupport (= 5.2.6)
12
+ activerecord (5.2.6)
13
+ activemodel (= 5.2.6)
14
+ activesupport (= 5.2.6)
15
+ arel (>= 9.0)
16
+ activesupport (5.2.6)
17
+ concurrent-ruby (~> 1.0, >= 1.0.2)
18
+ i18n (>= 0.7, < 2)
19
+ minitest (~> 5.1)
20
+ tzinfo (~> 1.1)
21
+ appraisal (2.4.0)
22
+ bundler
23
+ rake
24
+ thor (>= 0.14.0)
25
+ arel (9.0.0)
26
+ concurrent-ruby (1.1.8)
27
+ diff-lcs (1.4.4)
28
+ i18n (1.8.10)
29
+ concurrent-ruby (~> 1.0)
30
+ minitest (5.14.4)
31
+ rake (13.0.3)
32
+ rspec (3.10.0)
33
+ rspec-core (~> 3.10.0)
34
+ rspec-expectations (~> 3.10.0)
35
+ rspec-mocks (~> 3.10.0)
36
+ rspec-core (3.10.1)
37
+ rspec-support (~> 3.10.0)
38
+ rspec-expectations (3.10.1)
39
+ diff-lcs (>= 1.2.0, < 2.0)
40
+ rspec-support (~> 3.10.0)
41
+ rspec-mocks (3.10.2)
42
+ diff-lcs (>= 1.2.0, < 2.0)
43
+ rspec-support (~> 3.10.0)
44
+ rspec-support (3.10.2)
45
+ sqlite3 (1.4.2)
46
+ thor (1.1.0)
47
+ thread_safe (0.3.6)
48
+ tzinfo (1.2.9)
49
+ thread_safe (~> 0.1)
50
+
51
+ PLATFORMS
52
+ ruby
53
+
54
+ DEPENDENCIES
55
+ activerecord (~> 5.2.0)
56
+ appraisal
57
+ bundler
58
+ prioritized_callbacks!
59
+ rake
60
+ rspec
61
+ sqlite3
62
+
63
+ BUNDLED WITH
64
+ 2.1.4
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~>6.0.0"
6
+ gem "sqlite3"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,64 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ prioritized_callbacks (0.0.1)
5
+ activesupport (>= 5.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (6.0.3.7)
11
+ activesupport (= 6.0.3.7)
12
+ activerecord (6.0.3.7)
13
+ activemodel (= 6.0.3.7)
14
+ activesupport (= 6.0.3.7)
15
+ activesupport (6.0.3.7)
16
+ concurrent-ruby (~> 1.0, >= 1.0.2)
17
+ i18n (>= 0.7, < 2)
18
+ minitest (~> 5.1)
19
+ tzinfo (~> 1.1)
20
+ zeitwerk (~> 2.2, >= 2.2.2)
21
+ appraisal (2.4.0)
22
+ bundler
23
+ rake
24
+ thor (>= 0.14.0)
25
+ concurrent-ruby (1.1.8)
26
+ diff-lcs (1.4.4)
27
+ i18n (1.8.10)
28
+ concurrent-ruby (~> 1.0)
29
+ minitest (5.14.4)
30
+ rake (13.0.3)
31
+ rspec (3.10.0)
32
+ rspec-core (~> 3.10.0)
33
+ rspec-expectations (~> 3.10.0)
34
+ rspec-mocks (~> 3.10.0)
35
+ rspec-core (3.10.1)
36
+ rspec-support (~> 3.10.0)
37
+ rspec-expectations (3.10.1)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.10.0)
40
+ rspec-mocks (3.10.2)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.10.0)
43
+ rspec-support (3.10.2)
44
+ sqlite3 (1.4.2)
45
+ thor (1.1.0)
46
+ thread_safe (0.3.6)
47
+ tzinfo (1.2.9)
48
+ thread_safe (~> 0.1)
49
+ zeitwerk (2.4.2)
50
+
51
+ PLATFORMS
52
+ ruby
53
+
54
+ DEPENDENCIES
55
+ activerecord (~> 6.0.0)
56
+ appraisal
57
+ bundler
58
+ prioritized_callbacks!
59
+ rake
60
+ rspec
61
+ sqlite3
62
+
63
+ BUNDLED WITH
64
+ 2.1.4
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~>6.1.0"
6
+ gem "sqlite3"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,63 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ prioritized_callbacks (0.0.1)
5
+ activesupport (>= 5.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (6.1.3.2)
11
+ activesupport (= 6.1.3.2)
12
+ activerecord (6.1.3.2)
13
+ activemodel (= 6.1.3.2)
14
+ activesupport (= 6.1.3.2)
15
+ activesupport (6.1.3.2)
16
+ concurrent-ruby (~> 1.0, >= 1.0.2)
17
+ i18n (>= 1.6, < 2)
18
+ minitest (>= 5.1)
19
+ tzinfo (~> 2.0)
20
+ zeitwerk (~> 2.3)
21
+ appraisal (2.4.0)
22
+ bundler
23
+ rake
24
+ thor (>= 0.14.0)
25
+ concurrent-ruby (1.1.8)
26
+ diff-lcs (1.4.4)
27
+ i18n (1.8.10)
28
+ concurrent-ruby (~> 1.0)
29
+ minitest (5.14.4)
30
+ rake (13.0.3)
31
+ rspec (3.10.0)
32
+ rspec-core (~> 3.10.0)
33
+ rspec-expectations (~> 3.10.0)
34
+ rspec-mocks (~> 3.10.0)
35
+ rspec-core (3.10.1)
36
+ rspec-support (~> 3.10.0)
37
+ rspec-expectations (3.10.1)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.10.0)
40
+ rspec-mocks (3.10.2)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.10.0)
43
+ rspec-support (3.10.2)
44
+ sqlite3 (1.4.2)
45
+ thor (1.1.0)
46
+ tzinfo (2.0.4)
47
+ concurrent-ruby (~> 1.0)
48
+ zeitwerk (2.4.2)
49
+
50
+ PLATFORMS
51
+ ruby
52
+
53
+ DEPENDENCIES
54
+ activerecord (~> 6.1.0)
55
+ appraisal
56
+ bundler
57
+ prioritized_callbacks!
58
+ rake
59
+ rspec
60
+ sqlite3
61
+
62
+ BUNDLED WITH
63
+ 2.1.4
@@ -0,0 +1,14 @@
1
+ require 'prioritized_callbacks/version'
2
+ require 'prioritized_callbacks/active_support/callbacks_class_methods_patch'
3
+ require 'prioritized_callbacks/active_support/callback_chain_patch'
4
+ require 'prioritized_callbacks/active_support/callback_patch'
5
+ require 'prioritized_callbacks/callback_list'
6
+
7
+ ActiveSupport::Callbacks::ClassMethods.prepend PrioritizedCallbacks::ActiveSupport::CallbacksClassMethodsPatch
8
+ ActiveSupport::Callbacks::Callback.prepend PrioritizedCallbacks::ActiveSupport::CallbackPatch
9
+ ActiveSupport::Callbacks::CallbackChain.prepend PrioritizedCallbacks::ActiveSupport::CallbackChainPatch
10
+
11
+ if defined?(ActiveModel)
12
+ require 'prioritized_callbacks/active_model/callbacks_patch'
13
+ ActiveModel::Callbacks.prepend PrioritizedCallbacks::ActiveModel::CallbacksPatch
14
+ end
@@ -0,0 +1,49 @@
1
+ module PrioritizedCallbacks
2
+ module ActiveModel
3
+ module CallbacksPatch
4
+
5
+ def define_model_callbacks(*callbacks)
6
+ options = callbacks.extract_options!
7
+ options[:reverse_after] = true
8
+
9
+ super *callbacks, options
10
+
11
+ callbacks.each do |callback|
12
+ define_singleton_method "set_#{callback}_order" do |*args|
13
+ set_callbacks_order(:"#{callback}", args)
14
+ end
15
+ end
16
+ end
17
+
18
+ if ::ActiveModel.version >= Gem::Version.new('6')
19
+ private
20
+
21
+ def _define_before_model_callback(klass, callback)
22
+ klass.define_singleton_method("before_#{callback}") do |*args, **options, &block|
23
+ options.assert_valid_keys(:if, :unless, :prepend, :priority)
24
+ set_callback(:"#{callback}", :before, *args, options, &block)
25
+ end
26
+ end
27
+
28
+ def _define_around_model_callback(klass, callback)
29
+ klass.define_singleton_method("around_#{callback}") do |*args, **options, &block|
30
+ options.assert_valid_keys(:if, :unless, :prepend, :priority)
31
+ set_callback(:"#{callback}", :around, *args, options, &block)
32
+ end
33
+ end
34
+
35
+ def _define_after_model_callback(klass, callback)
36
+ klass.define_singleton_method("after_#{callback}") do |*args, **options, &block|
37
+ options.assert_valid_keys(:if, :unless, :prepend, :priority)
38
+ options[:prepend] = true
39
+ conditional = ::ActiveSupport::Callbacks::Conditionals::Value.new { |v|
40
+ v != false
41
+ }
42
+ options[:if] = Array(options[:if]) + [conditional]
43
+ set_callback(:"#{callback}", :after, *args, options, &block)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,22 @@
1
+ module PrioritizedCallbacks
2
+ module ActiveSupport
3
+ module CallbackChainPatch
4
+ def initialize(name, config)
5
+ super
6
+ @config[:order] ||= [:default]
7
+ @chain = PrioritizedCallbacks::CallbackList.new(@config)
8
+ end
9
+
10
+ def copy_with_new_order(order)
11
+ new_chain = dup
12
+ new_config = config.merge(order: order)
13
+ new_callbacks = @chain.dup
14
+ new_callbacks.instance_variable_set(:@config, new_config)
15
+ new_chain.instance_variable_set(:@config, new_config)
16
+ new_chain.instance_variable_set(:@chain, new_callbacks)
17
+ new_chain
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,26 @@
1
+ module PrioritizedCallbacks
2
+ module ActiveSupport
3
+ module CallbackPatch
4
+ def self.prepended(base)
5
+ base.singleton_class.prepend ClassMethods
6
+ end
7
+
8
+ attr_accessor :priority
9
+
10
+ def initialize(name, filter, kind, options, chain_config)
11
+ super
12
+ @priority = options[:priority] || :default
13
+ end
14
+
15
+ module ClassMethods
16
+ def build(chain, filter, kind, options)
17
+ if (priority = options[:priority]) && !chain.config[:order].include?(priority)
18
+ raise ArgumentError, "Unknown priority :#{priority} is specified. " +
19
+ "Known priorities are: #{chain.config[:order].inspect}"
20
+ end
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ module PrioritizedCallbacks
2
+ module ActiveSupport
3
+ module CallbacksClassMethodsPatch
4
+ def set_callbacks_order(name, order)
5
+ chain = get_callbacks(name)
6
+ raise ArgumentError, "Callbacks for #{name} are not defined" unless chain
7
+ set_callbacks name, chain.copy_with_new_order(order)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,42 @@
1
+ module PrioritizedCallbacks
2
+ class CallbackList < Array
3
+ attr_reader :config
4
+
5
+ def initialize(config, callbacks = [])
6
+ @config = config
7
+ super(callbacks)
8
+ end
9
+
10
+ def each
11
+ if config[:reverse]
12
+ callbacks = to_a.reverse
13
+ order = config[:order].reverse
14
+ else
15
+ callbacks = to_a
16
+ order = config[:order]
17
+ end
18
+ if config[:reverse_after]
19
+ order.each do |priority|
20
+ callbacks.each do |callback|
21
+ yield callback if callback.priority == priority && (callback.kind == :before || callback.kind == :around)
22
+ end
23
+ end
24
+ order.reverse.each do |priority|
25
+ callbacks.each do |callback|
26
+ yield callback if callback.priority == priority && callback.kind == :after
27
+ end
28
+ end
29
+ else
30
+ order.each do |priority|
31
+ callbacks.each do |callback|
32
+ yield callback if callback.priority == priority
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def reverse
39
+ CallbackList.new(config.merge(reverse: true), to_a)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module PrioritizedCallbacks
2
+ VERSION = '0.0.2'
3
+ end
@@ -0,0 +1,26 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'prioritized_callbacks/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'prioritized_callbacks'
7
+ s.version = PrioritizedCallbacks::VERSION
8
+ s.summary = "Prioritizing ActiveSupport::Callbacks"
9
+ s.description = "This gem allows to define a custom order in which callbacks are called"
10
+ s.authors = ["Anton Argirov"]
11
+ s.email = 'anton.argirov@gmail.com'
12
+ s.homepage = 'https://github.com/anteo/prioritized_callbacks'
13
+ s.license = 'MIT'
14
+ s.files = Dir.glob("{gemfiles,lib,spec}/**/*") + %w(.rspec Appraisals Gemfile LICENSE.txt prioritized_callbacks.gemspec Rakefile README.md)
15
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
16
+ s.require_paths = ['lib']
17
+
18
+ s.add_dependency 'activesupport', ['>=5.0']
19
+
20
+ s.add_development_dependency 'bundler'
21
+ s.add_development_dependency 'rake'
22
+ s.add_development_dependency 'rspec'
23
+ s.add_development_dependency 'activerecord'
24
+ s.add_development_dependency 'sqlite3'
25
+ s.add_development_dependency 'appraisal'
26
+ end
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ActiveRecord::Callbacks do
4
+ before(:all) do
5
+ ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
6
+
7
+ ActiveRecord::Schema.define version: 0 do
8
+ create_table :records do |t|
9
+ t.text :text, null: false, default: ''
10
+ end
11
+ end
12
+ end
13
+
14
+ class Record < ActiveRecord::Base
15
+ self.table_name = 'records'
16
+ end
17
+
18
+ class Record0 < Record
19
+ before_save do
20
+ self.text += "0"
21
+ end
22
+
23
+ after_save do
24
+ self.text += "9"
25
+ end
26
+ end
27
+
28
+ class Record1 < Record0
29
+ before_save do
30
+ self.text += '1'
31
+ end
32
+ before_save do
33
+ self.text += '2'
34
+ end
35
+ after_save do
36
+ self.text += '3'
37
+ end
38
+ after_save do
39
+ self.text += '4'
40
+ end
41
+ end
42
+
43
+ class Record2 < Record0
44
+ set_save_order :first, :default, :last
45
+
46
+ after_save do
47
+ self.text += '5'
48
+ end
49
+ before_save priority: :last do
50
+ self.text += '3'
51
+ end
52
+ after_save priority: :last do
53
+ self.text += '6'
54
+ end
55
+ before_save priority: :first do
56
+ self.text += '1'
57
+ end
58
+ after_save priority: :first do
59
+ self.text += '4'
60
+ end
61
+ before_save do
62
+ self.text += '2'
63
+ end
64
+ end
65
+
66
+ class Record3 < Record2
67
+ set_save_order :last, :default, :first
68
+
69
+ after_save priority: :first do
70
+ self.text += '8'
71
+ end
72
+ end
73
+
74
+ class Record4 < Record
75
+ set_save_order :another, :default
76
+
77
+ around_save do |_, block|
78
+ self.text += "3"
79
+ block.call
80
+ self.text += "4"
81
+ end
82
+
83
+ around_save priority: :another do |_, block|
84
+ self.text += "1"
85
+ block.call
86
+ self.text += "2"
87
+ end
88
+ end
89
+
90
+ let(:record1) { Record1.new.tap(&:save) }
91
+ let(:record2) { Record2.new.tap(&:save) }
92
+ let(:record3) { Record3.new.tap(&:save) }
93
+ let(:record4) { Record4.new.tap(&:save) }
94
+
95
+ it 'has default order if order is not specified' do
96
+ expect(record1.text).to eq('012934')
97
+ end
98
+
99
+ it 'has proper order when order is specified' do
100
+ expect(record2.text).to eq('10234956')
101
+ end
102
+
103
+ it "base class doesn't have order defined" do
104
+ expect(Record.send(:get_callbacks, :save).config[:order]).to eq([:default])
105
+ end
106
+
107
+ it "allows to change the order in derived class" do
108
+ expect(record3.text).to eq('302169548')
109
+ end
110
+
111
+ it "has proper order when around is used" do
112
+ expect(record4.text).to eq('1342')
113
+ end
114
+ end
@@ -0,0 +1,137 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ActiveSupport::Callbacks do
4
+ class Example
5
+ include ActiveSupport::Callbacks
6
+
7
+ def self.callbacks
8
+ get_callbacks(:save).entries
9
+ end
10
+
11
+ def callbacks
12
+ self.class.callbacks
13
+ end
14
+
15
+ def test_method
16
+ run_callbacks :save do
17
+ puts "test method"
18
+ end
19
+ end
20
+ end
21
+
22
+ class Example1 < Example
23
+ define_callbacks :save, order: [:first, :default, :last]
24
+
25
+ set_callback :save, :before do
26
+ puts "before default"
27
+ end
28
+
29
+ set_callback :save, :after do
30
+ puts "after default"
31
+ end
32
+
33
+ set_callback :save, :before, priority: :first do
34
+ puts "before first"
35
+ end
36
+
37
+ set_callback :save, :after, priority: :last do
38
+ puts "after last"
39
+ end
40
+
41
+ set_callback :save, :before, priority: :last do
42
+ puts "before last"
43
+ end
44
+
45
+ set_callback :save, :after, priority: :first do
46
+ puts "after first"
47
+ end
48
+ end
49
+
50
+ class Example2 < Example
51
+ define_callbacks :save, order: [:default, :another]
52
+
53
+ set_callback :save, :before, priority: :another do
54
+ puts "before another"
55
+ end
56
+
57
+ set_callback :save, :before do
58
+ puts "before default"
59
+ end
60
+ end
61
+
62
+ class Example3 < Example
63
+ define_callbacks :save, order: [:default, :another]
64
+
65
+ set_callback :save, :after, priority: :another do
66
+ puts "after another"
67
+ end
68
+
69
+ set_callback :save, :after do
70
+ puts "after default"
71
+ end
72
+ end
73
+
74
+ class Example4 < Example1
75
+ set_callback :save, :before do
76
+ puts "before example4"
77
+ end
78
+ set_callback :save, :after do
79
+ puts "after example4"
80
+ end
81
+ end
82
+
83
+ class Example5 < Example
84
+ define_callbacks :save, order: [:another, :default]
85
+
86
+ set_callback :save, :around do |_, block|
87
+ puts "around 1"
88
+ block.call
89
+ puts "around 2"
90
+ end
91
+
92
+ set_callback :save, :around, priority: :another do |_, block|
93
+ puts "around 3"
94
+ block.call
95
+ puts "around 4"
96
+ end
97
+ end
98
+
99
+ let(:example1) { Example1.new }
100
+ let(:example2) { Example2.new }
101
+ let(:example3) { Example3.new }
102
+ let(:example4) { Example4.new }
103
+ let(:example5) { Example5.new }
104
+
105
+ it 'accepts priority in callback options' do
106
+ expect(example2.callbacks[1].priority).to eq(:another)
107
+ end
108
+
109
+ it "has default priority if :priority is not specified" do
110
+ expect(example2.callbacks[1].priority).to eq(:another)
111
+ end
112
+
113
+ it "has proper order when called" do
114
+ expect { example2.test_method }.to output("before default\nbefore another\ntest method\n").to_stdout
115
+ expect { example3.test_method }.to output("test method\nafter another\nafter default\n").to_stdout
116
+ expect { example1.test_method }.to output("before first\nbefore default\nbefore last\ntest method\nafter last\nafter default\nafter first\n").to_stdout
117
+ end
118
+
119
+ it "should fail if unknown priority is specified" do
120
+ expect do
121
+ class ExampleWithException < Example
122
+ define_callbacks :save
123
+ set_callback :save, :after, priority: :another do
124
+ puts "after another"
125
+ end
126
+ end
127
+ end.to raise_error(ArgumentError)
128
+ end
129
+
130
+ it "has proper order in derived class" do
131
+ expect { example4.test_method }.to output("before first\nbefore default\nbefore example4\nbefore last\ntest method\nafter last\nafter example4\nafter default\nafter first\n").to_stdout
132
+ end
133
+
134
+ it "has proper order when around callbacks are used" do
135
+ expect { example5.test_method }.to output("around 3\naround 1\ntest method\naround 2\naround 4\n").to_stdout
136
+ end
137
+ end
@@ -0,0 +1,103 @@
1
+ require 'active_record'
2
+ require 'prioritized_callbacks'
3
+
4
+ # This file was generated by the `rspec --init` command. Conventionally, all
5
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
6
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
7
+ # this file to always be loaded, without a need to explicitly require it in any
8
+ # files.
9
+ #
10
+ # Given that it is always loaded, you are encouraged to keep this file as
11
+ # light-weight as possible. Requiring heavyweight dependencies from this file
12
+ # will add to the boot time of your test suite on EVERY test run, even for an
13
+ # individual file that may not need all of that loaded. Instead, consider making
14
+ # a separate helper file that requires the additional dependencies and performs
15
+ # the additional setup, and require it from the spec files that actually need
16
+ # it.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+ RSpec.configure do |config|
20
+ # rspec-expectations config goes here. You can use an alternate
21
+ # assertion/expectation library such as wrong or the stdlib/minitest
22
+ # assertions if you prefer.
23
+ config.expect_with :rspec do |expectations|
24
+ # This option will default to `true` in RSpec 4. It makes the `description`
25
+ # and `failure_message` of custom matchers include text for helper methods
26
+ # defined using `chain`, e.g.:
27
+ # be_bigger_than(2).and_smaller_than(4).description
28
+ # # => "be bigger than 2 and smaller than 4"
29
+ # ...rather than:
30
+ # # => "be bigger than 2"
31
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
32
+ end
33
+
34
+ # rspec-mocks config goes here. You can use an alternate test double
35
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
36
+ config.mock_with :rspec do |mocks|
37
+ # Prevents you from mocking or stubbing a method that does not exist on
38
+ # a real object. This is generally recommended, and will default to
39
+ # `true` in RSpec 4.
40
+ mocks.verify_partial_doubles = true
41
+ end
42
+
43
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
44
+ # have no way to turn it off -- the option exists only for backwards
45
+ # compatibility in RSpec 3). It causes shared context metadata to be
46
+ # inherited by the metadata hash of host groups and examples, rather than
47
+ # triggering implicit auto-inclusion in groups with matching metadata.
48
+ config.shared_context_metadata_behavior = :apply_to_host_groups
49
+
50
+ # The settings below are suggested to provide a good initial experience
51
+ # with RSpec, but feel free to customize to your heart's content.
52
+ =begin
53
+ # This allows you to limit a spec run to individual examples or groups
54
+ # you care about by tagging them with `:focus` metadata. When nothing
55
+ # is tagged with `:focus`, all examples get run. RSpec also provides
56
+ # aliases for `it`, `describe`, and `context` that include `:focus`
57
+ # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
58
+ config.filter_run_when_matching :focus
59
+
60
+ # Allows RSpec to persist some state between runs in order to support
61
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
62
+ # you configure your source control system to ignore this file.
63
+ config.example_status_persistence_file_path = "spec/examples.txt"
64
+
65
+ # Limits the available syntax to the non-monkey patched syntax that is
66
+ # recommended. For more details, see:
67
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
68
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
69
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
70
+ config.disable_monkey_patching!
71
+
72
+ # This setting enables warnings. It's recommended, but in some cases may
73
+ # be too noisy due to issues in dependencies.
74
+ config.warnings = true
75
+
76
+ # Many RSpec users commonly either run the entire suite or an individual
77
+ # file, and it's useful to allow more verbose output when running an
78
+ # individual spec file.
79
+ if config.files_to_run.one?
80
+ # Use the documentation formatter for detailed output,
81
+ # unless a formatter has already been configured
82
+ # (e.g. via a command-line flag).
83
+ config.default_formatter = "doc"
84
+ end
85
+
86
+ # Print the 10 slowest examples and example groups at the
87
+ # end of the spec run, to help surface which specs are running
88
+ # particularly slow.
89
+ config.profile_examples = 10
90
+
91
+ # Run specs in random order to surface order dependencies. If you find an
92
+ # order dependency and want to debug it, you can fix the order by providing
93
+ # the seed, which is printed after each run.
94
+ # --seed 1234
95
+ config.order = :random
96
+
97
+ # Seed global randomization in this process using the `--seed` CLI option.
98
+ # Setting this allows you to use `--seed` to deterministically reproduce
99
+ # test failures related to randomization by passing the same `--seed` value
100
+ # as the one that triggered the failure.
101
+ Kernel.srand config.seed
102
+ =end
103
+ end
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: prioritized_callbacks
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Anton Argirov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-06-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
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: rake
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: rspec
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: activerecord
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: sqlite3
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: appraisal
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: This gem allows to define a custom order in which callbacks are called
112
+ email: anton.argirov@gmail.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - ".rspec"
118
+ - Appraisals
119
+ - Gemfile
120
+ - LICENSE.txt
121
+ - README.md
122
+ - Rakefile
123
+ - gemfiles/rails_50.gemfile
124
+ - gemfiles/rails_50.gemfile.lock
125
+ - gemfiles/rails_51.gemfile
126
+ - gemfiles/rails_51.gemfile.lock
127
+ - gemfiles/rails_52.gemfile
128
+ - gemfiles/rails_52.gemfile.lock
129
+ - gemfiles/rails_60.gemfile
130
+ - gemfiles/rails_60.gemfile.lock
131
+ - gemfiles/rails_61.gemfile
132
+ - gemfiles/rails_61.gemfile.lock
133
+ - lib/prioritized_callbacks.rb
134
+ - lib/prioritized_callbacks/active_model/callbacks_patch.rb
135
+ - lib/prioritized_callbacks/active_support/callback_chain_patch.rb
136
+ - lib/prioritized_callbacks/active_support/callback_patch.rb
137
+ - lib/prioritized_callbacks/active_support/callbacks_class_methods_patch.rb
138
+ - lib/prioritized_callbacks/callback_list.rb
139
+ - lib/prioritized_callbacks/version.rb
140
+ - prioritized_callbacks.gemspec
141
+ - spec/lib/activerecord_callbacks_spec.rb
142
+ - spec/lib/activesupport_callbacks_spec.rb
143
+ - spec/spec_helper.rb
144
+ homepage: https://github.com/anteo/prioritized_callbacks
145
+ licenses:
146
+ - MIT
147
+ metadata: {}
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubygems_version: 3.1.4
164
+ signing_key:
165
+ specification_version: 4
166
+ summary: Prioritizing ActiveSupport::Callbacks
167
+ test_files:
168
+ - spec/lib/activerecord_callbacks_spec.rb
169
+ - spec/lib/activesupport_callbacks_spec.rb
170
+ - spec/spec_helper.rb