rampart-core 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 498ffbe558ca941b070948c4f39530085f8c2d1a73917186bd7b6ecab59192ab
4
- data.tar.gz: 1181ec35e2cd89d2afa0991fa1d6e421a3c43d82099b7eafd97ae88ce8ca6600
3
+ metadata.gz: 153fe0a9dfec68247636c637d29acc8405bc6d123fd9d43e8beea3e4e4cf6d9f
4
+ data.tar.gz: c93f4f3ac48733d1e6017560c4466c73da6da23ba1d1a658b55e8d6170c45c60
5
5
  SHA512:
6
- metadata.gz: fe575a9f4e72c5c2759bf00f5358fd563464e0074590fbf1e8b5c1c3c44c710ac0e381230646e2713c8e64aed4befaf0e668c55c1d4f1df59d3e75d21cc0a8aa
7
- data.tar.gz: 0d9cdd05bf982ba344967e37ebd880f7d0139dc32bfa8f7d0276879a99f1344bb0428ace2595d8cc08905292162a1ebeb7b1a14a65cff60614aed0a647cd2d8b
6
+ metadata.gz: f44a2e70c2668cd1275976b3675e6c2a54914b83c3b6828dd0ed63ab4115398d506ee1eccd28d53ed17c34b6dba0be85aa38cc7e7976f8d39eaf32da0241316d
7
+ data.tar.gz: d4afcfe18c00e66d495415ab7468d4b371cd52911c1c074021153a26fa5cce5500fbaedb5fca0baf2f1d4f6d36090c1e3e1b33c45a2975378f2db47f21f84d20
@@ -3,50 +3,115 @@
3
3
  module Rampart
4
4
  # Generic loader for Rails engines using hexagonal architecture
5
5
  #
6
- # This loader handles auto-discovery and loading of domain, application, and
7
- # infrastructure components in the correct order. It works around the mismatch
8
- # between directory structure (app/{layer}/{context}/) and Ruby namespace
9
- # conventions by using explicit eager loading.
6
+ # This loader configures Zeitwerk to properly handle the mismatch between
7
+ # hexagonal architecture directory structure (app/{layer}/{context}/{subdirs}/)
8
+ # and flat Ruby namespace conventions (Context::ClassName instead of
9
+ # Context::Subdirs::ClassName).
10
+ #
11
+ # It collapses ALL subdirectories within the context namespace so that
12
+ # Zeitwerk expects files to define constants directly under the context
13
+ # namespace (e.g., CatContent::CatListing, not CatContent::Aggregates::CatListing).
10
14
  #
11
15
  # Usage in your engine:
12
16
  # module YourContext
13
17
  # class Engine < ::Rails::Engine
14
- # config.to_prepare do
15
- # Rampart::EngineLoader.load_all(
16
- # engine_root: YourContext::Engine.root,
17
- # context_name: "your_context"
18
- # )
19
- # end
18
+ # Rampart::EngineLoader.setup(self)
20
19
  # end
21
20
  # end
22
21
  class EngineLoader
22
+ # Hexagonal architecture layers in load order
23
+ LAYERS = %w[domain application infrastructure].freeze
23
24
  class << self
25
+ # Set up hexagonal architecture loading for an engine
26
+ #
27
+ # This configures:
28
+ # 1. Autoload paths for domain, application, and infrastructure layers
29
+ # 2. Eager load paths for CI/production
30
+ # 3. Zeitwerk collapse for flat namespaces (before eager loading)
31
+ # 4. File loading in correct dependency order (via config.to_prepare)
32
+ #
33
+ # @param engine_class [Class] The Rails::Engine class (e.g., YourContext::Engine)
34
+ def setup(engine_class)
35
+ # Infer context_name from engine module (e.g., CatContent::Engine -> "cat_content")
36
+ context_name = engine_class.module_parent.name.underscore
37
+ root = engine_class.root
38
+
39
+ # Add hexagonal layer directories to autoload and eager_load paths
40
+ layer_paths = LAYERS.map { |layer| root.join("app/#{layer}") }
41
+ engine_class.config.autoload_paths += layer_paths
42
+ engine_class.config.eager_load_paths += layer_paths
43
+
44
+ # Register initializer to configure Zeitwerk collapse before autoload paths are set
45
+ engine_class.initializer "#{context_name}.zeitwerk", before: :set_autoload_paths do
46
+ Rampart::EngineLoader.configure_autoloading(
47
+ engine_root: root,
48
+ context_name: context_name
49
+ )
50
+ end
51
+
52
+ # Register config.to_prepare to load files in correct order
53
+ engine_class.config.to_prepare do
54
+ Rampart::EngineLoader.load_all(
55
+ engine_root: root,
56
+ context_name: context_name
57
+ )
58
+ end
59
+ end
60
+
61
+ # Configure Zeitwerk to collapse all subdirectories within hexagonal layers
62
+ #
63
+ # @param engine_root [Pathname] Root path of the Rails engine
64
+ # @param context_name [String] Snake-case name of the bounded context
65
+ def configure_autoloading(engine_root:, context_name:)
66
+ return unless defined?(Rails) && Rails.respond_to?(:autoloaders)
67
+
68
+ loader = Rails.autoloaders.main
69
+
70
+ # Configure collapse for each hexagonal layer
71
+ LAYERS.each do |layer|
72
+ layer_path = engine_root.join("app/#{layer}/#{context_name}")
73
+ next unless layer_path.exist?
74
+
75
+ # Recursively collapse ALL subdirectories within the context path
76
+ collapse_all_subdirectories(loader, layer_path)
77
+ end
78
+ end
79
+
24
80
  # Load all hexagonal architecture components for an engine
