hub_kernel 0.19.0 → 0.20.0
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 +4 -4
- data/README.md +46 -2
- data/lib/generators/hub_kernel/hub/hub_generator.rb +7 -2
- data/lib/hub_kernel/conformance/crossings.rb +17 -2
- data/lib/hub_kernel/crossings.rb +32 -5
- data/lib/hub_kernel/engine.rb +14 -0
- data/lib/hub_kernel/hub_folders.rb +25 -0
- data/lib/hub_kernel/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1bb347a71c0f40d275dc0c2d504fb186b474538a1b4ee80c7e4fd1270f064926
|
|
4
|
+
data.tar.gz: be51e4ff8a3b09e83cd2904745c39f30088c7471c0127f66fcd8107bc9f6272b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: db66d8c988b25b8639cde40e2b0212b8a651fb4b831cd675b0b615e2b7ee1a85e9bdbee002be16c03bc91d1cb7ad32a904139b384049281b83804d30636ad37c
|
|
7
|
+
data.tar.gz: 5524ff6a33af31e8e9c039568daa6f0eeb2e8e12202d8f032d78ed890f54f90b662ac771b0e58213e569e8f837f040a8947af61161e95c18a3314188c2438db4
|
data/README.md
CHANGED
|
@@ -18,6 +18,36 @@ Inside a Rails engine gem it writes the hub under the gem's namespace, such as
|
|
|
18
18
|
gemspec. It writes no hub check into the gem's tests, since the gem never fills its own
|
|
19
19
|
ports.
|
|
20
20
|
|
|
21
|
+
Run in an app, it writes the hub into its own folder, named after the last part of the
|
|
22
|
+
hub's name: `Console::Hubs::Supplies` goes to
|
|
23
|
+
`hubs/supplies/app/models/console/hubs/supplies.rb`, and its hub check to
|
|
24
|
+
`test/hubs/supplies/console/hubs/supplies_test.rb`, which the app's normal test run
|
|
25
|
+
reads.
|
|
26
|
+
|
|
27
|
+
### A hub folder in the host app
|
|
28
|
+
A hub that lives in the host app can keep its code in its own folder,
|
|
29
|
+
`hubs/<hub>/app/<kind>/`, laid out like the app's own `app/` folder:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
hubs/billing/app/models/billing/invoice.rb
|
|
33
|
+
hubs/billing/app/jobs/billing/reminder_job.rb
|
|
34
|
+
hubs/billing/app/event_definitions/billing/invoice_sent.rb
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
hub_kernel adds each of those folders to the host's load paths, so a class there loads
|
|
38
|
+
by its full name, `Billing::Invoice`, just as it would from `app/models/`. Moving a
|
|
39
|
+
class into its hub's folder changes nothing for the code that calls it. A namespace
|
|
40
|
+
module may be defined in `app/models/` or in another hub's folder. A host with no
|
|
41
|
+
`hubs/` folder loads exactly what it loads today.
|
|
42
|
+
|
|
43
|
+
Files under a hub's `lib/` folder, such as its generated event pack, are not loaded.
|
|
44
|
+
|
|
45
|
+
A hub folder's `views` are searched after the host's `app/views`, so a controller in the
|
|
46
|
+
hub renders its templates from there, any view can render a hub partial by its usual
|
|
47
|
+
path, and a template in `app/views` wins over one at the same path in a hub folder. A
|
|
48
|
+
hub folder's `helpers` are added to the host's helper paths, so they are available
|
|
49
|
+
wherever every helper is.
|
|
50
|
+
|
|
21
51
|
### Using a hub from a gem
|
|
22
52
|
The app that installs the gem fills each of the hub's ports in its own setup, then
|
|
23
53
|
runs the start check:
|
|
@@ -209,6 +239,11 @@ class HubCrossingsTest < ActiveSupport::TestCase
|
|
|
209
239
|
end
|
|
210
240
|
```
|
|
211
241
|
|
|
242
|
+
- A file under a hub's folder, `hubs/<hub>/`, belongs to that hub, and so does each class
|
|
243
|
+
defined there, without any entry in `owners`. `owners` is only asked about files and
|
|
244
|
+
classes outside every hub folder, and may be left out once every hub has its folder.
|
|
245
|
+
- By default the test reads every `.rb` and `.erb` file under `app/` and under each hub
|
|
246
|
+
folder's `app/`, helpers and services included.
|
|
212
247
|
- A constant inside another hub's class counts as naming that class.
|
|
213
248
|
- A name no hub owns, such as a namespace, is not a crossing, and neither is a class on
|
|
214
249
|
the `shared` list.
|
|
@@ -218,7 +253,9 @@ end
|
|
|
218
253
|
- A view belongs to the hub that owns its controller, then to the hub that owns the
|
|
219
254
|
record its folder is named after, and otherwise to the `host_layer`.
|
|
220
255
|
- The host's layer may name any hub's interface module, and a hub naming another hub's
|
|
221
|
-
interface module is a crossing.
|
|
256
|
+
interface module is a crossing. A hub's interface module is found in its folder, as the
|
|
257
|
+
module there that extends `HubKernel::Ports` or `HubKernel::Exposes`, so a hub in a
|
|
258
|
+
folder needs no entry in `interfaces`.
|
|
222
259
|
- The test fails with "The crossing check found no files to read" when none of the files
|
|
223
260
|
it read belongs to a hub or the host's layer, so a wrong `files:` pattern or an empty
|
|
224
261
|
map does not pass unnoticed.
|
|
@@ -227,7 +264,14 @@ end
|
|
|
227
264
|
own Shop::Receipt".
|
|
228
265
|
- It fails naming each class the map names that no longer exists in the app, such as "The
|
|
229
266
|
map names Shop::Ghost, which does not exist", so a renamed or removed class does not
|
|
230
|
-
stay in the map.
|
|
267
|
+
stay in the map. A class whose file sits in a hub folder is never reported this way.
|
|
268
|
+
- While hubs move into folders, a class the map gives one hub whose file sits in another
|
|
269
|
+
hub's folder is owned by two hubs. A class the map still lists whose file already sits
|
|
270
|
+
in that hub's folder fails with "Shop::Purchase sits in its hub's folder, so take it off
|
|
271
|
+
the list".
|
|
272
|
+
- It fails naming each folder under `hubs/` that holds no module extending
|
|
273
|
+
`HubKernel::Ports` or `HubKernel::Exposes`, such as "hubs/suplies holds no hub module",
|
|
274
|
+
so a misspelled or half-made hub folder does not pass unnoticed.
|
|
231
275
|
|
|
232
276
|
## Installation
|
|
233
277
|
Add this line to your application's Gemfile:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require "rails/generators/named_base"
|
|
2
|
+
require "hub_kernel/hub_folders"
|
|
2
3
|
|
|
3
4
|
module HubKernel
|
|
4
5
|
module Generators
|
|
@@ -6,7 +7,7 @@ module HubKernel
|
|
|
6
7
|
argument :ports, type: :array, default: [], banner: "port:method port:method"
|
|
7
8
|
|
|
8
9
|
def create_hub
|
|
9
|
-
create_file "
|
|
10
|
+
create_file "#{code_root}/models/#{file_path}.rb", <<~RUBY
|
|
10
11
|
module #{class_name}
|
|
11
12
|
extend HubKernel::Ports
|
|
12
13
|
#{port_lines}end
|
|
@@ -16,7 +17,7 @@ module HubKernel
|
|
|
16
17
|
def create_hub_test
|
|
17
18
|
return if gemspec
|
|
18
19
|
|
|
19
|
-
create_file "
|
|
20
|
+
create_file "#{HubKernel::HubFolders.test_folder(hub_folder)}/#{file_path}_test.rb", <<~RUBY
|
|
20
21
|
require "test_helper"
|
|
21
22
|
require "hub_kernel/conformance/hub"
|
|
22
23
|
|
|
@@ -38,6 +39,10 @@ module HubKernel
|
|
|
38
39
|
|
|
39
40
|
def gemspec = Dir.glob("*.gemspec", base: destination_root).first
|
|
40
41
|
|
|
42
|
+
def code_root = gemspec ? "app" : "#{HubKernel::HubFolders.folder(hub_folder)}/#{HubKernel::HubFolders::CODE}"
|
|
43
|
+
|
|
44
|
+
def hub_folder = file_path.split("/").last
|
|
45
|
+
|
|
41
46
|
def port_lines
|
|
42
47
|
return "" if ports.empty?
|
|
43
48
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "active_support/concern"
|
|
2
2
|
require "hub_kernel/crossings"
|
|
3
|
+
require "hub_kernel/hub_folders"
|
|
3
4
|
|
|
4
5
|
module HubKernel
|
|
5
6
|
module Conformance
|
|
@@ -7,8 +8,8 @@ module HubKernel
|
|
|
7
8
|
extend ActiveSupport::Concern
|
|
8
9
|
|
|
9
10
|
class_methods do
|
|
10
|
-
def crossings(root: nil, files: "app/**/*.{rb,erb}", except: [], &block)
|
|
11
|
-
define_method(:
|
|
11
|
+
def crossings(root: nil, files: "{app,#{HubKernel::HubFolders.code_glob}}/**/*.{rb,erb}", except: [], &block)
|
|
12
|
+
define_method(:built_crossings, &block)
|
|
12
13
|
define_method(:crossing_root) { root || Rails.root }
|
|
13
14
|
define_method(:crossing_files) { files }
|
|
14
15
|
define_method(:crossing_exceptions) { except }
|
|
@@ -35,6 +36,16 @@ module HubKernel
|
|
|
35
36
|
assert twice.empty?, twice.map { |name| "Two hubs own #{name}" }.join("\n")
|
|
36
37
|
end
|
|
37
38
|
|
|
39
|
+
test "no class is both listed and in its hub's folder" do
|
|
40
|
+
listed = the_crossings.listed_in_their_folders
|
|
41
|
+
assert listed.empty?, listed.map { |name| "#{name} sits in its hub's folder, so take it off the list" }.join("\n")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
test "every hub folder holds a hub module" do
|
|
45
|
+
bare = the_crossings.folders_without_hub_module
|
|
46
|
+
assert bare.empty?, bare.map { |folder| "#{folder} holds no hub module" }.join("\n")
|
|
47
|
+
end
|
|
48
|
+
|
|
38
49
|
test "every class the map names exists" do
|
|
39
50
|
missing = the_crossings.missing_classes
|
|
40
51
|
assert missing.empty?, missing.map { |name| "The map names #{name}, which does not exist" }.join("\n")
|
|
@@ -42,6 +53,10 @@ module HubKernel
|
|
|
42
53
|
end
|
|
43
54
|
|
|
44
55
|
def crossing_paths = (Dir.glob(crossing_files, base: crossing_root) - crossing_exceptions).sort
|
|
56
|
+
|
|
57
|
+
def the_crossings = @the_crossings ||= built_crossings.read_folders(hub_files).read_hub_modules(hub_files.to_h { |path| [ path, File.read(File.join(crossing_root, path)) ] })
|
|
58
|
+
|
|
59
|
+
def hub_files = @hub_files ||= Dir.glob("#{HubKernel::HubFolders.code_glob}/**/*.rb", base: crossing_root)
|
|
45
60
|
end
|
|
46
61
|
end
|
|
47
62
|
end
|
data/lib/hub_kernel/crossings.rb
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
require "hub_kernel/hub_folders"
|
|
1
2
|
require "active_support/core_ext/string/inflections"
|
|
2
3
|
|
|
3
4
|
module HubKernel
|
|
4
5
|
class Crossings
|
|
5
|
-
|
|
6
|
+
HUB_MODULE = /extend HubKernel::(Ports|Exposes)\b/
|
|
7
|
+
def initialize(owners: {}, shared: [], host_layer: nil, interfaces: {}, namespaces: {}, folder_files: [])
|
|
6
8
|
@owners = owners
|
|
9
|
+
@folder_classes = {}
|
|
10
|
+
read_folders(folder_files)
|
|
7
11
|
@shared = shared
|
|
8
12
|
@host_layer = host_layer
|
|
9
13
|
@interfaces = interfaces
|
|
@@ -15,15 +19,36 @@ module HubKernel
|
|
|
15
19
|
source.scan(/[A-Z]\w*(?:::[A-Z]\w*)*/).filter_map { |name| owned_class_in(name) }.uniq.select { |name| crossing?(name, hub) }
|
|
16
20
|
end
|
|
17
21
|
|
|
22
|
+
def read_folders(paths)
|
|
23
|
+
@folder_classes.merge!(paths.to_h { |path| [ HubFolders.class_of(path), HubFolders.hub_of(path) ] })
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def read_hub_modules(sources)
|
|
28
|
+
sources.each do |path, source|
|
|
29
|
+
next unless source.match?(HUB_MODULE) && path.match?(HubFolders::CODE_PATH)
|
|
30
|
+
|
|
31
|
+
@interfaces = @interfaces.merge(HubFolders.hub_of(path) => HubFolders.class_of(path))
|
|
32
|
+
end
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
|
|
18
36
|
def belongs?(path) = !owner_of_file(path).nil?
|
|
19
37
|
|
|
20
38
|
def unowned_classes(paths)
|
|
21
|
-
paths.select { |path| path.end_with?(".rb") && !path.start_with?("app/views/") }.map { |path| class_of(path) }.reject { |name| owner_of(name) }
|
|
39
|
+
paths.select { |path| path.end_with?(".rb") && !path.start_with?("app/views/") && !HubFolders.hub_of(path) }.map { |path| class_of(path) }.reject { |name| owner_of(name) }
|
|
22
40
|
end
|
|
23
41
|
|
|
24
|
-
def claimed_twice
|
|
42
|
+
def claimed_twice
|
|
43
|
+
listed = @owners.values.flatten
|
|
44
|
+
listed.tally.select { |_name, hubs| hubs > 1 }.keys | @owners.flat_map { |hub, classes| classes.select { |name| @folder_classes.fetch(name, hub) != hub } }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def listed_in_their_folders = @owners.flat_map { |hub, classes| classes.select { |name| @folder_classes[name] == hub } }
|
|
25
48
|
|
|
26
|
-
def
|
|
49
|
+
def folders_without_hub_module = @folder_classes.values.uniq.reject { |hub| @interfaces.key?(hub) }.map { |hub| HubFolders.folder(hub) }
|
|
50
|
+
|
|
51
|
+
def missing_classes = @owners.values.flatten.uniq.reject { |name| @folder_classes.key?(name) || name.safe_constantize }
|
|
27
52
|
|
|
28
53
|
private
|
|
29
54
|
|
|
@@ -46,13 +71,15 @@ module HubKernel
|
|
|
46
71
|
space && "#{space}::#{name.delete_prefix("#{space}::").split("::").first}"
|
|
47
72
|
end
|
|
48
73
|
|
|
49
|
-
def listed_owner_of(class_name) = @interfaces.key(class_name) || @owners.find { |_hub, classes| classes.include?(class_name) }&.first
|
|
74
|
+
def listed_owner_of(class_name) = @interfaces.key(class_name) || @folder_classes[class_name] || @owners.find { |_hub, classes| classes.include?(class_name) }&.first
|
|
50
75
|
|
|
51
76
|
def owner_of(class_name) = listed_owner_of(class_name) || owner_of_namespace(class_name)
|
|
52
77
|
|
|
53
78
|
def owner_of_namespace(class_name) = @namespaces.find { |_hub, spaces| spaces.any? { |space| class_name.start_with?("#{space}::") } }&.first
|
|
54
79
|
|
|
55
80
|
def owner_of_file(path)
|
|
81
|
+
return HubFolders.hub_of(path) if HubFolders.hub_of(path)
|
|
82
|
+
|
|
56
83
|
return owner_of_view_folder(File.dirname(path.delete_prefix("app/views/")).camelize) if path.start_with?("app/views/")
|
|
57
84
|
|
|
58
85
|
owner_of(class_of(path))
|
data/lib/hub_kernel/engine.rb
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
require "hub_kernel/hub_folders"
|
|
2
|
+
|
|
1
3
|
module HubKernel
|
|
2
4
|
class Engine < ::Rails::Engine
|
|
3
5
|
isolate_namespace HubKernel
|
|
6
|
+
|
|
7
|
+
initializer "hub_kernel.hub_folders", before: :set_load_path do |app|
|
|
8
|
+
folders = HubFolders.code_paths(app.root)
|
|
9
|
+
app.config.autoload_paths.concat(folders)
|
|
10
|
+
app.config.eager_load_paths.concat(folders)
|
|
11
|
+
app.config.helpers_paths.concat(HubFolders.helper_paths(app.root))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
initializer "hub_kernel.hub_views" do |app|
|
|
15
|
+
views = HubFolders.view_paths(app.root)
|
|
16
|
+
ActiveSupport.on_load(:action_controller) { append_view_path(views) }
|
|
17
|
+
end
|
|
4
18
|
end
|
|
5
19
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module HubKernel
|
|
2
|
+
module HubFolders
|
|
3
|
+
ROOT = "hubs".freeze
|
|
4
|
+
CODE = "app".freeze
|
|
5
|
+
|
|
6
|
+
PATH = %r{\A#{ROOT}/([^/]+)/}
|
|
7
|
+
CODE_PATH = %r{\A#{ROOT}/[^/]+/#{CODE}/[^/]+/}
|
|
8
|
+
|
|
9
|
+
def self.folder(hub) = "#{ROOT}/#{hub}"
|
|
10
|
+
|
|
11
|
+
def self.test_folder(hub) = "test/#{folder(hub)}"
|
|
12
|
+
|
|
13
|
+
def self.code_glob = "#{ROOT}/*/#{CODE}"
|
|
14
|
+
|
|
15
|
+
def self.hub_of(path) = path[PATH, 1]&.to_sym
|
|
16
|
+
|
|
17
|
+
def self.class_of(path) = path.sub(CODE_PATH, "").delete_suffix(".rb").camelize
|
|
18
|
+
|
|
19
|
+
def self.code_paths(root) = Dir[root.join("#{code_glob}/*")]
|
|
20
|
+
|
|
21
|
+
def self.view_paths(root) = Dir[root.join("#{code_glob}/views")]
|
|
22
|
+
|
|
23
|
+
def self.helper_paths(root) = Dir[root.join("#{code_glob}/helpers")]
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/hub_kernel/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hub_kernel
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.20.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- tylercschneider
|
|
@@ -62,6 +62,7 @@ files:
|
|
|
62
62
|
- lib/hub_kernel/crossings.rb
|
|
63
63
|
- lib/hub_kernel/engine.rb
|
|
64
64
|
- lib/hub_kernel/follow_up.rb
|
|
65
|
+
- lib/hub_kernel/hub_folders.rb
|
|
65
66
|
- lib/hub_kernel/hubs.rb
|
|
66
67
|
- lib/hub_kernel/markup.rb
|
|
67
68
|
- lib/hub_kernel/ports.rb
|