foobara 0.4.1 → 0.4.2

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +3 -1
  4. data/projects/command/src/command_pattern_implementation/concerns/transactions.rb +1 -0
  5. data/projects/command_connectors/Guardfile +1 -1
  6. data/projects/command_connectors/spec/auth_mappers_spec.rb +201 -0
  7. data/projects/command_connectors/spec/pre_commit_transformers/load_atoms_pre_commit_transformer_spec.rb +5 -2
  8. data/projects/command_connectors/spec/spec_helper.rb +8 -3
  9. data/projects/command_connectors/src/authenticator.rb +27 -16
  10. data/projects/command_connectors/src/command_connector/request.rb +27 -1
  11. data/projects/command_connectors/src/command_connector.rb +88 -3
  12. data/projects/command_connectors/src/transformed_command.rb +12 -2
  13. data/projects/entities/spec/spec_helper.rb +4 -2
  14. data/projects/entities_plumbing/Guardfile +6 -0
  15. data/projects/entities_plumbing/lib/foobara/entities_plumbing.rb +12 -0
  16. data/projects/entities_plumbing/spec/auth_mappers_spec.rb +159 -0
  17. data/projects/entities_plumbing/spec/spec_helper.rb +54 -0
  18. data/projects/entities_plumbing/spec/support/rubyprof.rb +32 -0
  19. data/projects/entities_plumbing/spec/support/term_trap.rb +6 -0
  20. data/projects/entities_plumbing/src/command_connectors_extension.rb +17 -0
  21. data/projects/entities_plumbing/src/extensions/authenticator.rb +13 -0
  22. data/projects/manifest/spec/spec_helper.rb +6 -3
  23. data/projects/typesystem/projects/builtin_types/src/builtin_types.rb +6 -6
  24. data/projects/typesystem/projects/domain/src/domain.rb +0 -15
  25. data/projects/typesystem/projects/domain/src/domain_module_extension.rb +0 -153
  26. data/projects/typesystem/projects/domain/src/module_extension.rb +0 -58
  27. data/projects/typesystem/projects/namespace/src/is_namespace.rb +8 -0
  28. data/projects/typesystem/projects/type_declarations/lib/foobara/type_declarations.rb +0 -15
  29. data/projects/typesystem/projects/type_declarations/src/type_declaration.rb +15 -12
  30. data/projects/typesystem/projects/type_declarations/src/typed_transformer.rb +13 -2
  31. data/projects/typesystem/spec/common/data_path_spec.rb +6 -8
  32. data/projects/typesystem/spec/spec_helper.rb +5 -3
  33. data/version.rb +1 -1
  34. metadata +11 -2
  35. data/projects/entities/projects/model/src/extensions/domain/module_extension.rb +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd07a1cd0a343e918e6686086afa86cb0996b04a120a67381d4582bfdfae7171
4
- data.tar.gz: cbfd94dde3fbee1846451afabac7ce3bb1b4b2f0ae7c49d2844df17486c18df4
3
+ metadata.gz: 979ed0e27280b7f1183c77f8ca34eacaab8b232c719bf9f9ad4964ef309da64c
4
+ data.tar.gz: 819ad164acb49337551e91814f5faf3a78a825085c5b723d76fff701d4c1adcd
5
5
  SHA512:
6
- metadata.gz: db7af13da7e1b0faff58350ea7953487f856cff45f92beb5edf8249dbeb041dffd1f7ef98cf405b5fd71c1cb6b4d46014256c48dd9432f84ea5b5688fa8cbfbe
7
- data.tar.gz: 54563dc01bca8752b202ac61e50909d92794ea80357d4090c4741e27c6db705d498508d8cabee7c6dc40c28c51731cbee6449c8484f732dbb49adf3749c5aebd
6
+ metadata.gz: 488905b81c1b660af40b374cef9f34ebaf5548cae9202afdb2a90680abd78be9673a7b51de2431f1ce836b7815cf82ff1be245757e86da1ea0d4bcb4eed74294
7
+ data.tar.gz: 4a1d3377183c75ceea13be7d1501c686c97f9b6b29f76a01e9a9b9fa0e1addd18a0fdbef4bf6879e21f383aa3dc304d6ed2c1f9d1a3ac788be3db41ecc1793eb
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ # [0.4.2] - 2026-01-15
2
+
3
+ - Fix relaxed lookup bug for a symbol starting with a prefix
4
+ - Allow registered types to be looked up by string and in relaxed mode
5
+ - Create entities_plumbing project and implement auth mapping for entities
6
+ - Implement auth mappers concept
7
+ - Change authenticator to be a TypedTransformer
8
+ - Allow passing a block to a connector to simplify connecting stuff a bit
9
+ - Remove likely-unused automatic creation of Types module constants
10
+ - Support official ruby debug
11
+
1
12
  # [0.4.1] - 2025-12-19
