Skip to content

Commit

Permalink
Use Zeitwerk for loading components
Browse files Browse the repository at this point in the history
- Replace AutoRegistration with a new Loader that uses Zeitwerk
- Remove support for non-standard setups where dir structure doesn't
  match namespaces
- Deprecate `ROM::Setup#auto_registration` and move it to `rom/compat`

Refs #607
  • Loading branch information
solnic committed Jun 12, 2021
1 parent 6f3c47a commit 69046a0
Show file tree
Hide file tree
Showing 57 changed files with 588 additions and 53 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ gem "rom-sql", github: "rom-rb/rom-sql", branch: "master"
gem "dry-monitor", github: "dry-rb/dry-monitor", branch: "master"
gem "dry-events", github: "dry-rb/dry-events", branch: "master"

gem "zeitwerk", github: "fxn/zeitwerk", branch: "main"

group :sql do
gem "jdbc-postgres", platforms: :jruby
gem "jdbc-sqlite3", platforms: :jruby
Expand Down
12 changes: 11 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ require "bundler/gem_tasks"
require "rspec/core"
require "rspec/core/rake_task"

RSpec::Core::RakeTask.new(:spec)
RSpec::Core::RakeTask.new("spec:all") do |t|
t.pattern = ["spec/unit/**/*_spec.rb", "spec/integration/**/*_spec.rb"]
end

RSpec::Core::RakeTask.new("spec:compat") do |t|
t.pattern = ["spec/compat/**/*_spec.rb"]
end

task "spec" => ["spec:all", "spec:compat"]

task default: :spec

Expand All @@ -25,8 +33,10 @@ namespace :benchmark do
end
end

# rubocop:disable Lint/SuppressedException
begin
require "yard-junk/rake"
YardJunk::Rake.define_task(:text)
rescue LoadError
end
# rubocop:enable Lint/SuppressedException
8 changes: 4 additions & 4 deletions docsite/core/source/framework-setup.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ module Persistence
end

