mojones 0.1.0 → 0.2.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/CHANGELOG.md +40 -0
- data/LICENSE.txt +21 -0
- data/README.md +127 -0
- data/lib/generators/mojones/templates/service.rb.tt +8 -0
- data/lib/mojones/base.rb +18 -60
- data/lib/mojones/matcher.rb +52 -62
- data/lib/mojones/version.rb +1 -1
- metadata +9 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ab2a33993a4fda3c283c9e61948fa594c550422bfd8faab82037c9603fc2f335
|
|
4
|
+
data.tar.gz: 7df0d3e348b050307dd5c26c8de32d4cb18bf588ec6e1435a4818925c518dae9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8e360c4ab6d4c34059cf4b0aa68fe7bc25276a080ef908c6355aab4dfdbf2b3898cd9531009abd1b45c8d7b62920293e84dcc413e8faef2a85b45fd2b797fb03
|
|
7
|
+
data.tar.gz: efb0eac1c498889aa00d2c08a233721e730ed039015535e4ba8ae30af3a9376653dc57fc1a7224efdf48d48c20ddf19b011f1491c1c584fe93c34624f94ea3e6
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 (unreleased)
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- **The first matching handler now wins, and later handlers don't run.**
|
|
8
|
+
Before, every matching handler ran and the last one's result was
|
|
9
|
+
returned. If you put a catch-all before a more specific handler, swap
|
|
10
|
+
them so the specific one comes first:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
# 0.1.0
|
|
14
|
+
m.failure { |_| "Something went wrong" }
|
|
15
|
+
m.failure(:card_declined) { |_| "Your card was declined" }
|
|
16
|
+
|
|
17
|
+
# 0.2.0
|
|
18
|
+
m.failure(:card_declined) { |_| "Your card was declined" }
|
|
19
|
+
m.failure { |_| "Something went wrong" }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- **Failure values other than Symbols and exceptions reach the handler
|
|
23
|
+
unchanged.** For example, `Failure(user.errors)` now yields the errors
|
|
24
|
+
object. Before, it became a string: `to_s`, or a "translation missing"
|
|
25
|
+
message when I18n was loaded.
|
|
26
|
+
- **Ruby 3.3 or later is required.** Ruby 3.2 reached end of life in March 2026.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- `rails generate mojones:service` works when the gem is installed from
|
|
31
|
+
RubyGems. The template was missing from the packaged gem.
|
|
32
|
+
- Failures from anonymous service or exception classes no longer raise
|
|
33
|
+
`NoMethodError` when I18n is loaded.
|
|
34
|
+
- Apps that load I18n without ActiveSupport no longer raise
|
|
35
|
+
`NoMethodError` from failure handlers. Translation is skipped instead.
|
|
36
|
+
- The matcher no longer writes debug output to stdout outside Rails.
|
|
37
|
+
|
|
38
|
+
## 0.1.0 (2026-03-21)
|
|
39
|
+
|
|
40
|
+
- Initial release.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Phil Brockwell, Habib Alamin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Mojones
|
|
2
|
+
|
|
3
|
+
Service objects with monadic result handling. A lightweight framework
|
|
4
|
+
around [dry-monads](https://dry-rb.org/gems/dry-monads/) that gives every
|
|
5
|
+
service object one enforced shape — `.call` always returns a `Success`/
|
|
6
|
+
`Failure` result (or raises), and a small block-based matcher lets callers
|
|
7
|
+
branch on the outcome without a pile of `if result.success?` checks.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
bundle add mojones
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or add it to your Gemfile directly:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem "mojones"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
Inherit from `Mojones::Base` and return a `Dry::Monads::Result` from
|
|
24
|
+
`#call`:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
class CreateUser < Mojones::Base
|
|
28
|
+
def initialize(params)
|
|
29
|
+
@params = params
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def call
|
|
33
|
+
user = User.new(@params)
|
|
34
|
+
return Failure(user.errors) unless user.save
|
|
35
|
+
|
|
36
|
+
Success(user)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Calling `.call` without a block gives you the `Result` itself:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
result = CreateUser.call(params) # => Success(#<User ...>) or Failure(#<ActiveModel::Errors ...>)
|
|
45
|
+
result.value! # => #<User ...>, or raises on failure
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Pass a block to match on the outcome instead:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
CreateUser.call(params) do |m|
|
|
52
|
+
m.success { |user| redirect_to user }
|
|
53
|
+
m.failure { |errors| render :new, status: :unprocessable_entity }
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`success` and `failure` can also filter on the value or error, using `===`
|
|
58
|
+
(so classes, ranges, regexps and literal values all work):
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
notice = ChargeCard.call(order) do |m|
|
|
62
|
+
m.success { |charge| "Charged #{charge.amount}" }
|
|
63
|
+
m.failure(CardDeclined) { |_| "Your card was declined" }
|
|
64
|
+
m.failure { |_error, message| message }
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Handlers are tried in order and the **first** match wins, like
|
|
69
|
+
`case`/`when`. Later handlers don't run at all, so put specific handlers
|
|
70
|
+
before catch-alls, and side effects like `redirect_to` only ever happen
|
|
71
|
+
once.
|
|
72
|
+
|
|
73
|
+
If the service raises, the exception is matchable as a failure, so
|
|
74
|
+
`m.failure(ActiveRecord::RecordNotFound) { ... }` works. Without a block,
|
|
75
|
+
or when no handler matches, the exception is re-raised.
|
|
76
|
+
|
|
77
|
+
If `#call` returns something that isn't a `Dry::Monads::Result`, `.call`
|
|
78
|
+
raises `Mojones::Base::Errors::ServiceReturnedNonResult`. That's a bug in
|
|
79
|
+
the service, so it can't be matched.
|
|
80
|
+
|
|
81
|
+
### Failure values and I18n
|
|
82
|
+
|
|
83
|
+
A one-argument `failure` block gets the failure value translated:
|
|
84
|
+
|
|
85
|
+
- A Symbol is looked up under the service's I18n scope, so
|
|
86
|
+
`Failure(:card_declined)` in `Billing::ChargeCard` is looked up as
|
|
87
|
+
`billing.charge_card.card_declined`. If there's no translation, it falls
|
|
88
|
+
back to `"Card declined"`.
|
|
89
|
+
- An exception is looked up by its class name, falling back to its message.
|
|
90
|
+
- Any other value (for example `user.errors`) is passed through unchanged.
|
|
91
|
+
|
|
92
|
+
Translation needs I18n and ActiveSupport, so in practice it means Rails.
|
|
93
|
+
Without them, Symbols and exceptions arrive as `to_s` strings. Anonymous
|
|
94
|
+
classes are never translated.
|
|
95
|
+
|
|
96
|
+
To get the untranslated value as well, take two arguments:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
m.failure { |error, message| flash[:alert] = message }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Rails generator
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
bin/rails generate mojones:service CreateUser
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Scaffolds `app/services/create_user.rb` with the `Mojones::Base` skeleton
|
|
109
|
+
above.
|
|
110
|
+
|
|
111
|
+
## Development
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
bundle install
|
|
115
|
+
bundle exec rspec # tests
|
|
116
|
+
bundle exec rubocop # lint
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
CI (`.github/workflows/`) runs both on every push and pull request.
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT — see `LICENSE.txt`.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
Built by [TrueFlux](https://trueflux.agency).
|
data/lib/mojones/base.rb
CHANGED
|
@@ -7,22 +7,13 @@ module Mojones
|
|
|
7
7
|
module Errors
|
|
8
8
|
class NoHandlerMatched < StandardError
|
|
9
9
|
def initialize(value)
|
|
10
|
-
super
|
|
11
|
-
@value = value
|
|
10
|
+
super("No handler matched for #{value.inspect}")
|
|
12
11
|
end
|
|
13
|
-
|
|
14
|
-
def message = "No handler matched for #{@value.inspect}"
|
|
15
12
|
end
|
|
16
13
|
|
|
17
14
|
class ServiceReturnedNonResult < StandardError
|
|
18
15
|
def initialize(service, value)
|
|
19
|
-
super(value)
|
|
20
|
-
@service_name = service.class.inspect.sub(/^Mojones::/, "")
|
|
21
|
-
@value = value
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def message
|
|
25
|
-
"Service #{@service_name} returned non-Result value (#{@value.inspect} : #{@value.class})"
|
|
16
|
+
super("Service #{service.name} returned non-Result value (#{value.inspect} : #{value.class})")
|
|
26
17
|
end
|
|
27
18
|
end
|
|
28
19
|
end
|
|
@@ -36,10 +27,6 @@ module Mojones
|
|
|
36
27
|
@value
|
|
37
28
|
end
|
|
38
29
|
|
|
39
|
-
def fmap
|
|
40
|
-
yield @value
|
|
41
|
-
end
|
|
42
|
-
|
|
43
30
|
def success?
|
|
44
31
|
@value.success?
|
|
45
32
|
end
|
|
@@ -57,30 +44,6 @@ module Mojones
|
|
|
57
44
|
end
|
|
58
45
|
end
|
|
59
46
|
|
|
60
|
-
class InitializerReturnedValue < ReturnedValue
|
|
61
|
-
def success? = true
|
|
62
|
-
def failure? = true
|
|
63
|
-
def original = @value
|
|
64
|
-
|
|
65
|
-
def assert_call_returned_result_monad_or_raised!
|
|
66
|
-
self
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
class CallReturnedValue < ReturnedValue
|
|
71
|
-
def initialize(service, value)
|
|
72
|
-
super(value)
|
|
73
|
-
@service = service
|
|
74
|
-
@value = value
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def assert_call_returned_result_monad_or_raised!
|
|
78
|
-
return self if @value.is_a?(Dry::Monads::Result)
|
|
79
|
-
|
|
80
|
-
raise Errors::ServiceReturnedNonResult.new(@service, @value)
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
47
|
class RaisedError
|
|
85
48
|
def initialize(error)
|
|
86
49
|
@error = error
|
|
@@ -97,35 +60,30 @@ module Mojones
|
|
|
97
60
|
@error
|
|
98
61
|
end
|
|
99
62
|
|
|
100
|
-
def fmap
|
|
101
|
-
self
|
|
102
|
-
end
|
|
103
|
-
|
|
104
63
|
def no_matches!
|
|
105
64
|
raise @error
|
|
106
65
|
end
|
|
107
|
-
|
|
108
|
-
def assert_call_returned_result_monad_or_raised!
|
|
109
|
-
self
|
|
110
|
-
end
|
|
111
66
|
end
|
|
112
67
|
|
|
113
|
-
def self.call(
|
|
114
|
-
result = (
|
|
115
|
-
begin
|
|
116
|
-
InitializerReturnedValue.new(new(*args, **kwargs))
|
|
117
|
-
rescue StandardError => e
|
|
118
|
-
RaisedError.new(e)
|
|
119
|
-
end
|
|
120
|
-
).fmap do |service_object|
|
|
121
|
-
CallReturnedValue.new(service_object, service_object.call)
|
|
122
|
-
rescue StandardError => e
|
|
123
|
-
RaisedError.new(e)
|
|
124
|
-
end.assert_call_returned_result_monad_or_raised!
|
|
125
|
-
|
|
68
|
+
def self.call(*, **, &)
|
|
69
|
+
result = execute(*, **)
|
|
126
70
|
return result.value! unless block_given?
|
|
127
71
|
|
|
128
72
|
Matcher.new(result, self, &).result
|
|
129
73
|
end
|
|
74
|
+
|
|
75
|
+
# Errors raised by the service become a matchable RaisedError. Returning a
|
|
76
|
+
# non-Result is a bug in the service, so it raises instead (an exception
|
|
77
|
+
# raised in `else` isn't caught by the method's `rescue`).
|
|
78
|
+
def self.execute(*, **)
|
|
79
|
+
value = new(*, **).call
|
|
80
|
+
rescue StandardError => e
|
|
81
|
+
RaisedError.new(e)
|
|
82
|
+
else
|
|
83
|
+
raise Errors::ServiceReturnedNonResult.new(self, value) unless value.is_a?(Dry::Monads::Result)
|
|
84
|
+
|
|
85
|
+
ReturnedValue.new(value)
|
|
86
|
+
end
|
|
87
|
+
private_class_method :execute
|
|
130
88
|
end
|
|
131
89
|
end
|
data/lib/mojones/matcher.rb
CHANGED
|
@@ -1,98 +1,88 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "logger"
|
|
4
|
-
|
|
5
3
|
module Mojones
|
|
6
4
|
class Matcher
|
|
7
|
-
class Match
|
|
8
|
-
attr_reader :matched, :result
|
|
9
|
-
|
|
10
|
-
def initialize(matched:, result:)
|
|
11
|
-
@matched = matched
|
|
12
|
-
@result = result
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
class Success < self; end
|
|
16
|
-
class Failure < self; end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
5
|
def initialize(service_result, service)
|
|
20
6
|
@service = service
|
|
21
7
|
@service_result = service_result
|
|
22
|
-
@
|
|
8
|
+
@matched = false
|
|
23
9
|
yield(self) if block_given?
|
|
24
10
|
end
|
|
25
11
|
|
|
26
12
|
def result
|
|
27
|
-
return @service_result.no_matches!
|
|
28
|
-
|
|
29
|
-
@
|
|
30
|
-
if @matches.size > 1
|
|
31
|
-
logger.debug <<~WARNING
|
|
32
|
-
#{service.name} matched multiple handlers; returning last result (#{chosen})
|
|
33
|
-
WARNING
|
|
34
|
-
end
|
|
35
|
-
end
|
|
13
|
+
return @service_result.no_matches! unless @matched
|
|
14
|
+
|
|
15
|
+
@result
|
|
36
16
|
end
|
|
37
17
|
|
|
38
|
-
|
|
39
|
-
|
|
18
|
+
# Handlers are tried in order and the first match wins, like case/when.
|
|
19
|
+
# Later handlers are skipped entirely, so their side effects never run.
|
|
20
|
+
def success(*patterns)
|
|
21
|
+
return if @matched || !@service_result.success?
|
|
40
22
|
|
|
41
23
|
value = @service_result.original
|
|
24
|
+
return unless matches?(patterns, value)
|
|
42
25
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@matches << Match::Success.new(
|
|
46
|
-
matched: value,
|
|
47
|
-
result: block.call(value)
|
|
48
|
-
)
|
|
26
|
+
match(yield(value))
|
|
49
27
|
end
|
|
50
28
|
|
|
51
|
-
def failure(*
|
|
52
|
-
return
|
|
29
|
+
def failure(*patterns, &block)
|
|
30
|
+
return if @matched || !@service_result.failure?
|
|
53
31
|
|
|
54
32
|
error = @service_result.original
|
|
55
|
-
|
|
56
|
-
return unless match_errors.empty? || match_errors.any? { _1 === error } # rubocop:disable Style/CaseEquality
|
|
33
|
+
return unless matches?(patterns, error)
|
|
57
34
|
|
|
58
35
|
translated = translate_error(error)
|
|
59
|
-
|
|
60
|
-
@matches << Match::Failure.new(
|
|
61
|
-
matched: error,
|
|
62
|
-
result: block.arity == 2 ? block.call(error, translated) : block.call(translated)
|
|
63
|
-
)
|
|
36
|
+
match(block.arity == 2 ? yield(error, translated) : yield(translated))
|
|
64
37
|
end
|
|
65
38
|
|
|
66
39
|
private
|
|
67
40
|
|
|
68
41
|
attr_reader :service
|
|
69
42
|
|
|
70
|
-
def
|
|
71
|
-
@
|
|
43
|
+
def match(result)
|
|
44
|
+
@matched = true
|
|
45
|
+
@result = result
|
|
72
46
|
end
|
|
73
47
|
|
|
74
|
-
|
|
75
|
-
|
|
48
|
+
# Matches like case/when, so classes, ranges, regexps and plain values all work.
|
|
49
|
+
def matches?(patterns, value)
|
|
50
|
+
return true if patterns.empty?
|
|
51
|
+
|
|
52
|
+
case value
|
|
53
|
+
when *patterns then true
|
|
54
|
+
else false
|
|
55
|
+
end
|
|
76
56
|
end
|
|
77
57
|
|
|
78
58
|
def translate_error(error)
|
|
79
|
-
return error
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
59
|
+
return error unless error.is_a?(Symbol) || error.is_a?(StandardError)
|
|
60
|
+
return error.to_s unless translatable?(error)
|
|
61
|
+
|
|
62
|
+
I18n.t(error_key(error), default: error_default(error))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Keys are built from class names, so anonymous classes can't be translated.
|
|
66
|
+
def translatable?(error)
|
|
67
|
+
i18n_available? && service.name && (error.is_a?(Symbol) || error.class.name)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Key building and defaults use ActiveSupport's inflector.
|
|
71
|
+
def i18n_available?
|
|
72
|
+
defined?(I18n) && defined?(ActiveSupport::Inflector)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def error_key(error)
|
|
76
|
+
key = error.is_a?(Symbol) ? error : i18n_path(error.class.name)
|
|
77
|
+
"#{i18n_path(service.name)}.#{key}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def error_default(error)
|
|
81
|
+
error.is_a?(Symbol) ? ActiveSupport::Inflector.humanize(error) : error.message
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def i18n_path(name)
|
|
85
|
+
ActiveSupport::Inflector.underscore(name.gsub("::", "."))
|
|
96
86
|
end
|
|
97
87
|
end
|
|
98
88
|
end
|
data/lib/mojones/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mojones
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Phil Brockwell
|
|
@@ -33,15 +33,20 @@ executables: []
|
|
|
33
33
|
extensions: []
|
|
34
34
|
extra_rdoc_files: []
|
|
35
35
|
files:
|
|
36
|
+
- CHANGELOG.md
|
|
37
|
+
- LICENSE.txt
|
|
38
|
+
- README.md
|
|
36
39
|
- lib/generators/mojones/service_generator.rb
|
|
40
|
+
- lib/generators/mojones/templates/service.rb.tt
|
|
37
41
|
- lib/mojones.rb
|
|
38
42
|
- lib/mojones/base.rb
|
|
39
43
|
- lib/mojones/matcher.rb
|
|
40
44
|
- lib/mojones/version.rb
|
|
41
|
-
homepage: https://
|
|
45
|
+
homepage: https://github.com/TrueFlux/mojones
|
|
42
46
|
licenses:
|
|
43
47
|
- MIT
|
|
44
48
|
metadata:
|
|
49
|
+
changelog_uri: https://github.com/TrueFlux/mojones/blob/main/CHANGELOG.md
|
|
45
50
|
rubygems_mfa_required: 'true'
|
|
46
51
|
rdoc_options: []
|
|
47
52
|
require_paths:
|
|
@@ -50,14 +55,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
50
55
|
requirements:
|
|
51
56
|
- - ">="
|
|
52
57
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '3.
|
|
58
|
+
version: '3.3'
|
|
54
59
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
60
|
requirements:
|
|
56
61
|
- - ">="
|
|
57
62
|
- !ruby/object:Gem::Version
|
|
58
63
|
version: '0'
|
|
59
64
|
requirements: []
|
|
60
|
-
rubygems_version:
|
|
65
|
+
rubygems_version: 4.0.3
|
|
61
66
|
specification_version: 4
|
|
62
67
|
summary: Service objects with monadic result handling
|
|
63
68
|
test_files: []
|