grumlin 0.22.4 → 1.0.0.rc1

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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.rubocop.yml +9 -9
  4. data/Gemfile.lock +10 -8
  5. data/README.md +102 -141
  6. data/Rakefile +1 -1
  7. data/bin/console +18 -3
  8. data/doc/middlewares.md +97 -0
  9. data/grumlin.gemspec +1 -0
  10. data/lib/async/channel.rb +54 -56
  11. data/lib/grumlin/benchmark/repository.rb +10 -14
  12. data/lib/grumlin/client.rb +92 -112
  13. data/lib/grumlin/config.rb +30 -15
  14. data/lib/grumlin/dummy_transaction.rb +13 -15
  15. data/lib/grumlin/edge.rb +18 -20
  16. data/lib/grumlin/expressions/cardinality.rb +5 -9
  17. data/lib/grumlin/expressions/column.rb +5 -9
  18. data/lib/grumlin/expressions/expression.rb +7 -11
  19. data/lib/grumlin/expressions/operator.rb +5 -9
  20. data/lib/grumlin/expressions/order.rb +5 -9
  21. data/lib/grumlin/expressions/p.rb +27 -31
  22. data/lib/grumlin/expressions/pop.rb +5 -9
  23. data/lib/grumlin/expressions/scope.rb +5 -9
  24. data/lib/grumlin/expressions/t.rb +5 -9
  25. data/lib/grumlin/expressions/text_p.rb +5 -9
  26. data/lib/grumlin/expressions/with_options.rb +17 -21
  27. data/lib/grumlin/features/feature_list.rb +8 -12
  28. data/lib/grumlin/features/neptune_features.rb +5 -9
  29. data/lib/grumlin/features/tinkergraph_features.rb +5 -9
  30. data/lib/grumlin/features.rb +8 -10
  31. data/lib/grumlin/middlewares/apply_shortcuts.rb +8 -0
  32. data/lib/grumlin/middlewares/build_query.rb +20 -0
  33. data/lib/grumlin/middlewares/builder.rb +15 -0
  34. data/lib/grumlin/middlewares/cast_results.rb +7 -0
  35. data/lib/grumlin/middlewares/find_blocklisted_steps.rb +14 -0
  36. data/lib/grumlin/middlewares/find_mutating_steps.rb +9 -0
  37. data/lib/grumlin/middlewares/middleware.rb +11 -0
  38. data/lib/grumlin/middlewares/run_query.rb +7 -0
  39. data/lib/grumlin/middlewares/serialize_to_bytecode.rb +9 -0
  40. data/lib/grumlin/middlewares/serialize_to_steps.rb +8 -0
  41. data/lib/grumlin/path.rb +11 -13
  42. data/lib/grumlin/property.rb +14 -16
  43. data/lib/grumlin/query_validators/blocklisted_steps_validator.rb +22 -0
  44. data/lib/grumlin/query_validators/validator.rb +36 -0
  45. data/lib/grumlin/repository/error_handling_strategy.rb +36 -40
  46. data/lib/grumlin/repository/instance_methods.rb +115 -118
  47. data/lib/grumlin/repository.rb +82 -58
  48. data/lib/grumlin/request_dispatcher.rb +55 -57
  49. data/lib/grumlin/request_error_factory.rb +53 -55
  50. data/lib/grumlin/shortcut.rb +19 -21
  51. data/lib/grumlin/shortcuts/properties.rb +12 -16
  52. data/lib/grumlin/shortcuts/storage.rb +67 -74
  53. data/lib/grumlin/shortcuts/upserts.rb +18 -22
  54. data/lib/grumlin/shortcuts.rb +23 -25
  55. data/lib/grumlin/shortcuts_applyer.rb +27 -29
  56. data/lib/grumlin/step.rb +92 -0
  57. data/lib/grumlin/step_data.rb +12 -14
  58. data/lib/grumlin/steppable.rb +24 -22
  59. data/lib/grumlin/steps.rb +51 -54
  60. data/lib/grumlin/steps_serializers/bytecode.rb +53 -56
  61. data/lib/grumlin/steps_serializers/human_readable_bytecode.rb +17 -21
  62. data/lib/grumlin/steps_serializers/serializer.rb +7 -11
  63. data/lib/grumlin/steps_serializers/string.rb +26 -30
  64. data/lib/grumlin/test/rspec/db_cleaner_context.rb +8 -12
  65. data/lib/grumlin/test/rspec/gremlin_context.rb +18 -16
  66. data/lib/grumlin/test/rspec.rb +1 -5
  67. data/lib/grumlin/transaction.rb +26 -27
  68. data/lib/grumlin/transport.rb +71 -73
  69. data/lib/grumlin/traversal_start.rb +31 -33
  70. data/lib/grumlin/traversal_strategies/options_strategy.rb +3 -7
  71. data/lib/grumlin/traverser.rb +5 -7
  72. data/lib/grumlin/typed_value.rb +11 -13
  73. data/lib/grumlin/typing.rb +70 -72
  74. data/lib/grumlin/version.rb +1 -1
  75. data/lib/grumlin/vertex.rb +14 -16
  76. data/lib/grumlin/vertex_property.rb +14 -16
  77. data/lib/grumlin/with_extension.rb +17 -19
  78. data/lib/grumlin.rb +23 -19
  79. metadata +32 -6
  80. data/lib/grumlin/action.rb +0 -92
  81. data/lib/grumlin/sugar.rb +0 -15
