From 0073fc6b019f1ca5197cf9055f3c62cabc50af4a Mon Sep 17 00:00:00 2001 From: Alessandro Desantis Date: Fri, 31 Jan 2020 15:33:15 +0100 Subject: [PATCH 1/2] Load Solidus engine extension files automatically By cleanly separating extension files for the different Solidus engines, we can only load solidus_backend files when the engine itself is available rather than doing it all the time. In turn, this allows extensions to only actually require the parts of Solidus that they really need. --- README.md | 20 ++++++ .../engine_extensions/decorators.rb | 64 +++++++++++++++---- 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 632006d..24848da 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,26 @@ end config.to_prepare(&method(:activate).to_proc) ``` +#### Loading files conditionally + +If you include `EngineExtensions::Decorators` in your extension and structure your files according +to the expected paths, they will be loaded automagically only when the relevant Solidus engines are +available. + +Here's what an example structure may look like: + +- `lib/views/backend`: will only be added to the view paths when `solidus_backend` is available. +- `lib/controllers/backend`: will only be added to the controller paths when `solidus_backend` is + available. +- `lib/decorators/backend`: will only be added to the decorator paths when `solidus_backend` is + available. + +The same goes for `frontend` and `api`. + +We strongly recommend following this structure and making your extensions so that they're not +dependent on anything other than `solidus_core`, only augmenting the functionality of the other +engines when they are available. + ## Development To install this gem onto your local machine, run `bundle exec rake install`. To release a new diff --git a/lib/solidus_support/engine_extensions/decorators.rb b/lib/solidus_support/engine_extensions/decorators.rb index 7cdfc54..af1a101 100644 --- a/lib/solidus_support/engine_extensions/decorators.rb +++ b/lib/solidus_support/engine_extensions/decorators.rb @@ -4,33 +4,71 @@ module SolidusSupport module EngineExtensions module Decorators def self.included(engine) + engine.extend ClassMethods + engine.class_eval do - extend ClassMethods - config.to_prepare(&method(:activate).to_proc) + config.to_prepare(&method(:activate)) + + enable_solidus_engine_support('backend') if SolidusSupport.backend_available? + enable_solidus_engine_support('frontend') if SolidusSupport.frontend_available? + enable_solidus_engine_support('api') if SolidusSupport.api_available? end end module ClassMethods def activate - base_path = root.join('app/decorators') - if Rails.respond_to?(:autoloaders) && Rails.autoloaders.main - # Add decorators folder to the Rails autoloader. This - # allows Zeitwerk to resolve decorators paths correctly, - # when used. - Dir.glob(base_path.join('*')) do |decorators_folder| + # Add decorators folder to the Rails autoloader. This tells Zeitwerk to treat paths + # such as app/decorators/controllers as roots. + solidus_decorators_root.glob('*') do |decorators_folder| Rails.autoloaders.main.push_dir(decorators_folder) end end - # Load decorator files. This is needed since they are - # never explicitely referenced in the application code - # and won't be loaded by default. We need them to be - # executed anyway to extend exisiting classes. - Dir.glob(base_path.join('**/*.rb')) do |decorator_path| + load_solidus_decorators_from(solidus_decorators_root) + end + + # Loads decorator files. + # + # This is needed since they are never explicitly referenced in the application code and + # won't be loaded by default. We need them to be executed regardless in order to decorate + # existing classes. + def load_solidus_decorators_from(path) + path.glob('**/*.rb') do |decorator_path| require_dependency(decorator_path) end end + + private + + # Returns the root for this engine's decorators. + # + # @return [Path] + def solidus_decorators_root + root.join('app/decorators') + end + + # Enables support for a Solidus engine. + # + # This will tell Rails to: + # + # * add +lib/controllers/[engine]+ to the controller paths; + # * add +lib/views/[engine]+ to the view paths; + # * load the decorators in +lib/decorators/[engine]+. + # + # @see #load_solidus_decorators_from + def enable_solidus_engine_support(engine) + paths['app/controllers'] << "lib/controllers/#{engine}" + paths['app/views'] << "lib/views/#{engine}" + + engine_context = self + config.to_prepare do + engine_context.instance_eval do + path = root.join("lib/decorators/#{engine}") + load_solidus_decorators_from(path) + end + end + end end end end From 947778ad3b45561a5248c59a01ffb91c858246b1 Mon Sep 17 00:00:00 2001 From: Alessandro Desantis Date: Fri, 31 Jan 2020 17:56:44 +0100 Subject: [PATCH 2/2] Move EngineExtensions::Decorators to EngineExtensions This module doesn't just deal with decorators anymore, but also loads views and controllers, so this seems like a better name. Separating it into two modules wouldn't be simple because they are interlinked. --- README.md | 6 +- lib/solidus_support/engine_extensions.rb | 75 ++++++++++++++++++- .../engine_extensions/decorators.rb | 75 ------------------- solidus_support.gemspec | 2 + 4 files changed, 79 insertions(+), 79 deletions(-) delete mode 100644 lib/solidus_support/engine_extensions/decorators.rb diff --git a/README.md b/README.md index 24848da..51afc7f 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ module SolidusExtensionName class Engine < Rails::Engine engine_name 'solidus_extension_name' - include SolidusSupport::EngineExtensions::Decorators + include SolidusSupport::EngineExtensions # ... end @@ -59,8 +59,8 @@ config.to_prepare(&method(:activate).to_proc) #### Loading files conditionally -If you include `EngineExtensions::Decorators` in your extension and structure your files according -to the expected paths, they will be loaded automagically only when the relevant Solidus engines are +If you include `EngineExtensions` in your extension and structure your files according to the +expected paths, they will be loaded automagically only when the relevant Solidus engines are available. Here's what an example structure may look like: diff --git a/lib/solidus_support/engine_extensions.rb b/lib/solidus_support/engine_extensions.rb index 86ad1cc..aa4baa6 100644 --- a/lib/solidus_support/engine_extensions.rb +++ b/lib/solidus_support/engine_extensions.rb @@ -1,3 +1,76 @@ # frozen_string_literal: true -require_relative 'engine_extensions/decorators' +module SolidusSupport + module EngineExtensions + include ActiveSupport::Deprecation::DeprecatedConstantAccessor + deprecate_constant 'Decorators', 'SolidusSupport::EngineExtensions' + + def self.included(engine) + engine.extend ClassMethods + + engine.class_eval do + config.to_prepare(&method(:activate)) + + enable_solidus_engine_support('backend') if SolidusSupport.backend_available? + enable_solidus_engine_support('frontend') if SolidusSupport.frontend_available? + enable_solidus_engine_support('api') if SolidusSupport.api_available? + end + end + + module ClassMethods + def activate + if Rails.respond_to?(:autoloaders) && Rails.autoloaders.main + # Add decorators folder to the Rails autoloader. This tells Zeitwerk to treat paths + # such as app/decorators/controllers as roots. + solidus_decorators_root.glob('*') do |decorators_folder| + Rails.autoloaders.main.push_dir(decorators_folder) + end + end + + load_solidus_decorators_from(solidus_decorators_root) + end + + # Loads decorator files. + # + # This is needed since they are never explicitly referenced in the application code and + # won't be loaded by default. We need them to be executed regardless in order to decorate + # existing classes. + def load_solidus_decorators_from(path) + path.glob('**/*.rb') do |decorator_path| + require_dependency(decorator_path) + end + end + + private + + # Returns the root for this engine's decorators. + # + # @return [Path] + def solidus_decorators_root + root.join('app/decorators') + end + + # Enables support for a Solidus engine. + # + # This will tell Rails to: + # + # * add +lib/controllers/[engine]+ to the controller paths; + # * add +lib/views/[engine]+ to the view paths; + # * load the decorators in +lib/decorators/[engine]+. + # + # @see #load_solidus_decorators_from + def enable_solidus_engine_support(engine) + paths['app/controllers'] << "lib/controllers/#{engine}" + paths['app/views'] << "lib/views/#{engine}" + + engine_context = self + config.to_prepare do + engine_context.instance_eval do + path = root.join("lib/decorators/#{engine}") + load_solidus_decorators_from(path) + end + end + end + end + end +end diff --git a/lib/solidus_support/engine_extensions/decorators.rb b/lib/solidus_support/engine_extensions/decorators.rb deleted file mode 100644 index af1a101..0000000 --- a/lib/solidus_support/engine_extensions/decorators.rb +++ /dev/null @@ -1,75 +0,0 @@ -# frozen_string_literal: true - -module SolidusSupport - module EngineExtensions - module Decorators - def self.included(engine) - engine.extend ClassMethods - - engine.class_eval do - config.to_prepare(&method(:activate)) - - enable_solidus_engine_support('backend') if SolidusSupport.backend_available? - enable_solidus_engine_support('frontend') if SolidusSupport.frontend_available? - enable_solidus_engine_support('api') if SolidusSupport.api_available? - end - end - - module ClassMethods - def activate - if Rails.respond_to?(:autoloaders) && Rails.autoloaders.main - # Add decorators folder to the Rails autoloader. This tells Zeitwerk to treat paths - # such as app/decorators/controllers as roots. - solidus_decorators_root.glob('*') do |decorators_folder| - Rails.autoloaders.main.push_dir(decorators_folder) - end - end - - load_solidus_decorators_from(solidus_decorators_root) - end - - # Loads decorator files. - # - # This is needed since they are never explicitly referenced in the application code and - # won't be loaded by default. We need them to be executed regardless in order to decorate - # existing classes. - def load_solidus_decorators_from(path) - path.glob('**/*.rb') do |decorator_path| - require_dependency(decorator_path) - end - end - - private - - # Returns the root for this engine's decorators. - # - # @return [Path] - def solidus_decorators_root - root.join('app/decorators') - end - - # Enables support for a Solidus engine. - # - # This will tell Rails to: - # - # * add +lib/controllers/[engine]+ to the controller paths; - # * add +lib/views/[engine]+ to the view paths; - # * load the decorators in +lib/decorators/[engine]+. - # - # @see #load_solidus_decorators_from - def enable_solidus_engine_support(engine) - paths['app/controllers'] << "lib/controllers/#{engine}" - paths['app/views'] << "lib/views/#{engine}" - - engine_context = self - config.to_prepare do - engine_context.instance_eval do - path = root.join("lib/decorators/#{engine}") - load_solidus_decorators_from(path) - end - end - end - end - end - end -end diff --git a/solidus_support.gemspec b/solidus_support.gemspec index 174141a..ddc1c61 100644 --- a/solidus_support.gemspec +++ b/solidus_support.gemspec @@ -21,6 +21,8 @@ Gem::Specification.new do |s| s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) } s.require_paths = ["lib"] + s.add_dependency 'activesupport', ['>= 5.2', '< 7.0.x'] + s.add_development_dependency 'bundler' s.add_development_dependency 'rake' s.add_development_dependency 'rspec-rails'