2
13
 
3
14
  - Add a model_plumbing project and use it to fix up commands when models are reregistered
data/README.md CHANGED
@@ -1578,7 +1578,9 @@ module FoobaraDemo
1578
1578
 
1579
1579
  alias animal from
1580
1580
 
1581
- foobara_delegate :first_name, :last_name, :birthday, to: :animal # Deprecated
1581
+ def first_name = animal.first_name
1582
+ def last_name = animal.last_name
1583
+ def birthday = animal.birthday
1582
1584
 
1583
1585
  def birthday_to_age
1584
1586
  today = Date.today
@@ -7,6 +7,7 @@ module Foobara
7
7
  include Concern
8
8
  include NestedTransactionable
9
9
 
10
+ # TODO: move this out of here!
10
11
  def relevant_entity_classes
11
12
  return @relevant_entity_classes if defined?(@relevant_entity_classes)
12
13
 
@@ -1,4 +1,4 @@
1
- guard :rspec, all_after_pass: true, all_on_start: true, cmd: "bundle exec rspec", failed_mode: :focus do
1
+ guard :rspec, all_after_pass: false, all_on_start: true, cmd: "bundle exec rspec", failed_mode: :focus do
2
2
  watch(%r{^spec/(.+)_spec\.rb$})
3
3
  watch(%r{^src/(.+)\.rb$}) { "spec/" }
4
4
  watch(%r{^lib/(.+)\.rb$}) { "spec/" }
