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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3efd66220ac58f67d7c6018b2e552f70641539e11da6ef13810d274cb1daea52
4
- data.tar.gz: 803ca1aa3d1258ae639ae2a6beb842b8e969f213b3d604e07d7acff7b9dc6523
3
+ metadata.gz: 1bb347a71c0f40d275dc0c2d504fb186b474538a1b4ee80c7e4fd1270f064926
4
+ data.tar.gz: be51e4ff8a3b09e83cd2904745c39f30088c7471c0127f66fcd8107bc9f6272b
5
5
  SHA512:
6
- metadata.gz: 111dfbefdd5d3fbb62241dc8142a5b16b8626f9af8d227df56033c60205a2d2bbd9db4a27fa39e487f31ea891dd1041a18c76519e5ba88e2a7a7279659e667cc
7
- data.tar.gz: bac66dfb083524ae2f30f2db97c637df7d88a70f19ce59f264acdbc40baa24e39fa3218ef19d472713e3c3c6ba7df8cf959078f2993ceddbc0b9790b44badff1
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 "app/models/#{file_path}.rb", <<~RUBY
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 "test/models/#{file_path}_test.rb", <<~RUBY
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(:the_crossings, &block)
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
@@ -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
- def initialize(owners:, shared: [], host_layer: nil, interfaces: {}, namespaces: {})
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 = @owners.values.flatten.tally.select { |_name, hubs| hubs > 1 }.keys
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 missing_classes = @owners.values.flatten.uniq.reject(&:safe_constantize)
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))
@@ -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
@@ -1,3 +1,3 @@
1
1
  module HubKernel
2
- VERSION = "0.19.0"
2
+ VERSION = "0.20.0"
3
3
  end
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.19.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