@@ -1,101 +1,99 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Grumlin
4
- class Transport
5
- # A transport based on https://github.com/socketry/async
6
- # and https://github.com/socketry/async-websocket
7
-
8
- include Console
9
-
10
- attr_reader :url
11
-
12
- # Transport is not reusable. Once closed should be recreated.
13
- def initialize(url, parent: Async::Task.current, **client_options)
14
- @url = url
15
- @parent = parent
16
- @client_options = client_options
17
- @request_channel = Async::Channel.new
18
- @response_channel = Async::Channel.new
19
- end
3
+ class Grumlin::Transport
4
+ # A transport based on https://github.com/socketry/async
5
+ # and https://github.com/socketry/async-websocket
20
6
 
21
- def connected?
22
- !@connection.nil?
23
- end
7
+ include Console
24
8
 
25
- def connect
26
- raise ClientClosedError if @closed
27
- raise AlreadyConnectedError if connected?
9
+ attr_reader :url
28
10
 
29
- @connection = Async::WebSocket::Client.connect(Async::HTTP::Endpoint.parse(@url), **@client_options)
30
- logger.debug(self) { "Connected to #{@url}." }
11
+ # Transport is not reusable. Once closed should be recreated.
12
+ def initialize(url, parent: Async::Task.current, **client_options)
13
+ @url = url
14
+ @parent = parent
15
+ @client_options = client_options
16
+ @request_channel = Async::Channel.new
17
+ @response_channel = Async::Channel.new
18
+ end
31
19
 
32
- @response_task = @parent.async { run_response_task }
20
+ def connected?
21
+ !@connection.nil?
22
+ end
33
23
 
34
- @request_task = @parent.async { run_request_task }
24
+ def connect
25
+ raise ClientClosedError if @closed
26
+ raise AlreadyConnectedError if connected?
35
27
 
36
- @response_channel
37
- end
28
+ @connection = Async::WebSocket::Client.connect(Async::HTTP::Endpoint.parse(@url), **@client_options)
29
+ logger.debug(self) { "Connected to #{@url}." }
38
30
 
39
- def write(message)
40
- raise NotConnectedError unless connected?
31
+ @response_task = @parent.async { run_response_task }
41
32
 
42
- @request_channel << message
43
- end
33
+ @request_task = @parent.async { run_request_task }
44
34
 
45
- def close
46
- return if @closed
35
+ @response_channel
36
+ end
47
37
 
48
- @closed = true
38
+ def write(message)
39
+ raise NotConnectedError unless connected?
49
40
 
50
- @request_channel.close
51
- @response_channel.close
41
+ @request_channel << message
42
+ end
52
43
 
53
- begin
54
- @connection.close
55
- rescue StandardError
56
- nil
57
- end
58
- @connection = nil
44
+ def close
45
+ return if @closed
59
46
 
60
- @request_task&.stop(true)
61
- @response_task&.stop(true)
62
- end
47
+ @closed = true
48
+
49
+ @request_channel.close
50
+ @response_channel.close
63
51
 