@@ -0,0 +1,201 @@
1
+ RSpec.describe Foobara::CommandConnector do
2
+ after do
3
+ Foobara.reset_alls
4
+ end
5
+
6
+ let(:command_class) do
7
+ stub_class(:ComputeExponent, Foobara::Command) do
8
+ inputs exponent: :integer, base: :integer
9
+ result :integer
10
+
11
+ def execute = base ** exponent
12
+ end
13
+ end
14
+
15
+ let(:command_connector) do
16
+ described_class.new(authenticator:, current_user: current_user_mapper)
17
+ end
18
+
19
+ let(:authenticator) do
20
+ proc { "some_user" }
21
+ end
22
+
23
+ let(:base) { 2 }
24
+ let(:exponent) { 3 }
25
+
26
+ let(:response) { command_connector.run(full_command_name:, action:, inputs:) }
27
+ let(:errors_hash) { response.body.errors_hash }
28
+
29
+ let(:action) { "run" }
30
+ let(:full_command_name) { "ComputeExponent" }
31
+ let(:inputs) do
32
+ { base:, exponent: }
33
+ end
34
+
35
+ describe "#run_command" do
36
+ class << self
37
+ def with_current_user_allowed_rule
38
+ let(:allowed_rule) do
39
+ -> { current_user == "current_user is some_user" }
40
+ end
41
+ end
42
+
43
+ def with_current_user_not_allowed_rule
44
+ let(:allowed_rule) do
45
+ -> { current_user == "asfdasdf" }
46
+ end
47
+ end
48
+
49
+ def with_authenticated_user_allowed_rule
50
+ let(:allowed_rule) do
51
+ -> { authenticated_user == "some_user" }
52
+ end
53
+ end
54
+
55
+ def with_authenticated_user_not_allowed_rule
56
+ let(:allowed_rule) do
57
+ -> { authenticated_user == "asfdasdf" }
58
+ end
59
+ end
60
+ end
61
+
62
+ shared_examples "a connector with auth mappers" do
63
+ before do
64
+ command_connector.connect(command_class, allowed_rule:)
65
+ end
66
+
67
+ context "when using current_user in the allowed rule" do
68
+ context "when allowed" do
69
+ with_current_user_allowed_rule
70
+
71
+ it "runs the command" do
72
+ expect(response.status).to be(0)
73
+ expect(response.body).to eq(8)
74
+ end
75
+ end
76
+
77
+ context "when not allowed" do
78
+ with_current_user_not_allowed_rule
79
+
80
+ it "fails with a relevant error" do
81
+ expect(response.status).to be(1)
82
+ expect(errors_hash.key?("runtime.not_allowed")).to be true
83
+ end
84
+ end
85
+
86
+ context "when calling current_user multiple times" do
87
+ let(:allowed_rule) do
88
+ -> { current_user && current_user == "current_user is some_user" }
89
+ end
90
+
91
+ it "runs the command" do
92
+ expect(response.status).to be(0)
93
+ expect(response.body).to eq(8)
94
+ end
95
+ end
96
+ end
97
+
98
+ context "when using authenticated_user in the allowed rule" do
99
+ context "when allowed" do
100
+ with_authenticated_user_allowed_rule
101
+
102
+ it "runs the command" do
103
+ expect(response.status).to be(0)
104
+ expect(response.body).to eq(8)
105
+ end
106
+ end
107
+
108
+ context "when not allowed" do
109
+ with_authenticated_user_not_allowed_rule
110
+
111
+ it "fails with a relevant error" do
112
+ expect(response.status).to be(1)
113
+ expect(errors_hash.key?("runtime.not_allowed")).to be true
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ context "when auth user mapper is a hash" do
120
+ let(:current_user_mapper) do
121
+ {
122
+ to: :string,
123
+ map: ->(authenticated_user) { "current_user is #{authenticated_user}" }
124
+ }
125
+ end
126
+
127
+ it_behaves_like "a connector with auth mappers"
128
+ end
129
+
130
+ context "when auth user mapper is an array" do
131
+ let(:current_user_mapper) { [:string, ->(authenticated_user) { "current_user is #{authenticated_user}" }] }
132
+
133
+ it_behaves_like "a connector with auth mappers"
134
+ end
135
+
136
+ context "when auth user mapper is a typed transformer class" do
137
+ let(:current_user_mapper) do
138
+ stub_class("SomeTypedTransformer", Foobara::TypeDeclarations::TypedTransformer) do
139
+ to :string
140
+
141
+ def transform(authenticated_user)
142
+ "current_user is #{authenticated_user}"
143
+ end
144
+ end
145
+ end
146
+
147
+ it_behaves_like "a connector with auth mappers"
148
+
149
+ context "when it is an instance" do
150
+ let(:current_user_mapper) { super().instance }
151
+
152
+ it_behaves_like "a connector with auth mappers"
153
+ end
154
+ end
155
+
156
+ context "when auth user mapper is a domain mapper" do
157
+ let(:current_user_mapper) do
158
+ stub_class("SomeDomainMapper", Foobara::DomainMapper) do
159
+ from :string
160
+ to :string
161
+
162
+ def map = "current_user is #{from}"
163
+ end
164
+ end
165
+
166
+ it_behaves_like "a connector with auth mappers"
167
+ end
168
+
169
+ context "when auth user mapper is a command" do
170
+ context "with only one input" do
171
+ let(:current_user_mapper) do
172
+ stub_class("SomeCommand", Foobara::Command) do
173
+ inputs auth_user: :string
174
+ result :string
175
+
176
+ def execute = "current_user is #{auth_user}"
177
+ end
178
+ end
179
+
180
+ it_behaves_like "a connector with auth mappers"
181
+ end
182
+
183
+ context "with multiple inputs but only one required input" do
184
+ let(:current_user_mapper) do
185
+ stub_class("SomeCommand", Foobara::Command) do
186
+ inputs do
187
+ foo :string
188
+ bar :string
189
+ auth_user :string, :required
190
+ end
191
+ result :string
192
+
193
+ def execute = "current_user is #{auth_user}"
194
+ end
195
+ end
196
+
197
+ it_behaves_like "a connector with auth mappers"
198
+ end
199
+ end
200
+ end
201
+ end
@@ -76,14 +76,17 @@ RSpec.describe Foobara::CommandConnectors::Transformers::LoadAtomsPreCommitTrans
76
76
  end
77
77
 
78
78
  let(:command_connector) do
79
- Foobara::CommandConnector.new(default_pre_commit_transformers: described_class)
79
+ cc = command_class
80
+ Foobara::CommandConnector.new(default_pre_commit_transformers: described_class) do
81
+ command cc
82
+ end
80
83
  end