configuration = ROM::Configuration.new(:memory)
configuration.auto_registration('root_dir/lib/persistence/')
configuration.auto_register('root_dir/lib/persistence/')
container = ROM.container(configuration)
```

Expand All @@ -92,7 +92,7 @@ Notice that the directory structure is different from our module structure. Sinc

```ruby
configuration = ROM::Configuration.new(:memory)
configuration.auto_registration('/path/to/lib', namespace: 'Persistence')
configuration.auto_register('/path/to/lib', namespace: 'Persistence')
container = ROM.container(configuration)
```

Expand Down Expand Up @@ -146,7 +146,7 @@ end
Then, auto-registration can be achieved with

```ruby
configuration.auto_registration('/path/to/lib', namespace: 'MyApp::Persistence')
configuration.auto_register('/path/to/lib', namespace: 'MyApp::Persistence')
```

#### Turning namespace off
Expand All @@ -162,7 +162,7 @@ end
```ruby
configuration = ROM::Configuration.new(:memory)
configuration.auto_registration('/path/to/lib', namespace: false)
configuration.auto_register('/path/to/lib', namespace: false)
container = ROM.container(configuration)
```
Expand Down
3 changes: 3 additions & 0 deletions lib/rom/compat.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# frozen_string_literal: true

require_relative "compat/setup"
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@

require "rom/types"
require "rom/initializer"
require "rom/setup/auto_registration_strategies/no_namespace"
require "rom/setup/auto_registration_strategies/with_namespace"
require "rom/setup/auto_registration_strategies/custom_namespace"

require_relative "auto_registration_strategies/no_namespace"
require_relative "auto_registration_strategies/with_namespace"
require_relative "auto_registration_strategies/custom_namespace"

module ROM
# AutoRegistration is used to load component files automatically from the provided directory path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

require "rom/support/inflector"
require "rom/types"
require "rom/setup/auto_registration_strategies/base"

require_relative "base"

module ROM
module AutoRegistrationStrategies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

require "rom/support/inflector"
require "rom/types"
require "rom/setup/auto_registration_strategies/base"
require_relative "base"

module ROM
module AutoRegistrationStrategies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require "pathname"

require "rom/support/inflector"
require "rom/setup/auto_registration_strategies/base"
require_relative "base"

module ROM
module AutoRegistrationStrategies
Expand Down
30 changes: 30 additions & 0 deletions lib/rom/compat/setup.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

require_relative "auto_registration"

module ROM
# Setup objects collect component classes during setup/finalization process
#
# @api public
class Setup
# Enable auto-registration for a given setup object
#
# @param [String, Pathname] directory The root path to components
# @param [Hash] options
# @option options [Boolean, String] :namespace Toggle root namespace
# or provide a custom namespace name
#
# @return [Setup]
#
# @deprecated
#
# @api public
def auto_registration(directory, **options)
auto_registration = AutoRegistration.new(directory, inflector: inflector, **options)
auto_registration.relations.map { |r| register_relation(r) }
auto_registration.commands.map { |r| register_command(r) }
auto_registration.mappers.map { |r| register_mapper(r) }
self
end
end
end
2 changes: 1 addition & 1 deletion lib/rom/container.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ module ROM
#
# @example multi-step setup with auto-registration
# config = ROM::Configuration.new(:sql, 'sqlite::memory')
# config.auto_registration('./persistence', namespace: false)
# config.auto_register('./persistence', namespace: false)
#
# config.default.create_table :users do
# primary_key :id
Expand Down
153 changes: 153 additions & 0 deletions lib/rom/loader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# frozen_string_literal: true

require "pathname"
require "zeitwerk"

require "rom/support/inflector"

require "rom/types"
require "rom/initializer"
require "rom/loader"

module ROM
# AutoRegistration is used to load component files automatically from the provided directory path
#
# @api public
class Loader
extend Initializer

NamespaceType = Types::Strict::Bool | Types.Instance(Module)

PathnameType = Types.Constructor(Pathname, &Kernel.method(:Pathname))

InflectorType = Types.Interface(:camelize)

ComponentDirs = Types::Strict::Hash.constructor { |hash|
hash.map { |key, value| [key, value.to_s] }.to_h
}

DEFAULT_MAPPING = {
relations: "relations",
mappers: "mappers",
commands: "commands"
}.freeze

# @!attribute [r] directory
# @return [Pathname] The root path
param :root_directory, type: PathnameType

# @!attribute [r] namespace
# @return [Boolean,String]
# The name of the top level namespace or true/false which
# enables/disables default top level namespace inferred from the dir name
option :namespace, type: NamespaceType, default: -> { true }

# @!attribute [r] component_dirs
# @return [Hash] component => dir-name map
option :component_dirs, type: ComponentDirs, default: -> { DEFAULT_MAPPING }

# @!attribute [r] inflector
# @return [Dry::Inflector] String inflector
# @api private
option :inflector, type: InflectorType, default: -> { Inflector }

# @!attribute [r] loaded_constants
# @return [Dry::Inflector] String inflector
# @api private
option :inflector, type: InflectorType, default: -> { Inflector }

option :loaded_constants, default: -> { {relations: [], commands: [], mappers: []} }

# Load components
#
# @api private
def constants(component_type)
unless @loaded
setup and backend.eager_load
@loaded = true
end

loaded_constants.fetch(component_type)
end

# Load relation files
#
# @api private
def relations
constants(__method__)
end

# Load command files
#
# @api private
def commands
constants(__method__)
end

# Load mapper files
#
# @api private
def mappers
constants(__method__)
end

private

# @api private
def backend
@backend ||= Zeitwerk::Loader.new
end

# @api private
# rubocop:disable Metrics/AbcSize
def setup
backend.inflector = inflector

case namespace
when true
backend.push_dir(root_directory.join("..").realpath)

component_dirs.each_value do |dir|
backend.collapse(root_directory.join(dir).join("**/*"))
end
when false
backend.push_dir(root_directory)
else
backend.push_dir(root_directory, namespace: namespace)
end

excluded_dirs.each do |dir|
backend.ignore(dir)
end

backend.on_load do |_, const, path|
if (type = path_to_component_type(path))
loaded_constants[type] << const
end
end

backend.setup
end
# rubocop:enable Metrics/AbcSize

# @api private
def path_to_component_type(path)
return unless File.file?(path)

component_dirs
.detect { |_, dir|
path.start_with?(root_directory.join(dir).to_s)
}
&.first
end

# @api private
def excluded_dirs
root_directory
.children
.select(&:directory?)
.reject { |dir| component_dirs.values.include?(dir.basename.to_s) }
.map { |name| root_directory.join(name) }
end
end
end
Loading

0 comments on commit 69046a0

Please sign in to comment.