diff options
author | Lin Jen-Shin <godfat@godfat.org> | 2018-08-30 19:05:00 +0800 |
---|---|---|
committer | Lin Jen-Shin <godfat@godfat.org> | 2018-09-11 18:45:49 +0800 |
commit | 71fefe147475bb85c67de95fba8211385cfb0a79 (patch) | |
tree | 12a8d1409f699ccd0832f6a26dad055c05b19898 | |
parent | c56f2b96159afaf6f1e0831d0e7a756a40568cab (diff) | |
download | gitlab-ce-71fefe147475bb85c67de95fba8211385cfb0a79.tar.gz |
Properly implement prepending for Concern
-rw-r--r-- | config/initializers/0_as_concern.rb | 22 | ||||
-rw-r--r-- | lib/gitlab/patch/prependable.rb | 71 | ||||
-rw-r--r-- | spec/lib/gitlab/patch/prependable_spec.rb | 121 | ||||
-rw-r--r-- | spec/support/helpers/stub_configuration.rb | 3 |
4 files changed, 197 insertions, 20 deletions
diff --git a/config/initializers/0_as_concern.rb b/config/initializers/0_as_concern.rb index 40232bd6252..ff132547225 100644 --- a/config/initializers/0_as_concern.rb +++ b/config/initializers/0_as_concern.rb @@ -1,25 +1,7 @@ -# This module is based on: https://gist.github.com/bcardarella/5735987 - -module Prependable - def prepend_features(base) - if base.instance_variable_defined?(:@_dependencies) - base.instance_variable_get(:@_dependencies) << self - false - else - return false if base < self - - super - base.singleton_class.send(:prepend, const_get('ClassMethods')) if const_defined?(:ClassMethods) - @_dependencies.each { |dep| base.send(:prepend, dep) } # rubocop:disable Gitlab/ModuleWithInstanceVariables - base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block) # rubocop:disable Gitlab/ModuleWithInstanceVariables - end - end -end +# frozen_string_literal: true module ActiveSupport module Concern - prepend Prependable - - alias_method :prepended, :included + prepend Gitlab::Patch::Prependable end end diff --git a/lib/gitlab/patch/prependable.rb b/lib/gitlab/patch/prependable.rb new file mode 100644 index 00000000000..45fac0ed4a7 --- /dev/null +++ b/lib/gitlab/patch/prependable.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +# rubocop:disable Gitlab/ModuleWithInstanceVariables +module Gitlab + module Patch + module Prependable + class MultiplePrependedBlocks < StandardError + def initialize + super "Cannot define multiple 'prepended' blocks for a Concern" + end + end + + module MetaConcern + def extended(base) + super + base.instance_variable_set(:@_prepend_dependencies, []) + end + end + + def self.prepended(base) + super + base.singleton_class.prepend MetaConcern + end + + def append_features(base) + super + + prepend_features(base) + end + + def prepend_features(base) + if base.instance_variable_defined?(:@_prepend_dependencies) + base.instance_variable_get(:@_prepend_dependencies) << self + false + else + return false if prepended?(base) + + @_prepend_dependencies.each { |dep| base.prepend(dep) } + + super + + if const_defined?(:ClassMethods) + base.singleton_class.prepend const_get(:ClassMethods) + end + + if instance_variable_defined?(:@_prepended_block) + base.class_eval(&@_prepended_block) + end + end + end + + def prepended(base = nil, &block) + if base.nil? + raise MultiplePrependedBlocks if + instance_variable_defined?(:@_prepended_block) + + @_prepended_block = block + else + super + end + end + + def prepended?(base) + index = base.ancestors.index(base) + + @_prepend_dependencies.index(self) || + base.ancestors[0...index].index(self) + end + end + end +end diff --git a/spec/lib/gitlab/patch/prependable_spec.rb b/spec/lib/gitlab/patch/prependable_spec.rb new file mode 100644 index 00000000000..1c90a24c0e8 --- /dev/null +++ b/spec/lib/gitlab/patch/prependable_spec.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +# Patching ActiveSupport::Concern +require_relative '../../../../config/initializers/0_as_concern' + +describe Gitlab::Patch::Prependable do + let(:prepended_modules) { [] } + + let(:ee) do + # So that block in Module.new could see them + prepended_modules_ = prepended_modules + + Module.new do + extend ActiveSupport::Concern + + class_methods do + def class_name + super.tr('C', 'E') + end + end + + this = self + prepended do + prepended_modules_ << this + end + + def name + super.tr('c', 'e') + end + end + end + + let(:ce) do + prepended_modules_ = prepended_modules + ee_ = ee + + Module.new do + extend ActiveSupport::Concern + prepend ee_ + + class_methods do + def class_name + 'CE' + end + end + + this = self + prepended do + prepended_modules_ << this + end + + def name + 'ce' + end + end + end + + describe 'a class including a concern prepending a concern' do + subject { Class.new.include(ce) } + + it 'returns values from prepended module ee' do + expect(subject.new.name).to eq('ee') + expect(subject.class_name).to eq('EE') + end + + it 'prepends only once' do + ce.prepend(ee) + ce.prepend(ee) + + subject + + expect(prepended_modules).to eq([ee, ce]) + end + end + + describe 'a class prepending a concern prepending a concern' do + subject { Class.new.prepend(ce) } + + it 'returns values from prepended module ce' do + expect(subject.new.name).to eq('ce') + expect(subject.class_name).to eq('CE') + end + + it 'prepends only once' do + subject.prepend(ce) + + expect(prepended_modules).to eq([ee, ce]) + end + end + + describe 'a class prepending a concern' do + subject do + ee_ = ee + + Class.new do + prepend ee_ + + def self.class_name + 'CE' + end + + def name + 'ce' + end + end + end + + it 'returns values from prepended module ee' do + expect(subject.new.name).to eq('ee') + expect(subject.class_name).to eq('EE') + end + + it 'prepends only once' do + subject.prepend(ee) + + expect(prepended_modules).to eq([ee]) + end + end +end diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb index 8475f91799b..776119564ec 100644 --- a/spec/support/helpers/stub_configuration.rb +++ b/spec/support/helpers/stub_configuration.rb @@ -1,5 +1,8 @@ require 'active_support/core_ext/hash/transform_values' require 'active_support/hash_with_indifferent_access' +require 'active_support/dependencies' + +require_dependency 'gitlab' module StubConfiguration def stub_application_setting(messages) |