64
- def wait
65
- @request_task.wait
66
- @response_task.wait
52
+ begin
53
+ @connection.close
54
+ rescue StandardError
55
+ nil
67
56
  end
57
+ @connection = nil
68
58
 
69
- private
59
+ @request_task&.stop(true)
60
+ @response_task&.stop(true)
61
+ end
62
+
63
+ def wait
64
+ @request_task.wait
65
+ @response_task.wait
66
+ end
70
67
 
71
- def run_response_task
72
- with_guard do
73
- loop do
74
- data = @connection.read
75
- @response_channel << data
76
- end
68
+ private
69
+
70
+ def run_response_task
71
+ with_guard do
72
+ loop do
73
+ data = @connection.read
74
+ @response_channel << data
77
75
  end
78
76
  end
77
+ end
79
78
 
80
- def run_request_task
81
- with_guard do
82
- @request_channel.each do |message|
83
- @connection.write(message)
84
- @connection.flush
85
- end
79
+ def run_request_task
80
+ with_guard do
81
+ @request_channel.each do |message|
82
+ @connection.write(message)
83
+ @connection.flush
86
84
  end
87
85
  end
86
+ end
88
87
 
89
- def with_guard
90
- yield
91
- rescue Async::Stop, Async::TimeoutError, StandardError => e
92
- logger.debug(self) { "Guard error, closing." }
93
- begin
94
- @response_channel.exception(e)
95
- rescue Async::Channel::ChannelClosedError
96
- nil
97
- end
98
- close
88
+ def with_guard
89
+ yield
90
+ rescue Async::Stop, Async::TimeoutError, StandardError => e
91
+ logger.debug(self) { "Guard error, closing." }
92
+ begin
93
+ @response_channel.exception(e)
94
+ rescue Async::Channel::ChannelClosedError
95
+ nil
99
96
  end
97
+ close
100
98
  end
101
99
  end
@@ -1,42 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Grumlin
4
- class TraversalStart < Steppable
5
- include WithExtension
6
-
7
- class TraversalError < Grumlin::Error; end
8
- class AlreadyBoundToTransactionError < TraversalError; end
9
-
10
- def tx
11
- raise AlreadyBoundToTransactionError if @session_id
12
-
13
- transaction = tx_class.new(self.class, pool: @pool)
14
- return transaction unless block_given?
15
-
16
- begin
17
- yield transaction.begin
18
- rescue Grumlin::Rollback
19
- transaction.rollback
20
- rescue StandardError
21
- transaction.rollback
22
- raise
23
- else
24
- transaction.commit
25
- end
3
+ class Grumlin::TraversalStart < Grumlin::Steppable
4
+ include Grumlin::WithExtension
5
+
6
+ class TraversalError < Grumlin::Error; end
7
+ class AlreadyBoundToTransactionError < TraversalError; end
8
+
9
+ def tx
10
+ raise AlreadyBoundToTransactionError if @session_id
11
+
12
+ transaction = tx_class.new(self.class, pool: @pool, middlewares: @middlewares)
13
+ return transaction unless block_given?
14
+
15
+ begin
16
+ yield transaction.begin
17
+ rescue Grumlin::Rollback
18
+ transaction.rollback
19
+ rescue StandardError
20
+ transaction.rollback
21
+ raise
22
+ else
23
+ transaction.commit
26
24
  end
25
+ end
27
26
 
28
- def to_s(*)
29
- self.class.to_s
30
- end
27
+ def to_s(*)
28
+ self.class.to_s
29
+ end
31
30
 
32
- def inspect
33
- self.class.inspect
34
- end
31
+ def inspect
32
+ self.class.inspect
33
+ end
35
34
 
36
- private
35
+ private
37
36
 
38
- def tx_class
39
- Grumlin.features.supports_transactions? ? Transaction : DummyTransaction
40
- end
37
+ def tx_class
38
+ Grumlin.features.supports_transactions? ? Grumlin::Transaction : Grumlin::DummyTransaction
41
39
  end
42
40
  end
