diff --git a/.rubocop.yml b/.rubocop.yml index 2e0537dc..48302228 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -10,3 +10,4 @@ RSpec/DescribeClass: Exclude: - spec/i18n_spec.rb - "spec/system/**/*" + - "spec/lib/**/*" diff --git a/lib/decidim/term_customizer.rb b/lib/decidim/term_customizer.rb index 7e3f2349..b1ff98d3 100644 --- a/lib/decidim/term_customizer.rb +++ b/lib/decidim/term_customizer.rb @@ -37,7 +37,13 @@ module TermCustomizer end class << self - attr_accessor :loader + def loader + Thread.current.thread_variable_get(:term_customizer_loader) + end + + def loader=(loader) + Thread.current.thread_variable_set(:term_customizer_loader, loader) + end end end end diff --git a/lib/decidim/term_customizer/engine.rb b/lib/decidim/term_customizer/engine.rb index 82f44ce8..25e1a888 100644 --- a/lib/decidim/term_customizer/engine.rb +++ b/lib/decidim/term_customizer/engine.rb @@ -33,6 +33,10 @@ class Engine < ::Rails::Engine customizer_backend.reload! end + ActiveSupport::Notifications.subscribe "process_action.action_controller" do + TermCustomizer.loader = nil + end + # The jobs are generally run in different context than the controllers # which causes the term customizations not to be active. During the # jobs, only the organization and global context translations are loaded @@ -62,6 +66,10 @@ class Engine < ::Rails::Engine # Force the backend to reload the translations for the job customizer_backend.reload! end + + ActiveSupport::Notifications.subscribe "perform.active_job" do + TermCustomizer.loader = nil + end end end end diff --git a/lib/decidim/term_customizer/i18n_backend.rb b/lib/decidim/term_customizer/i18n_backend.rb index 38dfd8ee..30cb6218 100644 --- a/lib/decidim/term_customizer/i18n_backend.rb +++ b/lib/decidim/term_customizer/i18n_backend.rb @@ -30,7 +30,6 @@ def reload! end def translations - return @translations if @translations return {} unless TermCustomizer.loader @translations = TermCustomizer.loader.translations_hash diff --git a/spec/lib/decidim/term_customizer/term_customizer_loader_spec.rb b/spec/lib/decidim/term_customizer/term_customizer_loader_spec.rb new file mode 100644 index 00000000..21afd0e6 --- /dev/null +++ b/spec/lib/decidim/term_customizer/term_customizer_loader_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Decidim::TermCustomizer.loader" do + describe ".loader" do + it "is isolated per thread" do + loader_org1 = double("loader_org1") + loader_org2 = double("loader_org2") + + thread1_loader = nil + thread2_loader = nil + + t1 = Thread.new do + Decidim::TermCustomizer.loader = loader_org1 + sleep 0.05 + thread1_loader = Decidim::TermCustomizer.loader + end + + t2 = Thread.new do + sleep 0.02 + Decidim::TermCustomizer.loader = loader_org2 + thread2_loader = Decidim::TermCustomizer.loader + end + + t1.join + t2.join + + expect(thread1_loader).to eq(loader_org1) + expect(thread2_loader).to eq(loader_org2) + end + end + + describe "Decidim::TermCustomizer::I18nBackend" do + let(:backend) { Decidim::TermCustomizer::I18nBackend.new } + + let(:translations_org1) { { en: { decidim: { test_key: "Org 1 translation" } } } } + let(:translations_org2) { { en: { decidim: { test_key: "Org 2 translation" } } } } + + it "does not bleed translations between concurrent threads" do + loader_org1 = double("loader_org1", translations_hash: translations_org1) + loader_org2 = double("loader_org2", translations_hash: translations_org2) + + result_thread1 = nil + result_thread2 = nil + + t1 = Thread.new do + Decidim::TermCustomizer.loader = loader_org1 + sleep 0.05 + result_thread1 = backend.translations + end + + t2 = Thread.new do + sleep 0.02 + Decidim::TermCustomizer.loader = loader_org2 + result_thread2 = backend.translations + end + + t1.join + t2.join + + expect(result_thread1).to eq(translations_org1) + expect(result_thread2).to eq(translations_org2) + end + end +end