prioritized_callbacks 0.0.2

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
+ 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