@@ -1,11 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Grumlin
4
- module TraversalStrategies
5
- class OptionsStrategy < TypedValue
6
- def initialize(value)
7
- super(type: "OptionsStrategy", value: value)
8
- end
9
- end
3
+ class Grumlin::TraversalStrategies::OptionsStrategy < Grumlin::TypedValue
4
+ def initialize(value)
5
+ super(type: "OptionsStrategy", value: value)
10
6
  end
11
7
  end
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Grumlin
4
- class Traverser
5
- attr_reader :bulk, :value
3
+ class Grumlin::Traverser
4
+ attr_reader :bulk, :value
6
5
 
7
- def initialize(value)
8
- @bulk = value.dig(:bulk, :@value) || 1
9
- @value = Typing.cast(value[:value])
10
- end
6
+ def initialize(value)
7
+ @bulk = value.dig(:bulk, :@value) || 1
8
+ @value = Grumlin::Typing.cast(value[:value])
11
9
  end
12
10
  end
@@ -1,20 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Grumlin
4
- class TypedValue
5
- attr_reader :type, :value
3
+ class Grumlin::TypedValue
4
+ attr_reader :type, :value
6
5
 
7
- def initialize(type: nil, value: nil)
8
- @type = type
9
- @value = value
10
- end
6
+ def initialize(type: nil, value: nil)
7
+ @type = type
8
+ @value = value
9
+ end
11
10
 
12
- def inspect
13
- "<#{type}.#{value}>"
14
- end
11
+ def inspect
12
+ "<#{type}.#{value}>"
13
+ end
15
14
 
16
- def to_s
17
- inspect
18
- end
15
+ def to_s
16
+ inspect
19
17
  end
20
18
  end
@@ -1,89 +1,87 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Grumlin
4
- module Typing
5
- TYPES = {
6
- "g:List" => ->(value) { cast_list(value) },
7
- "g:Set" => ->(value) { cast_list(value).to_set },
8
- "g:Map" => ->(value) { cast_map(value) },
9
- "g:Vertex" => ->(value) { cast_entity(Grumlin::Vertex, value) },
10
- "g:Edge" => ->(value) { cast_entity(Grumlin::Edge, value) },
11
- "g:Path" => ->(value) { cast_entity(Grumlin::Path, value) },
12
- "g:Traverser" => ->(value) { cast_entity(Grumlin::Traverser, value) },
13
- "g:Property" => ->(value) { cast_entity(Grumlin::Property, value) },
14
- "g:Int64" => ->(value) { cast_int(value) },
15
- "g:Int32" => ->(value) { cast_int(value) },
16
- "g:Double" => ->(value) { cast_double(value) },
17
- "g:Direction" => ->(value) { value },
18
- "g:VertexProperty" => ->(value) { cast_entity(Grumlin::VertexProperty, value) },
19
- "g:TraversalMetrics" => ->(value) { cast_map(value[:@value]) },
20
- "g:Metrics" => ->(value) { cast_map(value[:@value]) },
21
- "g:T" => ->(value) { Grumlin::Expressions::T.public_send(value) }
22
- }.freeze
23
-
24
- CASTABLE_TYPES = [Hash, String, Integer, TrueClass, FalseClass, NilClass].freeze
25
-
26
- class << self
27
- def cast(value)
28
- verify_type!(value)
29
-
30
- return value unless value.is_a?(Hash)
31
-
32
- type = TYPES[value[:@type]]
33
-
34
- verify_castable_hash!(value, type)
35
-
36
- type.call(value[:@value])
37
- end
3
+ module Grumlin::Typing
4
+ TYPES = {
5
+ "g:List" => ->(value) { cast_list(value) },
6
+ "g:Set" => ->(value) { cast_list(value).to_set },
7
+ "g:Map" => ->(value) { cast_map(value) },
8
+ "g:Vertex" => ->(value) { cast_entity(Grumlin::Vertex, value) },
9
+ "g:Edge" => ->(value) { cast_entity(Grumlin::Edge, value) },
10
+ "g:Path" => ->(value) { cast_entity(Grumlin::Path, value) },
11
+ "g:Traverser" => ->(value) { cast_entity(Grumlin::Traverser, value) },
12
+ "g:Property" => ->(value) { cast_entity(Grumlin::Property, value) },
13
+ "g:Int64" => ->(value) { cast_int(value) },
14
+ "g:Int32" => ->(value) { cast_int(value) },
15
+ "g:Double" => ->(value) { cast_double(value) },
16
+ "g:Direction" => ->(value) { value },
17
+ "g:VertexProperty" => ->(value) { cast_entity(Grumlin::VertexProperty, value) },
18
+ "g:TraversalMetrics" => ->(value) { cast_map(value[:@value]) },
19
+ "g:Metrics" => ->(value) { cast_map(value[:@value]) },
20
+ "g:T" => ->(value) { Grumlin::Expressions::T.public_send(value) }
21
+ }.freeze
22
+
23
+ CASTABLE_TYPES = [Hash, String, Integer, TrueClass, FalseClass, NilClass].freeze
24
+
25
+ class << self
26
+ def cast(value)
27
+ verify_type!(value)
28
+
29
+ return value unless value.is_a?(Hash)
30
+
31
+ type = TYPES[value[:@type]]
32
+
33
+ verify_castable_hash!(value, type)
34
+
35
+ type.call(value[:@value])
36
+ end
38
37
 
