hub_kernel 0.26.0 → 0.27.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 +31 -0
- data/lib/hub_kernel/conformance/unlinked_ids.rb +41 -0
- data/lib/hub_kernel/port_associations.rb +6 -0
- data/lib/hub_kernel/ports.rb +7 -1
- 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: d5519634e5f01317012e931057dc2caa508996a18ee0f952ee6e4de867f285ff
|
|
4
|
+
data.tar.gz: 5477988282902fb115811f3f08f6a7eebe8547eee1f7a5ec9c56a3b8f758272c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bf9219c9a2027f881df22d5aca5149f34408fb6c91156f550ab9fbd992c572d4bc3bfb31f944722fbb73154c37646ef3a3386e9049f3d5aefaf82a5962727f3c
|
|
7
|
+
data.tar.gz: 70344238486a35ed0bff5707898f24f3ee807647cb03f95e0b9426fd640a1506a968fb13dc561d18ab13e1262d203c887c0ae83e2aad7db070e8be095412943e
|
data/README.md
CHANGED
|
@@ -164,6 +164,10 @@ link. A boot with eager loading on defines it the same way. After a code reload
|
|
|
164
164
|
development, the reloaded model has the link again, and it points at the reloaded model
|
|
165
165
|
the app fills the port with.
|
|
166
166
|
|
|
167
|
+
A link that names a port its hub does not declare, or one of the hub's call ports, raises
|
|
168
|
+
an `ArgumentError` when the model loads, naming the model, the hub and the port. A link
|
|
169
|
+
whose `port:` is not a hub module raises an `ArgumentError` naming the model.
|
|
170
|
+
|
|
167
171
|
The model names only its own hub, so the crossing check passes it. The app's
|
|
168
172
|
`to_prepare` block that fills the port is the one place both models are named.
|
|
169
173
|
|
|
@@ -213,6 +217,9 @@ whichever order those happen, the same way as a `belongs_to_port` link. A boot w
|
|
|
213
217
|
loading on defines it, and after a code reload in development the reloaded model lists
|
|
214
218
|
the reloaded model the app fills the port with.
|
|
215
219
|
|
|
220
|
+
A has-many link refuses a port that is not one of its hub's record ports, and a `port:`
|
|
221
|
+
that is not a hub module, in the same way as a `belongs_to_port` link.
|
|
222
|
+
|
|
216
223
|
### The start check
|
|
217
224
|
Every hub that declares a port or exposes a method is on `HubKernel::Hubs.list`. Call
|
|
218
225
|
`HubKernel::Hubs.check!` after the app fills its ports, and the app refuses to start
|
|
@@ -393,6 +400,30 @@ end
|
|
|
393
400
|
`HubKernel::Ports` or `HubKernel::Exposes`, such as "hubs/suplies holds no hub module",
|
|
394
401
|
so a misspelled or half-made hub folder does not pass unnoticed.
|
|
395
402
|
|
|
403
|
+
### The unlinked id check
|
|
404
|
+
A host's test can check that every link a hub's model makes is an association, so a new
|
|
405
|
+
link to another hub's record cannot be added as a bare id column:
|
|
406
|
+
|
|
407
|
+
```ruby
|
|
408
|
+
require "hub_kernel/conformance/unlinked_ids"
|
|
409
|
+
|
|
410
|
+
class UnlinkedIdsTest < ActiveSupport::TestCase
|
|
411
|
+
include HubKernel::Conformance::UnlinkedIds
|
|
412
|
+
|
|
413
|
+
unlinked_ids except: [ "Billing::Invoice#legacy_id" ]
|
|
414
|
+
end
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
- The test reads every Active Record model under each hub folder's `app/models`, and
|
|
418
|
+
fails naming each column ending in `_id` that no `belongs_to` on that model uses as its
|
|
419
|
+
foreign key, such as "Kitchen::Dish#oven_id has no association using it".
|
|
420
|
+
- A `belongs_to_port` link counts, and so does an ordinary `belongs_to` inside a hub.
|
|
421
|
+
- `except` lists columns the test does not report, each written as the model and the
|
|
422
|
+
column joined by `#`. The `unlinked_ids` line may be left out when there are none.
|
|
423
|
+
- Abstract classes and models whose table does not exist are skipped.
|
|
424
|
+
- The test fails with "The unlinked id check found no model with a table in any hub
|
|
425
|
+
folder" when it finds none, so a wrong setup does not pass unnoticed.
|
|
426
|
+
|
|
396
427
|
## Installation
|
|
397
428
|
Add this line to your application's Gemfile:
|
|
398
429
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require "active_support/concern"
|
|
2
|
+
require "hub_kernel/hub_folders"
|
|
3
|
+
|
|
4
|
+
module HubKernel
|
|
5
|
+
module Conformance
|
|
6
|
+
module UnlinkedIds
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
class_methods do
|
|
10
|
+
def unlinked_ids(except: [])
|
|
11
|
+
define_method(:unlinked_id_exceptions) { except }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
included do
|
|
16
|
+
test "every id column of a hub model has an association" do
|
|
17
|
+
flunk "The unlinked id check found no model with a table in any hub folder" if hub_models.none?
|
|
18
|
+
|
|
19
|
+
found = hub_models.flat_map { |model| unlinked_columns(model).map { |column| "#{model.name}##{column}" } } - unlinked_id_exceptions
|
|
20
|
+
found = found.map { |entry| "#{entry} has no association using it" }
|
|
21
|
+
assert found.empty?, found.join("\n")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def unlinked_id_exceptions = []
|
|
26
|
+
|
|
27
|
+
def hub_models = @hub_models ||= tabled_hub_models
|
|
28
|
+
|
|
29
|
+
def tabled_hub_models
|
|
30
|
+
Dir.glob("#{HubKernel::HubFolders.code_glob}/models/**/*.rb", base: Rails.root).sort
|
|
31
|
+
.map { |path| path.sub(%r{\A.*?/models/}, "").delete_suffix(".rb").camelize.safe_constantize }
|
|
32
|
+
.select { |model| model.is_a?(Class) && model < ActiveRecord::Base && !model.abstract_class? && model.table_exists? }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def unlinked_columns(model)
|
|
36
|
+
linked = model.reflect_on_all_associations(:belongs_to).map { |association| association.foreign_key.to_s }
|
|
37
|
+
model.column_names.select { |column| column.end_with?("_id") }.sort - linked
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -7,6 +7,7 @@ module HubKernel
|
|
|
7
7
|
|
|
8
8
|
def belongs_to_port(name, scope = nil, port:, record:, **options)
|
|
9
9
|
refuse_writing_options(name, options)
|
|
10
|
+
refuse_non_hub(name, port)
|
|
10
11
|
port.link_record(record, [ self.name, name ]) do |model|
|
|
11
12
|
belongs_to name, read_only(scope), **options, class_name: model.name
|
|
12
13
|
refuse_creating_through(name)
|
|
@@ -15,6 +16,7 @@ module HubKernel
|
|
|
15
16
|
|
|
16
17
|
def has_many_port(name, scope = nil, port:, record:, **options)
|
|
17
18
|
refuse_writing_options(name, options.reject { |option, value| option == :dependent && RESTRICTING.include?(value) }, LIST_WRITING_OPTIONS)
|
|
19
|
+
refuse_non_hub(name, port)
|
|
18
20
|
refusal = "#{self.name}'s #{name} link is read-only, so no record can be written through it"
|
|
19
21
|
port.link_record(record, [ self.name, name ]) do |model|
|
|
20
22
|
has_many name, read_only(scope, refusing_list(refusal)), **options, class_name: model.name
|
|
@@ -29,6 +31,10 @@ module HubKernel
|
|
|
29
31
|
raise ArgumentError, "#{name} is a read-only record port link, so it does not take #{writing.to_sentence}" if writing.any?
|
|
30
32
|
end
|
|
31
33
|
|
|
34
|
+
def refuse_non_hub(name, port)
|
|
35
|
+
raise ArgumentError, "#{self.name} links #{name} through #{port}, which is not a hub module" unless port.is_a?(HubKernel::Ports)
|
|
36
|
+
end
|
|
37
|
+
|
|
32
38
|
def read_only(scope, *extensions)
|
|
33
39
|
return -> { readonly.extending(*extensions) } unless scope
|
|
34
40
|
return -> { instance_exec(&scope).readonly.extending(*extensions) } if scope.arity.zero?
|
data/lib/hub_kernel/ports.rb
CHANGED
|
@@ -22,6 +22,8 @@ module HubKernel
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def link_record(name, model_and_link, &link)
|
|
25
|
+
refuse_link(name, model_and_link, "#{self.name} does not declare") unless declared_ports.include?(name)
|
|
26
|
+
refuse_link(name, model_and_link, "is a call port, not a record port") unless record_ports.include?(name)
|
|
25
27
|
links_to(name)[model_and_link] = link
|
|
26
28
|
filling = public_send(name)
|
|
27
29
|
link.call(filling) if active_record_model?(filling)
|
|
@@ -50,7 +52,11 @@ module HubKernel
|
|
|
50
52
|
|
|
51
53
|
def record_ports = @record_ports ||= []
|
|
52
54
|
|
|
53
|
-
def
|
|
55
|
+
def refuse_link(name, (model, link), reason)
|
|
56
|
+
raise ArgumentError, "#{model} links #{link} through #{self.name} #{name}, which #{reason}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def links_to(name) =(@record_links ||= {})[name] ||= {}
|
|
54
60
|
|
|
55
61
|
def fill_record_port(name, model)
|
|
56
62
|
instance_variable_set(:"@#{name}", model)
|
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.27.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- tylercschneider
|
|
@@ -59,6 +59,7 @@ files:
|
|
|
59
59
|
- lib/hub_kernel/conformance/exposed.rb
|
|
60
60
|
- lib/hub_kernel/conformance/hub.rb
|
|
61
61
|
- lib/hub_kernel/conformance/partial.rb
|
|
62
|
+
- lib/hub_kernel/conformance/unlinked_ids.rb
|
|
62
63
|
- lib/hub_kernel/crossings.rb
|
|
63
64
|
- lib/hub_kernel/engine.rb
|
|
64
65
|
- lib/hub_kernel/follow_up.rb
|