• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pabloh / pathway / 10291457741

07 Aug 2024 08:42PM UTC coverage: 97.756% (+0.4%) from 97.329%
10291457741

Pull #48

github

web-flow
Merge 2bfa64f99 into 9fe850e95
Pull Request #48: Remove support for Ruby 2.x

79 of 80 new or added lines in 7 files covered. (98.75%)

1 existing line in 1 file now uncovered.

305 of 312 relevant lines covered (97.76%)

25.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.48
/lib/pathway.rb
1
# frozen_string_literal: true
2

3
require 'forwardable'
2✔
4
require 'dry/inflector'
2✔
5
require 'contextualizer'
2✔
6
require 'pathway/version'
2✔
7
require 'pathway/result'
2✔
8

9
module Pathway
2✔
10
  Inflector = Dry::Inflector.new
2✔
11
  class Operation
2✔
12
    class << self
2✔
13
      def plugin(name,...)
2✔
14
        require "pathway/plugins/#{Inflector.underscore(name)}" if name.is_a?(Symbol)
60✔
15

16
        plugin = name.is_a?(Module) ? name : Plugins.const_get(Inflector.camelize(name))
60✔
17

18
        self.extend plugin::ClassMethods if plugin.const_defined? :ClassMethods
60✔
19
        self.include plugin::InstanceMethods if plugin.const_defined? :InstanceMethods
60✔
20
        self::DSL.include plugin::DSLMethods if plugin.const_defined? :DSLMethods
60✔
21

22
        plugin.apply(self, ...) if plugin.respond_to?(:apply)
60✔
23
      end
24

25
      def inherited(subclass)
2✔
26
        super
60✔
27
        subclass.const_set :DSL, Class.new(self::DSL)
60✔
28
      end
29
    end
30

31
    class DSL
2✔
32
    end
33
  end
34

35
  class Error
2✔
36
    attr_reader :type, :message, :details
2✔
37

38
    singleton_class.send :attr_accessor, :default_messages
2✔
39

40
    @default_messages = {}
2✔
41

42
    def initialize(type:, message: nil, details: nil)
2✔
43
      @type    = type.to_sym
50✔
44
      @message = message || default_message_for(type)
50✔
45
      @details = details || {}
50✔
46
    end
47

48
    def deconstruct = [type, message, details]
2✔
49
    def deconstruct_keys(_) = { type:, message:, details: }
2✔
50

51
    private
2✔
52

53
    def default_message_for(type)
2✔
54
      self.class.default_messages[type] || Inflector.humanize(type)
36✔
55
    end
56
  end
57

58
  class State
2✔
59
    extend Forwardable
2✔
60
    delegate %i([] []= fetch store include? values_at deconstruct_keys) => :@hash
2✔
61

62
    def initialize(operation, values = {})
2✔
63
      @hash = operation.context.merge(values)
128✔
64
      @result_key = operation.result_key
128✔
65
    end
66

67
    def update(kargs)
2✔
68
      @hash.update(kargs)
114✔
69
      self
114✔
70
    end
71

72
    def result = @hash[@result_key]
2✔
73
    def to_hash = @hash
2✔
74

75
    def use(&bl)
2✔
76
      raise ArgumentError, 'a block must be provided' if !block_given?
30✔
77

78
      params = bl.parameters
28✔
79

80
      if !params.all? { _1 in [:block|:key|:keyreq|:keyrest, _] }
64✔
81
        raise ArgumentError, 'only keyword arguments are supported'
6✔
82
      elsif params in [*, [:keyrest, _], *]
22✔
83
        bl.call(**to_hash)
6✔
84
      else
85
        keys = params.select { _1 in [:key|:keyreq, _] }.map(&:last)
36✔
86
        bl.call(**to_hash.slice(*keys))
16✔
87
      end
88
    end
89

90
    alias_method :to_h, :to_hash
2✔
91
    alias_method :u, :use
2✔
92
    alias_method :unwrap, :use
2✔
93
  end
94

95
  module Plugins
2✔
96
    module Base