39
- private
38
+ private
40
39
 
41
- def verify_type!(value)
42
- raise TypeError, "#{value.inspect} cannot be casted" unless CASTABLE_TYPES.include?(value.class)
43
- end
40
+ def verify_type!(value)
41
+ raise TypeError, "#{value.inspect} cannot be casted" unless CASTABLE_TYPES.include?(value.class)
42
+ end
44
43
 
45
- def verify_castable_hash!(value, type)
46
- raise TypeError, "#{value} cannot be casted, @type is missing" if value[:@type].nil?
47
- raise(UnknownTypeError, value[:@type]) if type.nil?
48
- raise TypeError, "#{value} cannot be casted, @value is missing" if value[:@value].nil?
49
- end
44
+ def verify_castable_hash!(value, type)
45
+ raise TypeError, "#{value} cannot be casted, @type is missing" if value[:@type].nil?
46
+ raise(UnknownTypeError, value[:@type]) if type.nil?
47
+ raise TypeError, "#{value} cannot be casted, @value is missing" if value[:@value].nil?
48
+ end
50
49
 
51
- def cast_int(value)
52
- raise TypeError, "#{value} is not an Integer" unless value.is_a?(Integer)
50
+ def cast_int(value)
51
+ raise TypeError, "#{value} is not an Integer" unless value.is_a?(Integer)
53
52
 
54
- value
55
- end
53
+ value
54
+ end
56
55
 
57
- def cast_double(value)
58
- raise TypeError, "#{value} is not a Double" unless value.is_a?(Float)
56
+ def cast_double(value)
57
+ raise TypeError, "#{value} is not a Double" unless value.is_a?(Float)
59
58
 
60
- value
61
- end
59
+ value
60
+ end
62
61
 
63
- def cast_entity(entity, value)
64
- entity.new(**value)
65
- rescue ArgumentError, TypeError
66
- raise TypeError, "#{value} cannot be casted to #{entity.name}"
67
- end
62
+ def cast_entity(entity, value)
63
+ entity.new(**value)
64
+ rescue ArgumentError, TypeError
65
+ raise TypeError, "#{value} cannot be casted to #{entity.name}"
66
+ end
68
67
 
69
- def cast_map(value)
70
- Hash[*value].transform_keys do |key|
71
- next key.to_sym if key.respond_to?(:to_sym)
72
- next cast(key) if key[:@type] # TODO: g.V.group.by(:none_existing_property).next
68
+ def cast_map(value)
69
+ Hash[*value].transform_keys do |key|
70
+ next key.to_sym if key.respond_to?(:to_sym)
71
+ next cast(key) if key[:@type] # TODO: g.V.group.by(:none_existing_property).next
73
72
 
74
- raise UnknownMapKey, key, value
75
- end.transform_values { |v| cast(v) }
76
- rescue ArgumentError
77
- raise TypeError, "#{value} cannot be casted to Hash"
78
- end
73
+ raise UnknownMapKey, key, value
74
+ end.transform_values { |v| cast(v) }
75
+ rescue ArgumentError
76
+ raise TypeError, "#{value} cannot be casted to Hash"
77
+ end
79
78
 
