hub_kernel 0.8.0 → 0.10.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 +48 -0
- data/lib/hub_kernel/conformance/crossings.rb +28 -0
- data/lib/hub_kernel/conformance/exposed.rb +22 -0
- data/lib/hub_kernel/crossings.rb +40 -0
- data/lib/hub_kernel/exposes.rb +21 -0
- data/lib/hub_kernel/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 269ad669e7c014af9175190d416dbf7be82bfb8d797eb3a25107bf87b99b1344
|
|
4
|
+
data.tar.gz: 808639ddaca46f8d8cbe9f187a7d59ae9d50b704f773bf4f255bae731d4339a7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3c94024970cd0ae83bf695824439a959b6b03061eb39f92268f483ca769fbf323d4210710f805f052028590e65b87a84b43b8801cac1a9b927a9402205f1fd55
|
|
7
|
+
data.tar.gz: 307661997442f598ee6d2e931540336986a567794113298d496864f86d3ac19e2788ee091e75b8bc9434bd1c61bd3aa1df095b71af29d2479b7898869dfd416b
|
data/README.md
CHANGED
|
@@ -116,6 +116,54 @@ Supplies.exposed("record_purchase").takes # => [:supplier_id, :bought_on, :lines
|
|
|
116
116
|
Supplies.exposed("delete_everything") # => nil
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
+
### The exposed-list check
|
|
120
|
+
A hub's own test can check its exposed list, so a listed method the hub lacks, or one
|
|
121
|
+
listed with values it does not take, shows in the suite rather than on a caller's
|
|
122
|
+
request:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
require "hub_kernel/conformance/exposed"
|
|
126
|
+
|
|
127
|
+
class SuppliesHubTest < ActiveSupport::TestCase
|
|
128
|
+
include HubKernel::Conformance::Exposed
|
|
129
|
+
|
|
130
|
+
hub { Supplies }
|
|
131
|
+
end
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The test fails and names each problem. A method that takes `**` values accepts any
|
|
135
|
+
values it is listed with, as long as it is listed with each value it requires.
|
|
136
|
+
|
|
137
|
+
### The crossing check
|
|
138
|
+
A host's test can check that no hub's code names a class another hub owns. The host
|
|
139
|
+
passes a map of which classes each hub owns, and the test fails listing each file and
|
|
140
|
+
the class it names:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
require "hub_kernel/conformance/crossings"
|
|
144
|
+
|
|
145
|
+
class HubCrossingsTest < ActiveSupport::TestCase
|
|
146
|
+
include HubKernel::Conformance::Crossings
|
|
147
|
+
|
|
148
|
+
crossings files: "app/{models,controllers,jobs,views}/shop/**/*.{rb,erb}", except: [ "app/models/shop/hubs.rb" ] do
|
|
149
|
+
HubKernel::Crossings.new(
|
|
150
|
+
owners: { supplies: %w[Shop::Purchase Shop::PurchasesController], finance: %w[Shop::Expense], shop: %w[Shop::Home] },
|
|
151
|
+
shared: %w[Shop::BaseController],
|
|
152
|
+
host_layer: :shop,
|
|
153
|
+
interfaces: { supplies: "Shop::Hubs::Supplies", finance: "Shop::Hubs::Finance" }
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
- A constant inside another hub's class counts as naming that class.
|
|
160
|
+
- A name no hub owns, such as a namespace, is not a crossing, and neither is a class on
|
|
161
|
+
the `shared` list.
|
|
162
|
+
- A view belongs to the hub that owns its controller, then to the hub that owns the
|
|
163
|
+
record its folder is named after, and otherwise to the `host_layer`.
|
|
164
|
+
- The host's layer may name any hub's interface module, and a hub naming another hub's
|
|
165
|
+
interface module is a crossing.
|
|
166
|
+
|
|
119
167
|
## Installation
|
|
120
168
|
Add this line to your application's Gemfile:
|
|
121
169
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "active_support/concern"
|
|
2
|
+
require "hub_kernel/crossings"
|
|
3
|
+
|
|
4
|
+
module HubKernel
|
|
5
|
+
module Conformance
|
|
6
|
+
module Crossings
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
class_methods do
|
|
10
|
+
def crossings(root: nil, files: "app/**/*.{rb,erb}", except: [], &block)
|
|
11
|
+
define_method(:the_crossings, &block)
|
|
12
|
+
define_method(:crossing_root) { root || Rails.root }
|
|
13
|
+
define_method(:crossing_files) { files }
|
|
14
|
+
define_method(:crossing_exceptions) { except }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
included do
|
|
19
|
+
test "no file names a class another hub owns" do
|
|
20
|
+
found = (Dir.glob(crossing_files, base: crossing_root) - crossing_exceptions).sort.flat_map do |path|
|
|
21
|
+
the_crossings.in(path, File.read(File.join(crossing_root, path))).map { |name| "#{path} names #{name}" }
|
|
22
|
+
end
|
|
23
|
+
flunk found.join("\n") if found.any?
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require "active_support/concern"
|
|
2
|
+
|
|
3
|
+
module HubKernel
|
|
4
|
+
module Conformance
|
|
5
|
+
module Exposed
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
def hub(&block)
|
|
10
|
+
define_method(:the_hub, &block)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
included do
|
|
15
|
+
test "every method the hub exposes exists and takes the values it is listed with" do
|
|
16
|
+
problems = the_hub.exposure_problems
|
|
17
|
+
flunk problems.join("\n") if problems.any?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require "active_support/core_ext/string/inflections"
|
|
2
|
+
|
|
3
|
+
module HubKernel
|
|
4
|
+
class Crossings
|
|
5
|
+
def initialize(owners:, shared: [], host_layer: nil, interfaces: {})
|
|
6
|
+
@owners = owners
|
|
7
|
+
@shared = shared
|
|
8
|
+
@host_layer = host_layer
|
|
9
|
+
@interfaces = interfaces
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def in(path, source)
|
|
13
|
+
hub = owner_of_file(path)
|
|
14
|
+
source.scan(/[A-Z]\w*(?:::[A-Z]\w*)*/).filter_map { |name| owned_class_in(name) }.uniq.select { |name| crossing?(name, hub) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def crossing?(name, hub)
|
|
20
|
+
return false if @shared.include?(name) || owner_of(name) == hub
|
|
21
|
+
|
|
22
|
+
!(hub == @host_layer && @interfaces.value?(name))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def owned_class_in(name)
|
|
26
|
+
parts = name.split("::")
|
|
27
|
+
parts.size.downto(1).map { |size| parts.first(size).join("::") }.find { |candidate| owner_of(candidate) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def owner_of(class_name) = @interfaces.key(class_name) || @owners.find { |_hub, classes| classes.include?(class_name) }&.first
|
|
31
|
+
|
|
32
|
+
def owner_of_file(path)
|
|
33
|
+
return owner_of_view_folder(File.dirname(path.delete_prefix("app/views/")).camelize) if path.start_with?("app/views/")
|
|
34
|
+
|
|
35
|
+
owner_of(path.sub(%r{\Aapp/[^/]+/}, "").delete_suffix(".rb").camelize)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def owner_of_view_folder(folder) = owner_of("#{folder}Controller") || owner_of(folder) || @host_layer
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/hub_kernel/exposes.rb
CHANGED
|
@@ -8,8 +8,29 @@ module HubKernel
|
|
|
8
8
|
|
|
9
9
|
def exposed(name) = exposed_methods[name.to_s]
|
|
10
10
|
|
|
11
|
+
def exposure_problems
|
|
12
|
+
exposed_methods.values.filter_map { |exposure| exposure_problem(exposure) }
|
|
13
|
+
end
|
|
14
|
+
|
|
11
15
|
private
|
|
12
16
|
|
|
13
17
|
def exposed_methods = @exposed_methods ||= {}
|
|
18
|
+
|
|
19
|
+
def exposing_hub = name.demodulize
|
|
20
|
+
|
|
21
|
+
def exposure_problem(exposure)
|
|
22
|
+
return "#{exposing_hub} exposes #{exposure.name}, which it has no method for" unless respond_to?(exposure.name)
|
|
23
|
+
return if takes_listed_values?(exposure)
|
|
24
|
+
|
|
25
|
+
"#{exposing_hub} exposes #{exposure.name} with #{exposure.takes.join(", ")}, but it takes #{keywords(exposure, :keyreq, :key).join(", ")}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def takes_listed_values?(exposure)
|
|
29
|
+
(keywords(exposure, :keyreq) - exposure.takes).empty? && (takes_any_values?(exposure) || (exposure.takes - keywords(exposure, :keyreq, :key)).empty?)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def takes_any_values?(exposure) = method(exposure.name).parameters.any? { |kind, _| kind == :keyrest }
|
|
33
|
+
|
|
34
|
+
def keywords(exposure, *kinds) = method(exposure.name).parameters.filter_map { |kind, value| value if kinds.include?(kind) }
|
|
14
35
|
end
|
|
15
36
|
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.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- tylercschneider
|
|
@@ -42,9 +42,12 @@ files:
|
|
|
42
42
|
- lib/hub_kernel/answer.rb
|
|
43
43
|
- lib/hub_kernel/authz.rb
|
|
44
44
|
- lib/hub_kernel/conformance/action.rb
|
|
45
|
+
- lib/hub_kernel/conformance/crossings.rb
|
|
46
|
+
- lib/hub_kernel/conformance/exposed.rb
|
|
45
47
|
- lib/hub_kernel/conformance/hub.rb
|
|
46
48
|
- lib/hub_kernel/conformance/partial.rb
|
|
47
49
|
- lib/hub_kernel/context.rb
|
|
50
|
+
- lib/hub_kernel/crossings.rb
|
|
48
51
|
- lib/hub_kernel/engine.rb
|
|
49
52
|
- lib/hub_kernel/exposes.rb
|
|
50
53
|
- lib/hub_kernel/follow_up.rb
|