interactify 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Gem Version](https://badge.fury.io/rb/interactify.svg)](https://badge.fury.io/rb/interactify)
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/interactify.svg?1704002847)](https://badge.fury.io/rb/interactify)
|
4
4
|
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
|
5
5
|
![Ruby 3.3.0](https://img.shields.io/badge/ruby-3.3.0-green.svg)
|
6
6
|
![Ruby 3.2.2](https://img.shields.io/badge/ruby-3.2.2-green.svg)
|
@@ -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
|