80
- def cast_list(value)
81
- value.each_with_object([]) do |item, result|
82
- casted_value = cast(item)
83
- next (result << casted_value) unless casted_value.instance_of?(Traverser)
79
+ def cast_list(value)
80
+ value.each_with_object([]) do |item, result|
81
+ casted_value = cast(item)
82
+ next (result << casted_value) unless casted_value.instance_of?(Grumlin::Traverser)
84
83
 
85
- casted_value.bulk.times { result << casted_value.value }
86
- end
84
+ casted_value.bulk.times { result << casted_value.value }
87
85
  end
88
86
  end
89
87
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grumlin
4
- VERSION = "0.22.4"
4
+ VERSION = "1.0.0.rc1"
5
5
  end
@@ -1,24 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Grumlin
4
- class Vertex
5
- attr_reader :label, :id
3
+ class Grumlin::Vertex
4
+ attr_reader :label, :id
6
5
 
7
- def initialize(label:, id:)
8
- @label = label
9
- @id = Typing.cast(id)
10
- end
6
+ def initialize(label:, id:)
7
+ @label = label
8
+ @id = Grumlin::Typing.cast(id)
9
+ end
11
10
 
12
- def ==(other)
13
- self.class == other.class && @label == other.label && @id == other.id
14
- end
11
+ def ==(other)
12
+ self.class == other.class && @label == other.label && @id == other.id
13
+ end
15
14
 
16
- def inspect
17
- "v[#{@id}]"
18
- end
15
+ def inspect
16
+ "v[#{@id}]"
17
+ end
19
18
 
20
- def to_s
21
- inspect
22
- end
19
+ def to_s
20
+ inspect
23
21
  end
24
22
  end
@@ -1,24 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Grumlin
4
- class VertexProperty
5
- attr_reader :label, :value
3
+ class Grumlin::VertexProperty
4
+ attr_reader :label, :value
6
5
 
7
- def initialize(value)
8
- @label = value[:label]
9
- @value = Typing.cast(value[:value])
10
- end
6
+ def initialize(value)
7
+ @label = value[:label]
8
+ @value = Grumlin::Typing.cast(value[:value])
9
+ end
11
10
 
12
- def inspect
13
- "vp[#{label}->#{value}]"
14
- end
11
+ def inspect
12
+ "vp[#{label}->#{value}]"
13
+ end
15
14
 
16
- def to_s
17
- inspect
18
- end
15
+ def to_s
16
+ inspect
17
+ end
19
18
 
20
- def ==(other)
21
- self.class == other.class && @label == other.label && @value == other.value
22
- end
19
+ def ==(other)
20
+ self.class == other.class && @label == other.label && @value == other.value
23
21
  end
24
22
  end
@@ -1,27 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Grumlin
4
- module WithExtension
5
- def with(name, value)
6
- prev = self
7
- strategy = if is_a?(with_action_class)
8
- prev = previous_step
9
- TraversalStrategies::OptionsStrategy.new(args.first.value.merge(name => value))
10
- else
11
- TraversalStrategies::OptionsStrategy.new({ name => value })
12
- end
13
- with_action_class.new(:withStrategies, args: [strategy], previous_step: prev)
14
- end
3
+ module Grumlin::WithExtension
4
+ def with(name, value)
5
+ prev = self
6
+ strategy = if is_a?(with_step_class)
7
+ prev = previous_step
8
+ Grumlin::TraversalStrategies::OptionsStrategy.new(args.first.value.merge(name => value))
9
+ else
10
+ Grumlin::TraversalStrategies::OptionsStrategy.new({ name => value })
11
+ end
12
+ with_step_class.new(:withStrategies, args: [strategy], previous_step: prev)
13
+ end
15
14
 
16
- private
15
+ private
17
16
 
18
- def with_action_class
19
- @with_action_class ||= Class.new(shortcuts.action_class) do
20
- include WithExtension
17
+ def with_step_class
18
+ @with_step_class ||= Class.new(shortcuts.step_class) do
19
+ include Grumlin::WithExtension
21
20
 
22
- def with_action_class
23
- self.class
24
- end
21
+ def with_step_class
22
+ self.class
25
23
  end
26
24
  end
27
25
  end