interactify 0.4.1 → 0.5.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/.rubocop.yml +3 -0
- data/CHANGELOG.md +7 -0
- data/README.md +85 -9
- data/lib/interactify/configuration.rb +8 -0
- data/lib/interactify/contracts/helpers.rb +8 -0
- data/lib/interactify/contracts/mismatching_organizer_error.rb +40 -0
- data/lib/interactify/contracts/organizing.rb +32 -0
- data/lib/interactify/dsl/each_chain.rb +23 -5
- data/lib/interactify/dsl/if_interactor.rb +6 -5
- data/lib/interactify/dsl/if_klass.rb +38 -2
- data/lib/interactify/dsl/unique_klass_name.rb +1 -1
- data/lib/interactify/dsl/wrapper.rb +6 -0
- data/lib/interactify/dsl.rb +32 -10
- data/lib/interactify/interactify_callable.rb +11 -0
- data/lib/interactify/version.rb +1 -1
- data/lib/interactify.rb +28 -23
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d55a6d70e6996f8fb4e2f523f80532b6337e43072110283c9479fb4ee3f10bde
|
4
|
+
data.tar.gz: a948d6aed7f5076ebcc2851f1ede6658353c64c4e4947d68b7a70d0975828a92
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ad70be9c0770932b1658509e19e58a9251c91fc7117f42a9191e4b4a9ec1e9bb4606ea33b7321561036df3969505b347f61ceb8b387ae8b75381f8db0bc0a41
|
7
|
+
data.tar.gz: a6cd09925a0d1bad7d62ec24f06970a4647f20de4d135e671bf2ed1681d0976d76670d1c87bf28d5d7121826a86b8b61891ddac87921f210c69cc3da9c2db559
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -28,3 +28,10 @@
|
|
28
28
|
|
29
29
|
## [0.4.1] - 2023-12-29
|
30
30
|
- Fix bug triggered when nesting each and if
|
31
|
+
|
32
|
+
## [0.5.0] - 2024-01-01
|
33
|
+
- Add support for `SetA = Interactify { _1.a = 'a' }`, lambda and block class creation syntax
|
34
|
+
- Add support for organizing `organize A.organizing(B, C, D), E, F` contract syntax
|
35
|
+
- make definition errors raise optionally
|
36
|
+
- raise an error with unexpected keys in Interactify.if clause
|
37
|
+
- propagate caller_info through chains
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Interactify
|
2
2
|
|
3
|
-
[](https://badge.fury.io/rb/interactify)
|
3
|
+
[](https://badge.fury.io/rb/interactify)
|
4
4
|
[](LICENSE)
|
5
5
|

|
6
6
|

|
@@ -25,20 +25,30 @@ gem 'interactify'
|
|
25
25
|
```ruby
|
26
26
|
# in config/initializers/interactify.rb
|
27
27
|
Interactify.configure do |config|
|
28
|
-
#
|
29
|
-
|
30
|
-
end
|
28
|
+
# defaults
|
29
|
+
config.root = Rails.root / 'app'
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
end
|
31
|
+
config.on_contract_breach do |context, contract_failures|
|
32
|
+
# maybe add context to Sentry or Honeybadger etc here
|
33
|
+
end
|
34
|
+
|
35
|
+
# called when an Interactify.organizing or Interactify.promising fails to match the actual interactor
|
36
|
+
# definitions
|
37
|
+
config.on_definition_error = Kernel.method(:raise)
|
35
38
|
|
36
|
-
|
37
|
-
#
|
39
|
+
# config.on_definition_error do |error|
|
40
|
+
# # you may want to raise an error in test but not in production
|
41
|
+
# # or you may want to log the error
|
42
|
+
# end
|
43
|
+
|
44
|
+
config.before_raise do |exception|
|
45
|
+
# maybe add context to Sentry or Honeybadger etc here
|
46
|
+
end
|
38
47
|
end
|
39
48
|
```
|
40
49
|
|
41
50
|
### Using the RSpec matchers
|
51
|
+
|
42
52
|
```ruby
|
43
53
|
# e.g. in spec/support/interactify.rb
|
44
54
|
require 'interactify/rspec_matchers/matchers'
|
@@ -109,6 +119,24 @@ organize \
|
|
109
119
|
Thing2
|
110
120
|
```
|
111
121
|
|
122
|
+
Sometimes you also want a one liner but testability too.
|
123
|
+
the `Interactify` method will take a block or a lambda and return an Interactify class.
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
# passing a block
|
127
|
+
DecorateOrder = Interactify do |context|
|
128
|
+
context.order = context.order.decorate
|
129
|
+
end
|
130
|
+
|
131
|
+
# passing a lambda
|
132
|
+
DecorateOrder = Interactify(some_lambda)
|
133
|
+
|
134
|
+
# passing anything that responds to call
|
135
|
+
# please note if you pass a class it will be instantiated first
|
136
|
+
# so you can't pass a class with a constructor that takes arguments
|
137
|
+
DecorateOrder = Interactify(callable_object)
|
138
|
+
```
|
139
|
+
|
112
140
|
### Each/Iteration
|
113
141
|
|
114
142
|
Sometimes we want an interactor for each item in a collection.
|
@@ -308,6 +336,54 @@ Actual promises are:
|
|
308
336
|
step1
|
309
337
|
```
|
310
338
|
|
339
|
+
### Organizing
|
340
|
+
You can now annotate your interactors in the organize arguments with their sub-organizers' interactors.
|
341
|
+
This also serves as executable documentation, validated at load time, and is enforced to stay in sync.
|
342
|
+
|
343
|
+
|
344
|
+
```ruby
|
345
|
+
organize \
|
346
|
+
LoadOrder,
|
347
|
+
MarkAsPaid,
|
348
|
+
SendOutNotifications.organizing(
|
349
|
+
EmailUser,
|
350
|
+
SendPush,
|
351
|
+
NotifySomeThirdParty,
|
352
|
+
SendOutNotifications::DoAnotherThing
|
353
|
+
)
|
354
|
+
|
355
|
+
class SendOutNotifications
|
356
|
+
organize \
|
357
|
+
EmailUser,
|
358
|
+
SendPush,
|
359
|
+
NotifySomeThirdParty,
|
360
|
+
SetData = Interactify do |context|
|
361
|
+
context.data = {this: true}
|
362
|
+
end
|
363
|
+
end
|
364
|
+
```
|
365
|
+
|
366
|
+
In this example, it might seem reasonable for an editor of SendOutNotifications to append SetData to the end of the chain.
|
367
|
+
However, the naming here is not ideal.
|
368
|
+
If it's generically named and not easily searchable, then the discoverability of the code is reduced.
|
369
|
+
|
370
|
+
By invoking `.organizing` when the original author first uses `SendOutNotifications`, it encourages the subsequent editor to think about and document its own callers.
|
371
|
+
They may still choose to add `SetData` to the end of the chain, but this increases the chances of more easily finding where the data is changed.
|
372
|
+
|
373
|
+
|
374
|
+
Example load time failure:
|
375
|
+
```
|
376
|
+
SendOutNotifications does not organize:
|
377
|
+
[EmailUser, SendPush, NotifySomeThirdParty]
|
378
|
+
|
379
|
+
Actual organized classes are:
|
380
|
+
[EmailUser, SendPush, NotifySomeThirdParty, SendOutNotifications::SetData]
|
381
|
+
|
382
|
+
Expected organized classes are:
|
383
|
+
[SendOutNotifications::SetData]
|
384
|
+
```
|
385
|
+
|
386
|
+
|
311
387
|
### Interactor wiring specs
|
312
388
|
Sometimes you have an interactor chain that fails because something is expected deeper down the chain and not provided further up the chain.
|
313
389
|
The existing way to solve this is with enough integration specs to catch them, hunting and sticking a `byebug`, `debugger` or `binding.pry` in at suspected locations and inferring where in the chain the wiring went awry.
|
@@ -11,5 +11,13 @@ module Interactify
|
|
11
11
|
def fallback
|
12
12
|
Rails.root / "app" if Interactify.railties?
|
13
13
|
end
|
14
|
+
|
15
|
+
def trigger_definition_error(error)
|
16
|
+
@on_definition_error&.call(error)
|
17
|
+
end
|
18
|
+
|
19
|
+
def on_definition_error(handler = nil, &block)
|
20
|
+
@on_definition_error = block_given? ? block : handler
|
21
|
+
end
|
14
22
|
end
|
15
23
|
end
|
@@ -4,6 +4,8 @@ require "interactify/async/jobable"
|
|
4
4
|
require "interactify/contracts/call_wrapper"
|
5
5
|
require "interactify/contracts/failure"
|
6
6
|
require "interactify/contracts/setup"
|
7
|
+
require "interactify/contracts/promising"
|
8
|
+
require "interactify/contracts/organizing"
|
7
9
|
require "interactify/dsl/organizer"
|
8
10
|
|
9
11
|
module Interactify
|
@@ -11,6 +13,7 @@ module Interactify
|
|
11
13
|
module Helpers
|
12
14
|
extend ActiveSupport::Concern
|
13
15
|
|
16
|
+
# rubocop: disable Metrics/BlockLength
|
14
17
|
class_methods do
|
15
18
|
def expect(*attrs, filled: true)
|
16
19
|
Setup.expects(context: self, attrs:, filled:)
|
@@ -24,6 +27,10 @@ module Interactify
|
|
24
27
|
Promising.validate(self, *args)
|
25
28
|
end
|
26
29
|
|
30
|
+
def organizing(*args)
|
31
|
+
Organizing.validate(self, *args)
|
32
|
+
end
|
33
|
+
|
27
34
|
def promised_keys
|
28
35
|
_interactify_extract_keys(contract.promises)
|
29
36
|
end
|
@@ -50,6 +57,7 @@ module Interactify
|
|
50
57
|
clauses.instance_eval { @terms }.json&.rules&.keys
|
51
58
|
end
|
52
59
|
end
|
60
|
+
# rubocop: enable Metrics/BlockLength
|
53
61
|
|
54
62
|
included do
|
55
63
|
c = Class.new(Contracts::Failure)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "interactify/contracts/failure"
|
4
|
+
|
5
|
+
module Interactify
|
6
|
+
module Contracts
|
7
|
+
class MismatchingOrganizerError < Contracts::Failure
|
8
|
+
def initialize(interactor, organizing, organized_klasses)
|
9
|
+
@interactor = interactor
|
10
|
+
@organizing = organizing
|
11
|
+
@organized_klasses = organized_klasses
|
12
|
+
|
13
|
+
super formatted_message
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :interactor, :organizing, :organized_klasses
|
19
|
+
|
20
|
+
def formatted_message
|
21
|
+
<<~MESSAGE.chomp.strip
|
22
|
+
#{interactor} does not organize:
|
23
|
+
#{organizing.inspect}
|
24
|
+
|
25
|
+
Actual organized classes are:
|
26
|
+
#{organized_klasses.inspect}
|
27
|
+
|
28
|
+
#{missing_and_extra_message}
|
29
|
+
MESSAGE
|
30
|
+
end
|
31
|
+
|
32
|
+
def extra = organizing - organized_klasses
|
33
|
+
def missing = organized_klasses - organizing
|
34
|
+
def missing_message = missing.none? ? nil : "Missing classes are:\n#{missing.inspect}"
|
35
|
+
def extra_message = extra.none? ? nil : "Extra classes are:\n#{extra.inspect}"
|
36
|
+
|
37
|
+
def missing_and_extra_message = [missing_message, extra_message].compact.join("\n\n")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "interactify/contracts/mismatching_organizer_error"
|
4
|
+
|
5
|
+
module Interactify
|
6
|
+
module Contracts
|
7
|
+
class Organizing
|
8
|
+
attr_reader :interactor, :organizing
|
9
|
+
|
10
|
+
def self.validate(interactor, *organizing)
|
11
|
+
new(interactor, *organizing).validate
|
12
|
+
|
13
|
+
interactor
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(interactor, *organizing)
|
17
|
+
@interactor = interactor
|
18
|
+
@organizing = organizing
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate
|
22
|
+
return if organizing == organized
|
23
|
+
|
24
|
+
Interactify.trigger_definition_error(
|
25
|
+
MismatchingOrganizerError.new(interactor, organizing, organized)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
delegate :organized, to: :interactor
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -5,17 +5,23 @@ require "interactify/dsl/unique_klass_name"
|
|
5
5
|
module Interactify
|
6
6
|
module Dsl
|
7
7
|
class EachChain
|
8
|
-
|
8
|
+
MissingIteratableValueInContext = Class.new(ArgumentError)
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
attr_reader :each_loop_klasses, :plural_resource_name, :evaluating_receiver, :caller_info
|
11
|
+
|
12
|
+
def self.attach_klass(evaluating_receiver,
|
13
|
+
*each_loop_klasses,
|
14
|
+
plural_resource_name:,
|
15
|
+
caller_info:)
|
16
|
+
iteratable = new(each_loop_klasses, plural_resource_name, evaluating_receiver, caller_info:)
|
12
17
|
iteratable.attach_klass
|
13
18
|
end
|
14
19
|
|
15
|
-
def initialize(each_loop_klasses, plural_resource_name, evaluating_receiver)
|
20
|
+
def initialize(each_loop_klasses, plural_resource_name, evaluating_receiver, caller_info:)
|
16
21
|
@each_loop_klasses = each_loop_klasses
|
17
22
|
@plural_resource_name = plural_resource_name
|
18
23
|
@evaluating_receiver = evaluating_receiver
|
24
|
+
@caller_info = caller_info
|
19
25
|
end
|
20
26
|
|
21
27
|
# allows us to dynamically create an interactor chain
|
@@ -33,10 +39,17 @@ module Interactify
|
|
33
39
|
end # end
|
34
40
|
|
35
41
|
define_singleton_method(:source_location) do # def self.source_location
|
36
|
-
|
42
|
+
file, line = this.caller_info&.split(':')
|
43
|
+
return const_source_location(this.evaluating_receiver.to_s) if file.nil?
|
44
|
+
|
45
|
+
[file, line&.to_i]
|
37
46
|
end # end
|
38
47
|
|
39
48
|
define_method(:run!) do # def run!
|
49
|
+
resources = context.send(this.plural_resource_name) # packages = context.packages
|
50
|
+
|
51
|
+
bail_with_error(resources) unless resources.respond_to?(:each_with_index) # raise MissingIteratableValueInContext unless packages.respond_to?(:each_with_index)
|
52
|
+
|
40
53
|
context.send(this.plural_resource_name).each_with_index do |resource, index|# context.packages.each_with_index do |package, index|
|
41
54
|
context[this.singular_resource_name] = resource # context.package = package
|
42
55
|
context[this.singular_resource_index_name] = index # context.package_index = index
|
@@ -52,6 +65,11 @@ module Interactify
|
|
52
65
|
context # context
|
53
66
|
end # end
|
54
67
|
|
68
|
+
define_method(:bail_with_error) do |resources|
|
69
|
+
message = "Expected `context.#{this.plural_resource_name}`: #{resources.inspect}\nto respond to :each_with_index"
|
70
|
+
raise MissingIteratableValueInContext, message
|
71
|
+
end
|
72
|
+
|
55
73
|
define_singleton_method(:klasses) do # def self.klasses
|
56
74
|
klasses = instance_variable_get(:@klasses) # @klasses ||= Wrapper.wrap_many(self, [A, B, C])
|
57
75
|
return klasses if klasses
|
@@ -6,18 +6,19 @@ require "interactify/dsl/if_klass"
|
|
6
6
|
module Interactify
|
7
7
|
module Dsl
|
8
8
|
class IfInteractor
|
9
|
-
attr_reader :condition, :evaluating_receiver
|
9
|
+
attr_reader :condition, :evaluating_receiver, :caller_info
|
10
10
|
|
11
|
-
def self.attach_klass(evaluating_receiver, condition, succcess_interactor, failure_interactor)
|
12
|
-
ifable = new(evaluating_receiver, condition, succcess_interactor, failure_interactor)
|
11
|
+
def self.attach_klass(evaluating_receiver, condition, succcess_interactor, failure_interactor, caller_info:)
|
12
|
+
ifable = new(evaluating_receiver, condition, succcess_interactor, failure_interactor, caller_info:)
|
13
13
|
ifable.attach_klass
|
14
14
|
end
|
15
15
|
|
16
|
-
def initialize(evaluating_receiver, condition, succcess_arg, failure_arg)
|
16
|
+
def initialize(evaluating_receiver, condition, succcess_arg, failure_arg, caller_info:)
|
17
17
|
@evaluating_receiver = evaluating_receiver
|
18
18
|
@condition = condition
|
19
19
|
@success_arg = succcess_arg
|
20
20
|
@failure_arg = failure_arg
|
21
|
+
@caller_info = caller_info
|
21
22
|
end
|
22
23
|
|
23
24
|
def success_interactor
|
@@ -71,7 +72,7 @@ module Interactify
|
|
71
72
|
case arg
|
72
73
|
when Array
|
73
74
|
name = "If#{condition.to_s.camelize}#{truthiness ? 'IsTruthy' : 'IsFalsey'}"
|
74
|
-
klass_basis.chain(name, *arg)
|
75
|
+
klass_basis.chain(name, *arg, caller_info:)
|
75
76
|
else
|
76
77
|
arg
|
77
78
|
end
|
@@ -19,7 +19,7 @@ module Interactify
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def run!(context)
|
22
|
-
result =
|
22
|
+
result = invoke_callable(context)
|
23
23
|
|
24
24
|
interactor = result ? success_interactor : failure_interactor
|
25
25
|
interactor.respond_to?(:call!) ? interactor.call!(context) : interactor&.call(context)
|
@@ -27,6 +27,42 @@ module Interactify
|
|
27
27
|
|
28
28
|
private
|
29
29
|
|
30
|
+
def invoke_callable(context)
|
31
|
+
return handle_string_or_symbol(context) if string_or_symbol_condition?
|
32
|
+
return handle_interactor_subclass(context) if interactor_subclass_condition?
|
33
|
+
return handle_proc_or_class(context) if proc_or_class_condition?
|
34
|
+
|
35
|
+
raise_unknown_condition_error
|
36
|
+
end
|
37
|
+
|
38
|
+
def string_or_symbol_condition?
|
39
|
+
condition.class.in?([String, Symbol])
|
40
|
+
end
|
41
|
+
|
42
|
+
def handle_string_or_symbol(context)
|
43
|
+
context.send(condition)
|
44
|
+
end
|
45
|
+
|
46
|
+
def interactor_subclass_condition?
|
47
|
+
condition.is_a?(Class) && condition < Interactor
|
48
|
+
end
|
49
|
+
|
50
|
+
def handle_interactor_subclass(context)
|
51
|
+
condition.new(context).call
|
52
|
+
end
|
53
|
+
|
54
|
+
def proc_or_class_condition?
|
55
|
+
condition.is_a?(Proc) || condition.is_a?(Class)
|
56
|
+
end
|
57
|
+
|
58
|
+
def handle_proc_or_class(context)
|
59
|
+
condition.call(context)
|
60
|
+
end
|
61
|
+
|
62
|
+
def raise_unknown_condition_error
|
63
|
+
raise "Unknown condition: #{condition.inspect}"
|
64
|
+
end
|
65
|
+
|
30
66
|
def attach_source_location
|
31
67
|
attach do |_klass, this|
|
32
68
|
define_singleton_method(:source_location) do # def self.source_location
|
@@ -38,7 +74,7 @@ module Interactify
|
|
38
74
|
def attach_expectations
|
39
75
|
attach do |klass, this|
|
40
76
|
klass.expects do
|
41
|
-
required(this.condition)
|
77
|
+
required(this.condition.to_sym) if this.condition.class.in?([String, Symbol])
|
42
78
|
end
|
43
79
|
end
|
44
80
|
end
|
@@ -5,7 +5,7 @@ module Interactify
|
|
5
5
|
module UniqueKlassName
|
6
6
|
def self.for(namespace, prefix)
|
7
7
|
id = generate_unique_id
|
8
|
-
klass_name = :"#{prefix}#{id}"
|
8
|
+
klass_name = :"#{prefix.to_s.camelize.gsub("::", "__")}#{id}"
|
9
9
|
|
10
10
|
while namespace.const_defined?(klass_name)
|
11
11
|
id = generate_unique_id
|
@@ -29,6 +29,12 @@ module Interactify
|
|
29
29
|
when Array
|
30
30
|
wrap_chain
|
31
31
|
when Proc
|
32
|
+
wrap_proc
|
33
|
+
when Class
|
34
|
+
return interactor if interactor < Interactor
|
35
|
+
|
36
|
+
raise ArgumentError, "#{interactor} must respond_to .call" unless interactor.respond_to?(:call)
|
37
|
+
|
32
38
|
wrap_proc
|
33
39
|
else
|
34
40
|
interactor
|
data/lib/interactify/dsl.rb
CHANGED
@@ -6,6 +6,9 @@ require "interactify/dsl/unique_klass_name"
|
|
6
6
|
|
7
7
|
module Interactify
|
8
8
|
module Dsl
|
9
|
+
Error = Class.new(::ArgumentError)
|
10
|
+
IfDefinitionUnexpectedKey = Class.new(Error)
|
11
|
+
|
9
12
|
# creates a class in the attach_klass_to's namespace
|
10
13
|
# e.g.
|
11
14
|
#
|
@@ -15,25 +18,27 @@ module Interactify
|
|
15
18
|
# will create a class called Orders::EachPackage, that
|
16
19
|
# will call the interactor chain A, B, C for each package in the context
|
17
20
|
def each(plural_resource_name, *each_loop_klasses)
|
21
|
+
caller_info = caller(1..1).first
|
22
|
+
|
18
23
|
EachChain.attach_klass(
|
19
24
|
self,
|
20
|
-
|
21
|
-
|
25
|
+
*each_loop_klasses,
|
26
|
+
caller_info:,
|
27
|
+
plural_resource_name:
|
22
28
|
)
|
23
29
|
end
|
24
30
|
|
25
31
|
def if(condition, success_arg, failure_arg = nil)
|
26
|
-
then_else =
|
27
|
-
|
28
|
-
|
29
|
-
{ then: success_arg, else: failure_arg }
|
30
|
-
end
|
32
|
+
then_else = parse_if_args(condition, success_arg, failure_arg)
|
33
|
+
|
34
|
+
caller_info = caller(1..1).first
|
31
35
|
|
32
36
|
IfInteractor.attach_klass(
|
33
37
|
self,
|
34
38
|
condition,
|
35
39
|
then_else[:then],
|
36
|
-
then_else[:else]
|
40
|
+
then_else[:else],
|
41
|
+
caller_info:
|
37
42
|
)
|
38
43
|
end
|
39
44
|
|
@@ -54,7 +59,8 @@ module Interactify
|
|
54
59
|
# it will attach the generate class to the currenct class and
|
55
60
|
# use the class name passed in
|
56
61
|
# rubocop:disable all
|
57
|
-
def chain(klass_name, *chained_klasses, expect: [])
|
62
|
+
def chain(klass_name, *chained_klasses, expect: [], caller_info: nil)
|
63
|
+
caller_info ||= caller(1..1).first
|
58
64
|
expectations = expect
|
59
65
|
|
60
66
|
klass = Class.new do # class EvaluatingNamespace::SomeClass
|
@@ -62,7 +68,7 @@ module Interactify
|
|
62
68
|
expect(*expectations) if expectations.any? # expect :foo, :bar
|
63
69
|
|
64
70
|
define_singleton_method(:source_location) do # def self.source_location
|
65
|
-
|
71
|
+
caller_info # [file, line]
|
66
72
|
end # end
|
67
73
|
|
68
74
|
organize(*chained_klasses) # organize(A, B, C)
|
@@ -73,5 +79,21 @@ module Interactify
|
|
73
79
|
klass_name = UniqueKlassName.for(where_to_attach, klass_name)
|
74
80
|
where_to_attach.const_set(klass_name, klass)
|
75
81
|
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def parse_if_args(condition, success_arg, failure_arg)
|
86
|
+
then_else = if success_arg.is_a?(Hash) && failure_arg.nil?
|
87
|
+
extra_keys = success_arg.except(:then, :else)
|
88
|
+
|
89
|
+
if extra_keys.any?
|
90
|
+
raise IfDefinitionUnexpectedKey, "Unexpected keys: #{extra_keys.keys.join(", ")}"
|
91
|
+
end
|
92
|
+
|
93
|
+
success_arg.slice(:then, :else)
|
94
|
+
else
|
95
|
+
{ then: success_arg, else: failure_arg }
|
96
|
+
end
|
97
|
+
end
|
76
98
|
end
|
77
99
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "interactify/dsl/wrapper"
|
4
|
+
|
5
|
+
# rubocop: disable Naming/MethodName
|
6
|
+
def Interactify(method_callable = nil, &block)
|
7
|
+
to_wrap = method_callable || block
|
8
|
+
|
9
|
+
Interactify::Dsl::Wrapper.wrap(self, to_wrap)
|
10
|
+
end
|
11
|
+
# rubocop: enable Naming/MethodName
|
data/lib/interactify/version.rb
CHANGED
data/lib/interactify.rb
CHANGED
@@ -10,38 +10,43 @@ require "interactify/contracts/promising"
|
|
10
10
|
require "interactify/dsl"
|
11
11
|
require "interactify/wiring"
|
12
12
|
require "interactify/configuration"
|
13
|
+
require "interactify/interactify_callable"
|
13
14
|
|
14
15
|
module Interactify
|
15
|
-
|
16
|
-
|
17
|
-
end
|
16
|
+
class << self
|
17
|
+
delegate :on_definition_error, :trigger_definition_error, to: :configuration
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
def railties_missing?
|
20
|
+
@railties_missing
|
21
|
+
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
def railties_missing!
|
24
|
+
@railties_missing = true
|
25
|
+
end
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
def railties
|
28
|
+
railties?
|
29
|
+
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
def railties?
|
32
|
+
!railties_missing?
|
33
|
+
end
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
def sidekiq_missing?
|
36
|
+
@sidekiq_missing
|
37
|
+
end
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
def sidekiq_missing!
|
40
|
+
@sidekiq_missing = true
|
41
|
+
end
|
42
|
+
|
43
|
+
def sidekiq
|
44
|
+
sidekiq?
|
45
|
+
end
|
42
46
|
|
43
|
-
|
44
|
-
|
47
|
+
def sidekiq?
|
48
|
+
!sidekiq_missing?
|
49
|
+
end
|
45
50
|
end
|
46
51
|
end
|
47
52
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: interactify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Burns
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -96,7 +96,9 @@ files:
|
|
96
96
|
- lib/interactify/contracts/call_wrapper.rb
|
97
97
|
- lib/interactify/contracts/failure.rb
|
98
98
|
- lib/interactify/contracts/helpers.rb
|
99
|
+
- lib/interactify/contracts/mismatching_organizer_error.rb
|
99
100
|
- lib/interactify/contracts/mismatching_promise_error.rb
|
101
|
+
- lib/interactify/contracts/organizing.rb
|
100
102
|
- lib/interactify/contracts/promising.rb
|
101
103
|
- lib/interactify/contracts/setup.rb
|
102
104
|
- lib/interactify/dsl.rb
|
@@ -106,6 +108,7 @@ files:
|
|
106
108
|
- lib/interactify/dsl/organizer.rb
|
107
109
|
- lib/interactify/dsl/unique_klass_name.rb
|
108
110
|
- lib/interactify/dsl/wrapper.rb
|
111
|
+
- lib/interactify/interactify_callable.rb
|
109
112
|
- lib/interactify/rspec_matchers/matchers.rb
|
110
113
|
- lib/interactify/version.rb
|
111
114
|
- lib/interactify/wiring.rb
|