2✔
97
      module ClassMethods
2✔
98
        attr_accessor :result_key
2✔
99

100
        alias_method :result_at, :result_key=
2✔
101

102
        def process(&bl)
2✔
103
          dsl = self::DSL
38✔
104
          define_method(:call) do |input|
38✔
105
            dsl.new(State.new(self, input:), self)
94✔
106
               .run(&bl)
107
               .then(&:result)
108
          end
109
        end
110

111
        def call(ctx,...) = new(ctx).call(...)
2✔
112

113
        def inherited(subclass)
2✔
114
          super
60✔
115
          subclass.result_key = result_key
60✔
116
        end
117
      end
118

119
      module InstanceMethods
2✔
120
        extend Forwardable
2✔
121

122
        delegate :result_key => 'self.class'
2✔
123
        delegate %i[result success failure] => Result
2✔
124

125
        alias_method :wrap, :result
2✔
126

127
        def call(*) = raise 'must implement at subclass'
2✔
128

129
        def error(type, message: nil, details: nil)
2✔
130
          failure(Error.new(type:, message:, details:))
32✔
131
        end
132

133
        def wrap_if_present(value, type: :not_found, message: nil, details: {})
2✔
134
          value.nil? ? error(type, message:, details:) : success(value)
76✔
135
        end
136
      end
137

138
      def self.apply(klass)
2✔
139
        klass.extend Contextualizer
2✔
140
        klass.result_key = :value
2✔
141
      end
142

143
      module DSLMethods
2✔
144
        def initialize(state, operation)
2✔
145
          @result, @operation = wrap(state), operation
102✔
146
        end
147

148
        def run(&bl)
2✔
149
          instance_eval(&bl)
140✔
150
          @result
124✔
151
        end
152

153
        # Execute step and preserve the former state
154
        def step(callable,...)
2✔
155
          bl = _callable(callable)
134✔
156
          @result = @result.tee { |state| bl.call(state,...) }
260✔
157
        end
158

159
        # Execute step and modify the former state setting the key
160
        def set(callable, *args, to: @operation.result_key, **kwargs)
2✔
161
          bl = _callable(callable)
88✔
162

163
          @result = @result.then do |state|
88✔
164
            wrap(bl.call(state, *args, **kwargs))
76✔
165
              .then { |value| state.update(to => value) }
72✔
166
          end
167
        end
168

169
        # Execute step and replace the current state completely
170
        def map(callable,...)
2✔
UNCOV
171
          bl = _callable(callable)
×
NEW
172
          @result = @result.then { |state| bl.call(state,...) }
×
173
        end
174

175
        def around(execution_strategy, &dsl_block)
2✔
176
          @result.then do |state|
154✔
177
            dsl_runner = ->(dsl = self) { @result = dsl.run(&dsl_block) }
178✔
178

179
            _callable(execution_strategy).call(dsl_runner, state)
132✔
180
          end
181
        end
182

183
        def if_true(cond, &dsl_block)
2✔
184
          cond = _callable(cond)
56✔
185
          around(->(dsl_runner, state) { dsl_runner.call if cond.call(state) }, &dsl_block)
104✔
186
        end
187

188
        def if_false(cond, &dsl_block)
2✔
189
          cond = _callable(cond)
28✔
190
          if_true(->(state) { !cond.call(state) }, &dsl_block)
52✔
191
        end
192

193
        alias_method :sequence, :around
2✔
194
        alias_method :guard, :if_true
2✔
195

196
        private
2✔
197

198
        def wrap(obj) = Result.result(obj)
2✔
199

200
        def _callable(callable)
2✔
201
          case callable
434✔
202
          when Proc
203
            ->(*args, **kwargs) { @operation.instance_exec(*args, **kwargs, &callable) }
296✔
204
          when Symbol
205
            ->(*args, **kwargs) { @operation.send(callable, *args, **kwargs) }
540✔
206
          else
207
            callable
×
208
          end
209
        end
210
      end
211
    end
212
  end
213

214
  Operation.plugin Plugins::Base
2✔
215
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc