hub_kernel 0.7.0 → 0.9.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: 03e711745ec2a1b5caccf83f09e80cad02ba4214cc7eff72745c2a6d760e175e
4
- data.tar.gz: f3bc9c33cd2f1ae4878f9cb9dd2216ebda64c2c3628f7d1a29794dee4f10ef11
3
+ metadata.gz: 6c55c177c5ca371059cf35185392a5b283a314254dff6b18aa2d13f7c12793cd
4
+ data.tar.gz: 23afef8f196447cecf97c1b2e0355aba897388784e2c1ddb80d6ff7ff9da7f98
5
5
  SHA512:
6
- metadata.gz: 443d578d53ebb20b08fb3e3c3557df70780f8f85e8aab44af99c68ee6f3311897649210b544ba6356b4d1e7a1692576e36e84c36daed045ab2d47270ddc69b2c
7
- data.tar.gz: 833fba7864e95ad4ff7e18284bd07998f7ff30df5269489c879a447541e207997ebeb09d2dafad1596aa0299c7a05259d2e997682c6b5edffe490772f405a061
6
+ metadata.gz: 61caa0ec7d31d6fbd86dc005dae507895eb2a7e4a1f1c09b3be2976edbc8ff69b5f14108a007dffe38b501b4d773250544ed625457d87e8ed7cbdf7f811796ff
7
+ data.tar.gz: 532c56d1800f02ab79faab68553fc75edd7f20bdcbc8677d2a4e7416dde7ec063ff79c94825db9bf99f10bd380b5874d52657e6beef5a03b8ef08532b6c71a93
data/README.md CHANGED
@@ -4,16 +4,43 @@ Gives every hub in a Rails app one way to declare what it needs from another hub
4
4
  ## Usage
5
5
 
6
6
  ### Starting a hub
7
- The generator writes a new hub's module and its test. Name each port as the port and
8
- the method the hub's own code calls:
7
+ A hub usually lives in a domain gem, next to the domain logic it serves. Run the
8
+ generator from the gem's root, naming each port as the port and the method the hub's
9
+ own code calls:
9
10
 
10
11
  ```bash
11
- bin/rails generate hub_kernel:hub Console::Hubs::Billing spend_recorder:record_spend
12
+ bin/rails generate hub_kernel:hub Ledger entry_recorder:record_entry
12
13
  ```
13
14
 
14
- It writes `app/models/console/hubs/billing.rb`, which extends `HubKernel::Ports` and
15
- declares each port, and `test/models/console/hubs/billing_test.rb`, which includes the
16
- hub check below. The app then fills the new ports where it fills its others.
15
+ Inside a Rails engine gem it writes the hub under the gem's namespace, such as
16
+ `app/models/billing/ledger.rb` with `module Billing::Ledger`, which extends
17
+ `HubKernel::Ports` and declares each port. It also adds hub_kernel to the gem's
18
+ gemspec. It writes no hub check into the gem's tests, since the gem never fills its own
19
+ ports.
20
+
21
+ ### Using a hub from a gem
22
+ The app that installs the gem fills each of the hub's ports in its own setup, then
23
+ runs the start check:
24
+
25
+ ```ruby
26
+ Rails.application.config.to_prepare do
27
+ Billing::Ledger.entry_recorder = Finance.method(:record_entry)
28
+ HubKernel::Hubs.check!
29
+ end
30
+ ```
31
+
32
+ The app's own test for that hub includes the hub check, where the ports are filled:
33
+
34
+ ```ruby
35
+ class Billing::LedgerHubTest < ActiveSupport::TestCase
36
+ include HubKernel::Conformance::Hub
37
+
38
+ hub { Billing::Ledger }
39
+ end
40
+ ```
41
+
42
+ Run inside an app rather than a gem, the generator writes the hub under
43
+ `app/models` and its hub check under `test/models`.
17
44
 
18
45
  ### Ports
19
46
  A port is something a hub needs from outside itself. The hub declares it by name,
@@ -89,6 +116,24 @@ Supplies.exposed("record_purchase").takes # => [:supplier_id, :bought_on, :lines
89
116
  Supplies.exposed("delete_everything") # => nil
90
117
  ```
91
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
+
92
137
  ## Installation
93
138
  Add this line to your application's Gemfile:
94
139
 
@@ -14,6 +14,8 @@ module HubKernel
14
14
  end
15
15
 
16
16
  def create_hub_test
17
+ return if gemspec
18
+
17
19
  create_file "test/models/#{file_path}_test.rb", <<~RUBY
18
20
  require "test_helper"
19
21
  require "hub_kernel/conformance/hub"
@@ -26,8 +28,16 @@ module HubKernel
26
28
  RUBY
27
29
  end
28
30
 
31
+ def depend_on_hub_kernel
32
+ return unless gemspec
33
+
34
+ inject_into_file gemspec, " spec.add_dependency \"hub_kernel\", \"~> #{HubKernel::VERSION[/\A\d+\.\d+/]}\"\n", before: /^end\s*\z/
35
+ end
36
+
29
37
  private
30
38
 
39
+ def gemspec = Dir.glob("*.gemspec", base: destination_root).first
40
+
31
41
  def port_lines
32
42
  return "" if ports.empty?
33
43
 
@@ -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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module HubKernel
2
- VERSION = "0.7.0"
2
+ VERSION = "0.9.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.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - tylercschneider
@@ -42,6 +42,7 @@ 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/exposed.rb
45
46
  - lib/hub_kernel/conformance/hub.rb
46
47
  - lib/hub_kernel/conformance/partial.rb
47
48
  - lib/hub_kernel/context.rb