81
84
 
82
85
  let(:transformer) { described_class.new(to: type) }
83
86
 
84
87
  describe "#transform" do
85
88
  it "gives what was passed in because we expect this data to be cast where needed" do
86
- command_connector.connect(command_class)
89
+ command_connector
87
90
  response = command_connector.run(
88
91
  full_command_name: command_class.full_command_name,
89
92
  inputs: value,
@@ -2,9 +2,11 @@ ENV["FOOBARA_ENV"] = "test"
2
2
 
3
3
  require "bundler/setup"
4
4
 
5
- unless ENV["SKIP_PRY"] == "true" || ENV["CI"] == "true"
5
+ if ENV["RUBY_DEBUG"] == "true"
6
+ require "debug"
7
+ elsif ENV["SKIP_PRY"] != "true" && ENV["CI"] != "true"
6
8
  require "pry"
7
- require "pry-byebug"
9
+ require "pry-byebug" unless ENV["SKIP_BYEBUG"] == "true"
8
10
  end
9
11
 
10
12
  require "rspec/its"
@@ -22,13 +24,16 @@ end
22
24
 
23
25
  require "foobara/all"
24
26
  require "foobara/command_connectors"
27
+ # TODO: eliminate this require! We should not depend on entities in this project!
28
+ require "foobara/entities_plumbing"
25
29
 
26
30
  RSpec.configure do |config|
27
31
  # Need to do :all instead of :each because for specs that use .around,
28
32
  # .after(:each) do |example| here is called after example.run but before any threads created in
29
33
  # .around might have been cleaned up.
30
34
  config.after(:suite) do
31
- expect(Thread.list.size).to eq(1)
35
+ threads = Thread.list.reject { |t| t.name == "DEBUGGER__::SESSION@server" }
36
+ expect(threads.size).to eq(1)
32
37
  end
33
38
  config.filter_run_when_matching :focus
34
39
 
@@ -1,28 +1,39 @@
1
1
  module Foobara
2
2
  class CommandConnector
3
- # TODO: should switch to a processor and give errors if the authenticator header is malformed
4
- class Authenticator < Value::Transformer
5
- attr_reader :block
3
+ class Authenticator < TypeDeclarations::TypedTransformer
4
+ class << self
5
+ attr_writer :default_symbol
6
+ attr_accessor :default_explanation, :default_block
6
7
 
7
- def initialize(symbol: nil, explanation: nil, &block)
8
- symbol ||= Util.non_full_name_underscore(self.class).to_sym
9
- explanation ||= symbol
8
+ def subclass(to: nil, symbol: nil, explanation: nil, &default_block)
9
+ klass = super(to:, from: Request, &nil)
10
10
 
11
- super(symbol:, explanation:)
11
+ klass.default_symbol = symbol if symbol
12
+ klass.default_explanation = explanation if explanation
13
+ klass.default_block = default_block if default_block
12
14
 
13
- @block = block
14
- end
15
+ klass
16
+ end
15
17
 
16
- def relevant_entity_classes(_request)
17
- nil
18
- end
18
+ def default_symbol
19
+ @default_symbol || Util.non_full_name_underscore(self)&.to_sym
20
+ end
19
21
 
20
- def symbol
21
- declaration_data[:symbol]
22
+ def instance
23
+ @instance ||= new
24
+ end
22
25
  end
23
26
 
24
- def explanation
25
- declaration_data[:explanation]
27
+ attr_accessor :symbol
28
+ attr_reader :block, :explanation
29
+
30
+ def initialize(symbol: nil, explanation: nil, &block)
31
+ self.symbol = symbol || self.class.default_symbol
32
+ @explanation = explanation || self.class.default_explanation || self.symbol
33
+
34
+ super()
35
+
36
+ @block = block || self.class.default_block
26
37
  end
27
38
 
28
39
  def transform(request)
@@ -63,6 +63,31 @@ module Foobara
63
63
  command_class.authenticator
64
64
  end
65
65
 
66
+ def auth_mappers
67
+ # TODO: make this consistent?
68
+ command_connector.auth_map
69
+ end
70
+
71
+ def auth_mapped_method?(method_name)
72
+ auth_mappers&.key?(method_name)
73
+ end
74
+
75
+ def auth_mapper_for(name)
76
+ auth_mappers&.[](name.to_sym)
77
+ end
78
+
79
+ def auth_mapped_value_for(name)
80
+ return unless authenticated_user
81
+
82
+ @auth_mapper_cache ||= {}
83
+
84
+ if @auth_mapper_cache.key?(name)
85
+ @auth_mapper_cache[name]
86
+ else
87
+ @auth_mapper_cache[name] = auth_mapper_for(name)&.process_value!(authenticated_user)
88
+ end
89
+ end
90
+
66
91
  def serializer
67
92
  return @serializer if defined?(@serializer)
68
93
 
@@ -113,11 +138,12 @@ module Foobara
113
138
  outcome.error_collection
114
139
  end
115
140
 
141
+ # TODO: move this to entity_plumbing
116
142
  def relevant_entity_classes
117
143
  if command_class.is_a?(::Class) && command_class < TransformedCommand
118
144
  entity_classes = authenticator&.relevant_entity_classes(self)
119
145
  [*entity_classes, *relevant_entity_classes_from_inputs_transformer]
120
- end || []
146
+ end || EMPTY_ARRAY
121
147
  end
122
148
 
123
149
  private
@@ -63,7 +63,7 @@ module Foobara
63
63
  case object
64
64
  when Class
65
65
  if object < Authenticator
66
- object.new
66
+ object.instance
67
67
  else
68
68
  # :nocov:
69
69
  raise ArgumentError, "Expected a class that inherits from Authenticator"
@@ -116,17 +116,94 @@ module Foobara
116
116
  end
117
117
  end
118
118
  end
119
+
120
+ # don't call this auth_user ?
121
+ def to_auth_user_mapper(object)
122
+ case object
123
+ when TypeDeclarations::TypedTransformer
124
+ object
125
+ when ::Class
126
+ if object < TypeDeclarations::TypedTransformer
127
+ object.instance
128
+ elsif object < Foobara::DomainMapper
129
+ build_auth_mapper(object.to_type) { |authenticated_user| object.map!(authenticated_user) }
130
+ elsif object < Foobara::Command
131
+ inputs_type = object.inputs_type
132
+ element_types = inputs_type.element_types
133
+ size = element_types.size
134
+
135
+ first_required_input = if size == 1
136
+ element_types.keys.first
137
+ elsif size > 1
138
+ declaration = inputs_type&.declaration_data
139
+ required_attribute_names = declaration&.[](:required) || EMPTY_ARRAY
140
+
141
+ if required_attribute_names.size == 1
142
+ required_attribute_names.first
143
+ else
144
+ # :nocov:
145
+ raise ArgumentError,
146
+ "Ambiguous inputs when trying to use #{object} as a mapper. " \
147
+ "Should have either only 1 input or only 1 required input."
148
+ # :nocov:
149
+ end
150
+ else
151
+ # :nocov:
152
+ raise ArgumentError, "To use a command as a mapper it must take an input to map"
153
+ # :nocov:
154
+ end
155
+
156
+ build_auth_mapper(object.result_type) do |authenticated_user|
157
+ object.run!(first_required_input => authenticated_user)
158
+ end
159
+ else
160
+ # :nocov:
161
+ raise ArgumentError, "not sure how to convert a #{object} to an auth mapper"
162
+ # :nocov:
163
+ end
164
+ when ::Hash
165
+ object => { to:, map: }
166
+ build_auth_mapper(to, &map)
167
+ when ::Array
168
+ object => [to, map]
169
+ build_auth_mapper(to, &map)
170
+ else
171
+ # :nocov:
172
+ raise ArgumentError, "Not sure how to convert #{object} to an auth mapper"
173
+ # :nocov:
174
+ end
175
+ end
176
+
177
+ # TODO: make private
178
+ def build_auth_mapper(to_type, &)
179
+ TypeDeclarations::TypedTransformer.subclass(to: to_type, &).instance
180
+ end
119
181
  end
120
182
 
121
- attr_accessor :command_registry, :authenticator, :capture_unknown_error, :name
183
+ attr_accessor :command_registry, :authenticator, :capture_unknown_error, :name,
184
+ :auth_map
122
185
 
123
186
  def initialize(name: nil,
124
187
  authenticator: nil,
125
188
  capture_unknown_error: nil,
126
189
  default_serializers: nil,
127
- default_pre_commit_transformers: nil)
190
+ default_pre_commit_transformers: nil,
191
+ auth_map: nil,
192
+ current_user: nil,
193
+ &block)
128
194
  authenticator = self.class.to_authenticator(authenticator)