25
81
  #
26
82
  # @param engine_root [Pathname] Root path of the Rails engine
27
- # @param context_name [String] Snake-case name of the bounded context (e.g., "cat_content")
83
+ # @param context_name [String] Snake-case name of the bounded context
28
84
  def load_all(engine_root:, context_name:)
29
- load_domain_layer(engine_root, context_name)
30
- load_application_layer(engine_root, context_name)
31
- load_infrastructure_layer(engine_root, context_name)
85
+ LAYERS.each do |layer|
86
+ send("load_#{layer}_layer", engine_root, context_name)
87
+ end
32
88
  end
33
89
 
34
90
  private
35
91
 
92
+ # Recursively collapse all subdirectories
93
+ def collapse_all_subdirectories(loader, path)
94
+ return unless path.exist?
95
+
96
+ Dir.glob(path.join("*/")).each do |subdir|
97
+ subdir_path = Pathname.new(subdir)
98
+ loader.collapse(subdir_path.to_s)
99
+ # Recursively collapse nested subdirectories
100
+ collapse_all_subdirectories(loader, subdir_path)
101
+ end
102
+ end
103
+
36
104
  def load_domain_layer(root, context_name)
37
105
  domain = root.join("app/domain/#{context_name}")
38
106
  return unless domain.exist?
39
107
 
40
- # Load files in specific order: errors, value objects, entities, events, aggregates, services, ports
108
+ # Load errors/exceptions first (they have no dependencies)
41
109
  load_directory(domain, pattern: "*_error.rb")
42
110
  load_directory(domain, pattern: "*_exception.rb")
43
- load_directory(domain.join("value_objects"))
44
- load_directory(domain.join("entities"))
45
- load_directory(domain.join("events"))
46
- load_directory(domain.join("aggregates"))
47
- load_directory(domain.join("services"))
48
- load_directory(domain.join("ports"))
49
-
111
+
112
+ # Load all subdirectories
113
+ load_all_subdirectories(domain)
114
+
50
115
  # Load any remaining files at domain root
51
116
  load_directory(domain, pattern: "*.rb")
52
117
  end
@@ -55,10 +120,8 @@ module Rampart
55
120
  app = root.join("app/application/#{context_name}")
56
121
  return unless app.exist?
57
122
 
58
- # Load all application layer files
59
- load_directory(app.join("commands"))
60
- load_directory(app.join("queries"))
61
- load_directory(app.join("services"))
123
+ # Load all subdirectories and root files
124
+ load_all_subdirectories(app)
62
125
  load_directory(app, pattern: "*.rb")
63
126
  end
64
127
 
@@ -66,35 +129,34 @@ module Rampart
66
129
  infra = root.join("app/infrastructure/#{context_name}")
67
130
  return unless infra.exist?
68
131
 
69
- # Load persistence layer first (base_record before models)
132
+ # Load base_record.rb first (models depend on it)
70
133
  persistence = infra.join("persistence")
71
- if persistence.exist?
72
- load_directory(persistence, pattern: "base_record.rb")
73
- load_directory(persistence.join("models"))
74
- load_directory(persistence.join("mappers"))
75
- load_directory(persistence.join("repositories"))
76
- end
134
+ load_directory(persistence, pattern: "base_record.rb") if persistence.exist?
77
135
 
78
- # Load other infrastructure components
79
- load_directory(infra.join("adapters"))
80
- load_directory(infra.join("http"))
81
- load_directory(infra.join("wiring"))
136
+ # Load all subdirectories
137
+ load_all_subdirectories(infra)
138
+
139
+ # Load any remaining files at infrastructure root
82
140
  load_directory(infra, pattern: "*.rb")
83
141
  end
84
142
 
143
+ def load_all_subdirectories(dir)
144
+ return unless dir.exist?
145
+
146
+ Dir.glob(dir.join("*/")).sort.each do |subdir|
147
+ load_directory(Pathname.new(subdir))
148
+ end
149
+ end
150
+
85
151
  def load_directory(dir, pattern: "**/*.rb")
86
152
  return unless dir.exist?
87
153
 
88
154
  Dir.glob(dir.join(pattern)).sort.each do |file|
89
155
  next if File.directory?(file)
90
- if Rails.env.development? || Rails.env.test?
91
- # support auto-reloading in development and test environments
92
- load file.to_s
93
- else
94
- # In eager load environments (CI/production), Zeitwerk has already loaded these files.
95
- # Using require instead of load prevents re-execution and duplicate definitions.
96
- require file.to_s
97
- end
156
+ # With Zeitwerk collapse configured, Zeitwerk manages autoloading and hot-reloading.
157
+ # Use require to ensure files are loaded in correct dependency order without
158
+ # re-executing already-loaded files. Zeitwerk handles reloading on code changes.
159
+ require file.to_s
98
160
  end
99
161
  end
100
162
  end
@@ -1,3 +1,3 @@
1
1
  module Rampart
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rampart-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rampart Team