提交 94f8e8c8 编写于 作者: Z zvkemp

use a proxy matcher for AS::N fanout

上级 b67d5c6d
* Revise `ActiveSupport::Notifications.unsubscribe` to correctly handle Regex or other multiple-pattern subscribers.
*Zach Kemp*
* Add `before_reset` callback to `CurrentAttributes` and define `after_reset` as an alias of `resets` for symmetry. * Add `before_reset` callback to `CurrentAttributes` and define `after_reset` as an alias of `resets` for symmetry.
*Rosa Gutierrez* *Rosa Gutierrez*
......
...@@ -153,6 +153,15 @@ module ActiveSupport ...@@ -153,6 +153,15 @@ module ActiveSupport
# #
# ActiveSupport::Notifications.unsubscribe("render") # ActiveSupport::Notifications.unsubscribe("render")
# #
# Subscribers using a regexp or other pattern-matching object will remain subscribed
# to all events that match their original pattern, unless those events match a string
# passed to `unsubscribe`:
#
# subscriber = ActiveSupport::Notifications.subscribe(/render/) { }
# ActiveSupport::Notifications.unsubscribe('render_template.action_view')
# subscriber.matches?('render_template.action_view') # => false
# subscriber.matches?('render_partial.action_view') # => true
#
# == Default Queue # == Default Queue
# #
# Notifications ships with a queue implementation that consumes and publishes events # Notifications ships with a queue implementation that consumes and publishes events
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
require "mutex_m" require "mutex_m"
require "concurrent/map" require "concurrent/map"
require "set"
module ActiveSupport module ActiveSupport
module Notifications module Notifications
...@@ -39,6 +40,7 @@ def unsubscribe(subscriber_or_name) ...@@ -39,6 +40,7 @@ def unsubscribe(subscriber_or_name)
when String when String
@string_subscribers[subscriber_or_name].clear @string_subscribers[subscriber_or_name].clear
@listeners_for.delete(subscriber_or_name) @listeners_for.delete(subscriber_or_name)
@other_subscribers.each { |sub| sub.unsubscribe!(subscriber_or_name) }
else else
pattern = subscriber_or_name.try(:pattern) pattern = subscriber_or_name.try(:pattern)
if String === pattern if String === pattern
...@@ -113,11 +115,33 @@ def self.wrap_all(pattern, subscriber) ...@@ -113,11 +115,33 @@ def self.wrap_all(pattern, subscriber)
end end
end end
class Matcher #:nodoc:
attr_reader :pattern, :exclusions
def self.wrap(pattern)
return pattern if String === pattern
new(pattern)
end
def initialize(pattern)
@pattern = pattern
@exclusions = Set.new
end
def unsubscribe!(name)
exclusions << -name if pattern === name
end
def ===(name)
pattern === name && !exclusions.include?(name)
end
end
class Evented #:nodoc: class Evented #:nodoc:
attr_reader :pattern attr_reader :pattern
def initialize(pattern, delegate) def initialize(pattern, delegate)
@pattern = pattern @pattern = Matcher.wrap(pattern)
@delegate = delegate @delegate = delegate
@can_publish = delegate.respond_to?(:publish) @can_publish = delegate.respond_to?(:publish)
end end
...@@ -137,11 +161,15 @@ def finish(name, id, payload) ...@@ -137,11 +161,15 @@ def finish(name, id, payload)
end end
def subscribed_to?(name) def subscribed_to?(name)
@pattern === name pattern === name
end end
def matches?(name) def matches?(name)
@pattern && @pattern === name pattern && pattern === name
end
def unsubscribe!(name)
pattern.unsubscribe!(name)
end end
end end
...@@ -204,6 +232,10 @@ def subscribed_to?(name) ...@@ -204,6 +232,10 @@ def subscribed_to?(name)
true true
end end
def unsubscribe!(*)
false
end
alias :matches? :=== alias :matches? :===
end end
end end
......
...@@ -84,6 +84,39 @@ def test_evented_listener_priority ...@@ -84,6 +84,39 @@ def test_evented_listener_priority
[:finish, "hi", 1, {}] [:finish, "hi", 1, {}]
], listener.events ], listener.events
end end
def test_listen_to_regexp
notifier = Fanout.new
listener = Listener.new
notifier.subscribe(/[a-z]*.world/, listener)
notifier.start("hi.world", 1, {})
notifier.finish("hi.world", 2, {})
notifier.start("hello.world", 1, {})
notifier.finish("hello.world", 2, {})
assert_equal [
[:start, "hi.world", 1, {}],
[:finish, "hi.world", 2, {}],
[:start, "hello.world", 1, {}],
[:finish, "hello.world", 2, {}]
], listener.events
end
def test_listen_to_regexp_with_exclusions
notifier = Fanout.new
listener = Listener.new
notifier.subscribe(/[a-z]*.world/, listener)
notifier.unsubscribe("hi.world")
notifier.start("hi.world", 1, {})
notifier.finish("hi.world", 2, {})
notifier.start("hello.world", 1, {})
notifier.finish("hello.world", 2, {})
assert_equal [
[:start, "hello.world", 1, {}],
[:finish, "hello.world", 2, {}]
], listener.events
end
end end
end end
end end
...@@ -128,6 +128,25 @@ def test_unsubscribing_by_name_leaves_the_other_subscriptions ...@@ -128,6 +128,25 @@ def test_unsubscribing_by_name_leaves_the_other_subscriptions
assert_equal [["named.subscription", :foo], ["named.subscription", :foo]], @events assert_equal [["named.subscription", :foo], ["named.subscription", :foo]], @events
end end
def test_unsubscribing_by_name_leaves_regexp_matched_subscriptions
@matched_events = []
@notifier.subscribe(/subscription/) { |*args| @matched_events << event(*args) }
@notifier.publish("named.subscription", :before)
@notifier.wait
[@events, @named_events, @matched_events].each do |collector|
assert_includes(collector, ["named.subscription", :before])
end
@notifier.unsubscribe("named.subscription")
@notifier.publish("named.subscription", :after)
@notifier.publish("other.subscription", :after)
@notifier.wait
assert_includes(@events, ["named.subscription", :after])
assert_includes(@events, ["other.subscription", :after])
assert_includes(@matched_events, ["other.subscription", :after])
assert_not_includes(@matched_events, ["named.subscription", :after])
assert_not_includes(@named_events, ["named.subscription", :after])
end
private private
def event(*args) def event(*args)
args args
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册