129
195
 
196
+ if current_user
197
+ auth_map ||= {}
198
+ auth_map[:current_user] = current_user
199
+ end
200
+
201
+ if auth_map
202
+ self.auth_map = auth_map.transform_values do |mapper|
203
+ self.class.to_auth_user_mapper(mapper)
204
+ end
205
+ end
206
+
130
207
  self.authenticator = authenticator
131
208
  self.command_registry = CommandRegistry.new(authenticator:)
132
209
  self.capture_unknown_error = capture_unknown_error
@@ -143,8 +220,15 @@ module Foobara
143
220
  self.class.allowed_rules_to_register.each do |ruleish_args|
144
221
  command_registry.allowed_rule(*ruleish_args)
145
222
  end
223
+
224
+ if block
225
+ instance_eval(&block)
226
+ end
146
227
  end
147
228
 
229
+ # TODO: should this be the official way to connect a command instead of #connect ?
230
+ def command(...) = connect(...)
231
+
148
232
  def connect(*args, **opts)
149
233
  args, opts = desugarize_connect_args(args, opts)
150
234
 
@@ -477,6 +561,7 @@ module Foobara
477
561
  end
478
562
  end
479
563
 
564
+ # TODO: get all this persistence stuff out of here and into entities plumbing somehow
480
565
  def run_request(request)
