Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change internal mapping for event subscriber registrations #3758

Merged

Conversation

spaghetticode
Copy link
Member

@spaghetticode spaghetticode commented Sep 11, 2020

Fixes #3757

The previous way of registering subscribers could generate some issues with rails 6 applications, due to how Zeitwerk (re)loads classes in initializers. The previous approach was also scattering the information into every single subscriber, while now it's centralized in a single place.

Currently, Spree::Event.subscribers includes only the list of subscriber classes, while the rest of the information is delegated to each subscriber. This is how information can be accessed with the current interface:

# list of subscribers:
$ Spree::Event.subscribers
 => #<Spree::Core::ClassConstantizer::Set:0x00007ff62dd69a18 @collection=#<Set: {"AppEventSubscriber", "Spree::MailerSubscriber"}>> 

# internal mapping for AppEventSubscriber:
$ AppEventSubscriber.event_actions
 => {:app_event=>"app_event"} 

# mapping to the ActiveSupport::Notifications subscription:

```ruby
AppEventSubscriber.app_event_handler
 => #<ActiveSupport::Notifications::Fanout::Subscribers::Timed:0x00007ff67e582880 @pattern="app_event.spree", @delegate=#<Proc:0x00007ff67e582df8@/Users/andrea/nebulab/solidus_event_subscribers_autoload/vendor/gems/solidus/core/lib/spree/event/adapters/active_support_notifications.rb:18>, @can_publish=false> 

After merging this PR, the functionality provided by Spree::Event.subscribers will be managed by the new Spree::Event.subscriber_registry. The registry stores information with the format:

{ subscriber_class_name => { subscriber_method => ActiveSupportNotifications_subscription } }

Real world example:

pp Spree::Event.subscriber_registry
#<Spree::Event::SubscriberRegistry:0x00007fce3e80d798
 @registry=
  {"Spree::MailerSubscriber"=>
    {:order_finalized=>
      #<ActiveSupport::Notifications::Fanout::Subscribers::Timed:0x00007fce3ec86e78
       @can_publish=false,
       @delegate=
        #<Proc:0x00007fce3ec86f40@/Users/andrea/nebulab/solidus/core/lib/spree/event/adapters/active_support_notifications.rb:18>,
       @pattern="order_finalized.spree">,
     :send_reimbursement_email=>
      #<ActiveSupport::Notifications::Fanout::Subscribers::Timed:0x00007fce3ec86a18
       @can_publish=false,
       @delegate=
        #<Proc:0x00007fce3ec86ab8@/Users/andrea/nebulab/solidus/core/lib/spree/event/adapters/active_support_notifications.rb:18>,
       @pattern="reimbursement_reimbursed.spree">},
   "AppSubscriber"=>
    {:app_event=>
      #<ActiveSupport::Notifications::Fanout::Subscribers::Timed:0x00007fce3ec851e0
       @can_publish=false,
       @delegate=
        #<Proc:0x00007fce3ec85280@/Users/andrea/nebulab/solidus/core/lib/spree/event/adapters/active_support_notifications.rb:18>,
       @pattern="app_event.spree">}},
 @semaphore=#<Thread::Mutex:0x00007fce3e80d6a8>

This PR requires a small change in solidus_support too, see solidusio/solidus_support#53

These changes should be transparent to stores as they only modify how event subscribers are mapped and managed internally in Solidus.

Many thanks to @cedum and @DanielePalombo who helped in moving forward the PR.

Checklist:

  • I have followed Pull Request guidelines
  • I have added a detailed description into each commit message
  • I have added tests to cover this change (if needed)

@spaghetticode spaghetticode self-assigned this Sep 11, 2020
@spaghetticode spaghetticode force-pushed the spaghetticode/event-subscribers-mappings branch from 91eaf64 to a20b488 Compare September 18, 2020 12:48
@spaghetticode spaghetticode force-pushed the spaghetticode/event-subscribers-mappings branch 4 times, most recently from 2cfe4be to 1304862 Compare September 18, 2020 15:45
core/lib/spree/event/subscriber.rb Outdated Show resolved Hide resolved
core/lib/spree/event/subscriber.rb Outdated Show resolved Hide resolved
@spaghetticode spaghetticode force-pushed the spaghetticode/event-subscribers-mappings branch 2 times, most recently from a090359 to 1bbe5b1 Compare September 25, 2020 07:55
@kennyadsl kennyadsl added this to the 2.11 milestone Sep 25, 2020
spaghetticode added a commit to nebulab/solidus_support that referenced this pull request Sep 25, 2020
The new method `Spree::Event.subscribe_subscribers!` is introduced
in Solidus with solidusio/solidus#3758 as a better way to subscribe
event subscribers. Under the hood, event subscribers use new interal
mappings. We need to keep around the old interface as well until it
won't be supported anymore in Solidus.
spaghetticode added a commit to nebulab/solidus_support that referenced this pull request Sep 25, 2020
The new method `Spree::Event.subscribe_subscribers!` is introduced
in Solidus with solidusio/solidus#3758 as a better way to subscribe
event subscribers: under the hood, event subscribers use new interal
mappings.

Also, we need to keep around the old interface as well until it won't
be supported anymore in Solidus.
@spaghetticode spaghetticode marked this pull request as ready for review September 25, 2020 09:38
Copy link
Member

@kennyadsl kennyadsl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments, I think we are close to a solution, thanks @spaghetticode !

send event_action, event
}
subscription = Spree::Event.subscribe(event_name) { |event| send event_action, event }
# old mappings, to be removed when Solidus 2.10 is not supported anymore:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use the word deprecated instead of old? Just because it will be easier to find it if we perform a search of all the deprecated code within the codebase with a text editor.

subscription = Spree::Event.subscribe(event_name) { |event| send event_action, event }
# old mappings, to be removed when Solidus 2.10 is not supported anymore:
send "#{event_action}_handler=", subscription
# new mappings:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can get rid of this comment, probably improving a little bit the one with the deprecated code to explain that this other way is the new way of doing things.

@@ -14,7 +14,9 @@ module Preferences
#
# @deprecated
def reset_spree_preferences(&config_block)
Spree::Config.instance_variables.each { |iv| Spree::Config.remove_instance_variable(iv) }
Spree::Config.instance_variables.
reject { |iv| iv == :@events_configuration }.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this. What if some specs need to reset that configuration? It's applicable? How do people know that all the preferences except that one will be reset when calling reset_spree_preferences in their code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helper method is deprecated, so it won't be around for long. We don't use it anymore in our codebase, but we have some specs that verify its behavior. I'm not sure if creating a better workaround for making it work properly with the new event mappings is worth the effort at this point.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, it's deprecated. 👍

Spree::Event.subscribers.each_key do |klass|
klass.constantize.subscribe!
end
Spree::Event.subscribe_subscribers!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more I look into this and the more I think we need another class called Spree::Event::Subscribers that acts as a collection of subscribers. No matter if it's hash inside but the interface will probably look better, having something like:

Spree::Event.subscribers.subscribe_all.

WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, makes sense! I'll work on your suggestion 👍

@@ -54,7 +54,14 @@ def self.included(base)
# end
# end
def event_action(method_name, event_name: nil)
mattr_accessor "#{method_name}_handler"
mattr_writer "#{method_name}_handler"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we need to deprecate the writer as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still use the writer internally to keep compatibility, if somebody is still using the reader in their codebase (we don't use the reader anymore). So, if we deprecate the writer, then we'll need to silence the deprecation when using it. So, deprecating the writer would be impractical at this point.

But I think we don't really need to deprecate the writer as well. The only reason for some developers to be using the writer in their codebase is to set a value to be accessed from the reader. When this happens, they will see the deprecation when the matching reader is called.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks Andrea.

@kennyadsl kennyadsl added the changelog:solidus_core Changes to the solidus_core gem label Sep 25, 2020
@spaghetticode spaghetticode force-pushed the spaghetticode/event-subscribers-mappings branch from 1bbe5b1 to c49b9f5 Compare October 2, 2020 12:53
@spaghetticode spaghetticode force-pushed the spaghetticode/event-subscribers-mappings branch 3 times, most recently from 2c48a9f to be6e796 Compare October 2, 2020 15:32
@spaghetticode spaghetticode force-pushed the spaghetticode/event-subscribers-mappings branch from be6e796 to b2a8847 Compare October 2, 2020 15:34
event_actions.keys.each do |event_action|
Spree::Event.unsubscribe send("#{event_action}_handler")
end
Spree::Deprecation.warn("#{name}.unsubscribe! is deprecated. Please move your event subscribers to folder `app/subscribers/*_subscriber.rb` and they will be unloaded automatically. ", caller)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the message here is incorrect. You have to delete the subscriber file to unsubscribe, isn't it? Also, I'd keep the method since maybe you don't have control over where the file has been added (core or an extension).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're right about the message. And also the remainder of the comment is right. I think we need to keep offering that kind of granularity, even though it will be rarely used.

@spaghetticode spaghetticode force-pushed the spaghetticode/event-subscribers-mappings branch from b2a8847 to 0fa6598 Compare October 16, 2020 08:31
@spaghetticode spaghetticode force-pushed the spaghetticode/event-subscribers-mappings branch 2 times, most recently from 5d36a67 to 465a8bf Compare October 16, 2020 10:55
@spaghetticode spaghetticode force-pushed the spaghetticode/event-subscribers-mappings branch 3 times, most recently from 880bcde to 996bd4f Compare October 16, 2020 15:05
The class `Spree::Event::SubscriberRegistry` is introduced in
order to centralize and handle in a dedicated environment
event mappings for subscriber modules. This was previously
responsibility of each `Spree::Event::Subscriber` module, but
due to some issues with Zeitwerk and the way these modules
were activated in rails initializers, that approach is not
working properly anymore with Rails 6.

This class also manages the activation/deactivation of all
event subscribers that happens at boot time.

The activation/deactivation code is threadsafe, just in the
remote case that somebody tries to use it in a multithreaded
context. This does not happen in the standard use-case of
this class, as it is being used during the app initialization
time.
Subscribers are loaded once at runtime during the app initialization
process, so if they get reset by `reset_spree_preferences` then the
original value will not come back.
@spaghetticode spaghetticode force-pushed the spaghetticode/event-subscribers-mappings branch from 996bd4f to 6816374 Compare October 16, 2020 15:54
DanielePalombo and others added 5 commits October 16, 2020 18:22
The event  config setting `Spree::Event.config.subscribers` (accessible
via Spree::Event.subscribers) is now deprecated in favor of the new
subscriber registry.
`Spree::Event.subscribers` is deprecated and its functionality is
replaced by `.subscriber_registry`.

`.subscriber_registry` is not a 100% compatible replacement for
`.subscribers`, as the internal mapping system for event subscribers
is changing completely, but for existing Solidus stores the change
should be transparent enough.

`Spree::Event.require_subscriber_files` is deprecated and delegated
to the subscriber registry object. The method with the same name
defined in `Spree::Event::SubscriberRegistry` is now private, as
there is no use in keeping it public.
As `Spree::Event.subscribers` is deprecated and being replaced
by `Spree::Event.subscriber_registry`.
`Spree::Event::Subscriber.subscribe!` is deprecated and replaced by
`::activate`.

`Spree::Event::Subscriber.unsubscribe!` is deprecated and replaced by
`::deactivate`.

The method names `subscribe` and `unsubscribe` are already used by
`Spree::Event`, so the naming was a bit confusing; `activate` and
`deactivate` should help in making this a bit more clear.

`::activate` and `::deacticate` are now using the new subscriber
registry for activation/deactivation, as `Spree::Event.subscribers`
is now deprecated.
We're keeping the old event mappings handler methods, as somebody may
still be relying on them, but they're now deprecated.
@spaghetticode spaghetticode force-pushed the spaghetticode/event-subscribers-mappings branch from 6816374 to 6e96808 Compare October 16, 2020 16:24
Copy link
Member

@kennyadsl kennyadsl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found something that looks like a mistake, let me know if I'm missing something instead.

core/lib/spree/core/engine.rb Outdated Show resolved Hide resolved
The methods `Spree::Event.activate_autoloadable_subscribers` and
`Spree::Event.deactivate_all_subscribers` encapsulate in a single
call all the logic needed to activate event subscribers during
the app init process.

Those methods will need to be used also in the engine.rb file
provided by the gem `solidus_support`.
@spaghetticode spaghetticode force-pushed the spaghetticode/event-subscribers-mappings branch 2 times, most recently from c49b9f5 to b2a5a03 Compare October 19, 2020 15:42
spaghetticode added a commit to nebulab/solidus_support that referenced this pull request Oct 19, 2020
The new method `Spree::Event.activate_all_subscribers` is introduced
in Solidus with solidusio/solidus#3758 as a better way to subscribe
event subscribers. Under the hood, event subscribers use new interal
mappings.

Also, we need to keep around the old interface as well until it won't
be supported anymore in Solidus.
@spaghetticode
Copy link
Member Author

@kennyadsl thanks for spotting the bug ❤️

I have just tested this PR with a newly generated sandbox app using solidus_support with this branch, and it seems to be working properly when adding a custom autoloadable subscriber.

Copy link
Member

@kennyadsl kennyadsl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @spaghetticode!

@kennyadsl kennyadsl merged commit 8c0a907 into solidusio:master Oct 23, 2020
@kennyadsl kennyadsl deleted the spaghetticode/event-subscribers-mappings branch October 23, 2020 08:27
AlessioRocco pushed a commit to nebulab/solidus_support that referenced this pull request Nov 20, 2020
The new method `Spree::Event.activate_all_subscribers` is introduced
in Solidus with solidusio/solidus#3758 as a better way to subscribe
event subscribers. Under the hood, event subscribers use new interal
mappings.

Also, we need to keep around the old interface as well until it won't
be supported anymore in Solidus.
@elia
Copy link
Member

elia commented Dec 21, 2020

I'm getting these deprecations while running a generator (although I don't believe that to be relevant), I think the caller should be refined to point at the right level of the backtrace, instead of showing -e.

DEPRECATION WARNING: `Spree::Event.subscribers` is deprecated. Please use `Spree::Event.subscriber_registry` instead. (called from <main> at -e:1)
DEPRECATION WARNING: `Spree::Config.events.subscribers` is deprecated. Please use `Spree::Config.events.subscriber_registry`. (called from <main> at -e:1)
DEPRECATION WARNING: Spree::MailerSubscriber.subscribe! is deprecated. Please use `Spree::MailerSubscriber.activate`. (called from <main> at -e:1)
DEPRECATION WARNING: `Spree::Event.subscribers` is deprecated. Please use `Spree::Event.subscriber_registry` instead. (called from <main> at -e:1)
DEPRECATION WARNING: `Spree::Config.events.subscribers` is deprecated. Please use `Spree::Config.events.subscriber_registry`. (called from <main> at -e:1)
DEPRECATION WARNING: Spree::MailerSubscriber.subscribe! is deprecated. Please use `Spree::MailerSubscriber.activate`. (called from <main> at -e:1)
DEPRECATION WARNING: `Spree::Event.subscribers` is deprecated. Please use `Spree::Event.subscriber_registry` instead. (called from <main> at -e:1)
DEPRECATION WARNING: `Spree::Config.events.subscribers` is deprecated. Please use `Spree::Config.events.subscriber_registry`. (called from <main> at -e:1)
DEPRECATION WARNING: Spree::MailerSubscriber.subscribe! is deprecated. Please use `Spree::MailerSubscriber.activate`. (called from <main> at -e:1)
DEPRECATION WARNING: `Spree::Event.subscribers` is deprecated. Please use `Spree::Event.subscriber_registry` instead. (called from <main> at -e:1)
DEPRECATION WARNING: `Spree::Config.events.subscribers` is deprecated. Please use `Spree::Config.events.subscriber_registry`. (called from <main> at -e:1)
DEPRECATION WARNING: Spree::MailerSubscriber.subscribe! is deprecated. Please use `Spree::MailerSubscriber.activate`. (called from <main> at -e:1)

Unless I'm mistaken and this is correct for some reason we should probably turn this comment into an issue

cc @spaghetticode

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changelog:solidus_core Changes to the solidus_core gem
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Event subscribers loaded twice with Rails 6/Zeitwerk
6 participants