481
566
  command_class = determine_command_class(request)
482
567
  request.command_class = command_class
@@ -782,7 +782,9 @@ module Foobara
782
782
  end
783
783
 
784
784
  def method_missing(method_name, ...)
785
- if command.respond_to?(method_name)
785
+ if auth_mapped_method?(method_name)
786
+ auth_mapped_value_for(method_name)
787
+ elsif command.respond_to?(method_name)
786
788
  command.send(method_name, ...)
787
789
  else
788
790
  # :nocov:
@@ -791,8 +793,16 @@ module Foobara
791
793
  end
792
794
  end
793
795
 
796
+ def auth_mapped_value_for(name)
797
+ request.auth_mapped_value_for(name)
798
+ end
799
+
800
+ def auth_mapped_method?(method_name)
801
+ request&.auth_mapped_method?(method_name)
802
+ end
803
+
794
804
  def respond_to_missing?(method_name, private = false)
795
- command.respond_to?(method_name, private) || super
805
+ command.respond_to?(method_name, private) || auth_mapped_method?(method_name) || super
796
806
  end
797
807
 
798
808
  private
@@ -2,9 +2,11 @@ ENV["FOOBARA_ENV"] = "test"
2
2
 
3
3
  require "bundler/setup"
4
4
 
5
- unless ENV["SKIP_PRY"] == "true" || ENV["CI"] == "true"
5
+ if ENV["RUBY_DEBUG"] == "true"
6
+ require "debug"
7
+ elsif ENV["SKIP_PRY"] != "true" && ENV["CI"] != "true"
6
8
  require "pry"
7
- require "pry-byebug"
9
+ require "pry-byebug" unless ENV["SKIP_BYEBUG"] == "true"
8
10
  end
9
11
 
10
12
  require "rspec/its"
@@ -0,0 +1,6 @@
1
+ guard :rspec, all_after_pass: false, all_on_start: true, cmd: "bundle exec rspec", failed_mode: :focus do
2
+ watch(%r{^spec/(.+)_spec\.rb$})
3
+ watch(%r{^src/(.+)\.rb$}) { "spec/" }
4
+ watch(%r{^lib/(.+)\.rb$}) { "spec/" }
5
+ watch(%r{^spec/spec_helper.rb$}) { "spec/" }
6
+ end
@@ -0,0 +1,12 @@
1
+ require "foobara/command_connectors"
2
+
3
+ module Foobara
4
+ module EntitiesPlumbing
5
+ end
6
+ end
7
+
8
+ Foobara.project("entities_plumbing", project_path: "#{__dir__}/../..")
9
+
10
+ Foobara::CommandConnector.singleton_class.prepend(
11
+ Foobara::EntitiesPlumbing::CommandConnectorsExtension::ClassMethods
12
+ )