navigable 1.5.1 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97471cde6b2f3d8127d70152b824662e1d62694d5c7630d05c06d5767b0fb5c7
4
- data.tar.gz: b3bbd91964cacf1d7929419d268e6af3631a625d6ec4af7eca47741649bd4577
3
+ metadata.gz: aa68142c1f4409d279fc63286903134018efc36b11325184012ca55a335a998a
4
+ data.tar.gz: 9426d5938d355c4f47dfba1970c8e835635c2cfd0511fd064bee4abb8bfa04a8
5
5
  SHA512:
6
- metadata.gz: 6587cf7816236fb7f68cfc7f8793ebd6c14a8684687409ef656769e93cc149e47bec147a7f2083eda4470db0c35c1bd069051a895ae5664db9cc670270f29f98
7
- data.tar.gz: b3f81adf0992c20e928eae72e4c0911401c83ded05f2b21c92f09242948020d5f2866be96d7c622f0ded6daeb37c1cb75c3442687283513392b49368d3f19560
6
+ metadata.gz: d9e4e3f63dab110af1fd28d0592f00d5c4b1bbc4e9c8347e78b9cbe35f979c3ee8974061a5a90c49c8f2242fa39771564ebed84e7d14b11697ccbcbcc3ec7ef7
7
+ data.tar.gz: f78fd62ce3dc5cf6602bf2b6d1fbadc46593ead0e3a967669f808f03206447d11d50a5e47c18d3e4c971ecb3ed541a884be0573f1161a50927fae501741beb2a
data/README.md CHANGED
@@ -2,97 +2,39 @@
2
2
 
3
3
  # Navigable
4
4
 
5
- [![Gem Version](https://badge.fury.io/rb/navigable.svg)](https://badge.fury.io/rb/navigable) [![Build Status](https://travis-ci.org/first-try-software/navigable.svg?branch=main)](https://travis-ci.org/first-try-software/navigable) [![Maintainability](https://api.codeclimate.com/v1/badges/33ca28cb17e1b512e006/maintainability)](https://codeclimate.com/github/first-try-software/navigable/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/33ca28cb17e1b512e006/test_coverage)](https://codeclimate.com/github/first-try-software/navigable/test_coverage)
6
-
7
- Navigable is a family of gems that together provide all the tools you need to build fast, testable, and reliable JSON and/or GraphQL based APIs with isolated, composable business logic. The gems include:
8
-
9
- <table style="margin: 20px 0">
10
- <tr height="140">
11
- <td width="130"><img alt="Clipper Ship" src="https://raw.githubusercontent.com/first-try-software/navigable/main/assets/clipper.png"></td>
12
- <td>
13
-
14
- **[Navigable][navigable]**<br>
15
- A stand-alone tool for isolating business logic from external interfaces and cross-cutting concerns. Navigable composes self-configured command and observer objects to allow you to extend your business logic without modifying it. Navigable is compatible with any Ruby-based application development framework, including Rails, Hanami, and Sinatra.
16
-
17
- </td>
18
- </tr>
19
- <tr height="140">
20
- <td width="130"><img alt="Compass" src="https://raw.githubusercontent.com/first-try-software/navigable/main/assets/sextant.png"></td>
21
- <td>
22
-
23
- **[Navigable Router][router]** *(coming soon)*<br>
24
- A simple, highly-performant, Rack-based router.
25
-
26
- </td>
27
- </tr>
28
- <tr height="140">
29
- <td width="130"><img alt="Compass" src="https://raw.githubusercontent.com/first-try-software/navigable/main/assets/compass.png"></td>
30
- <td>
31
-
32
- **[Navigable Server][server]** *(coming soon)*<br>
33
- A Rack-based server for building Ruby and Navigable web applications.
34
-
35
- </td>
36
- </tr>
37
- <tr height="140">
38
- <td width="130"><img alt="Map" src="https://raw.githubusercontent.com/first-try-software/navigable/main/assets/map.png"></td>
39
- <td>
40
-
41
- **[Navigable GraphQL][graphql]** *(coming soon)*<br>
42
- An extension of Navigable Server for building GraphQL APIs.
43
-
44
- </td>
45
- </tr>
46
- </table>
47
-
48
- <br><br>
49
-
50
- <img style="width: 600px; display: block; margin: 0 auto;" alt="Lighthouse" src="https://raw.githubusercontent.com/first-try-software/navigable/main/assets/lighthouse.png">
51
-
52
- # The Navigable Charter
5
+ Navigable is a stand-alone tool for isolating business logic from external interfaces and cross-cutting concerns. Navigable composes self-configured command and observer objects to allow you to extend your business logic without modifying it. Navigable is compatible with any Ruby-based application development framework, including Rails, Hanami, and Sinatra.
53
6
 
54
- We hold these truths to be self-evident, that not all objects are created equal, that poorly structured code leads to poorly tested code, and that poorly tested code leads to rigid software and fearful engineers.
7
+ <br>
55
8
 
56
- We believe a framework should break free of this tyranny. It should be simple, testable, and fast. It can be opinionated. But, it should leverage SOLID principles to guide us toward well structured, well tested, maleable code that is truly navigable.
57
-
58
- ## Who We Are
59
-
60
- We are professional Rubyists. We could write software in any language, but we choose to work in Ruby because it is so beautiful and expressive. We love Ruby.
61
-
62
- We are test-oriented developers. We always write tests for our code, often before we've written the code. And, despite the conventional wisdom that investing in tests produces diminishing returns as you approach 100% coverage, we prefer the confidence we get with full coverage.
63
-
64
- We are also students of software architecture. We apply SOLID object-oriented design principles like the [Single Responsibility][srp] and [Open/Closed][ocp] Principles to everything we build. And, we follow [Sandi Metz's Rules][sandi] as much as possible. This leads us to write small, loosely coupled, highly cohesive classes.
65
-
66
- ## Why We Wrote Navigable
67
-
68
- Besides being Rubyists, we are also seasoned Rails developers. Most of our experience with Ruby has involved Rails. We've also built applications in Sinatra, and dabbled with Hanami. And, while they all have strengths, we're not completely satisfied with any of them.
69
-
70
- ### Rails
9
+ [![Gem Version](https://badge.fury.io/rb/navigable.svg)](https://badge.fury.io/rb/navigable) [![Build Status](https://travis-ci.org/first-try-software/navigable.svg?branch=main)](https://travis-ci.org/first-try-software/navigable) [![Maintainability](https://api.codeclimate.com/v1/badges/33ca28cb17e1b512e006/maintainability)](https://codeclimate.com/github/first-try-software/navigable/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/33ca28cb17e1b512e006/test_coverage)](https://codeclimate.com/github/first-try-software/navigable/test_coverage)
71
10
 
72
- In our experience, Rails is a fantastic tool for building complex web applications. But, too often, we see engineers let Rails constrain them into thinking that all of their business logic belongs in models (and controllers, and even views!). This leads to overly complex classes with too many responsibilities that are difficult to test. It also reduces the reusability of any one bit of business logic to have it burried in a 5,000 line file. (Yes, we've seen 5,000 line models and controllers, and much worse!) Rails is also a bit of a large toolbox when all you want to do is build a JSON and/or GraphQL API.
11
+ ## Installation
73
12
 
74
- ### Hanami
13
+ Add this line to your application's Gemfile:
75
14
 
76
- In dabbling with Hanami, we feel it has a great deal of promise. As proponents of [Domain Driven Design][ddd], we agree with many of the decisions that went into the framework. But, given that it uses convention as much as Rails, and given the lack of a dedicated home for business logic, we worry that Hanami applications will fall prey to the same problem many large Rails apps face: bloated models/entities and controller actions.
15
+ ```ruby
16
+ gem 'navigable'
17
+ ```
77
18
 
78
- ### Sinatra
19
+ And then execute:
79
20
 
80
- In our experience, Sinatra is a great tool for small, simple applications. But, it's too slow to be of much use with larger, enterprise applications. So, we've limited our use to very specific types of applications with limited scope.
21
+ $ bundle install
81
22
 
82
- ### And, finally...
23
+ Or install it yourself as:
83
24
 
84
- All three of these frameworks take a central role in the organization of an application. We've worked on Rails applications in multiple different domains. They all looked like Rails applications. You had to dig into them to discover the core concepts of each specific domain. We believe software should be more reflective of the problem space than it is of the framework used to solve the problem.
25
+ $ gem install navigable
85
26
 
86
- ## How is Navigable Different?
27
+ ## Usage
87
28
 
88
- So, we built Navigable to help separate the web adapter (controller) and persistence layer (model) from your actual business logic. And, we did it in a composable manner that allows for incredible flexibility. Here's a peek:
29
+ We built Navigable to help separate the web adapter and persistence layer from your actual business logic. And, we did it in a composable manner that allows for incredible flexibility. Here's a peek:
89
30
 
90
31
  ```ruby
32
+ # CreateAlert command
91
33
  class CreateAlert
92
34
  extend Navigable::Command
93
35
 
94
36
  corresponds_to :create_alert
95
- corresponds_to :create_alert_with_notifications
37
+ corresponds_to :create_alert_and_notify
96
38
 
97
39
  def execute
98
40
  return failed_to_validate(new_alert) unless new_alert.valid?
@@ -101,13 +43,22 @@ class CreateAlert
101
43
  successfully created_alert
102
44
  end
103
45
 
104
- # ...
46
+ private
47
+
48
+ def new_alert
49
+ @new_alert ||= Alert.new(params)
50
+ end
51
+
52
+ def created_alert
53
+ @created_alert ||= @new_alert.save
54
+ end
105
55
  end
106
56
 
57
+ # AllRecipientsNotifier observer
107
58
  class AllRecipientsNotifier
108
59
  extend Navigable::Observer
109
60
 
110
- observes :create_alert_with_notifications
61
+ observes :create_alert_and_notify
111
62
 
112
63
  def on_success(alert)
113
64
  NotifyAllRecipientsWorker.perform_async(alert_id: alert.id)
@@ -124,7 +75,7 @@ Navigable::Dispatcher.dispatch(:create_alert, params: alert_params)
124
75
  Or, create an alert and notify all recipients:
125
76
 
126
77
  ```ruby
127
- Navigable::Dispatcher.dispatch(:create_alert_with_notifications, params: alert_params)
78
+ Navigable::Dispatcher.dispatch(:create_alert_and_notify, params: alert_params)
128
79
  ```
129
80
 
130
81
  All without having to add conditional logic about the notifications to the `CreateAlert` class.
@@ -132,6 +83,7 @@ All without having to add conditional logic about the notifications to the `Crea
132
83
  Similarly, you can add cross-cutting concerns to an application just as easily:
133
84
 
134
85
  ```ruby
86
+ # Monitor observer
135
87
  class Monitor
136
88
  extend Navigable::Observer
137
89
 
@@ -164,27 +116,33 @@ Here are a few things to look for in the code above:
164
116
 
165
117
  For a deeper look at the core concepts introduced by Navigable, please have a look at our [wiki][wiki].
166
118
 
167
- ## Feedback
168
-
169
- We are really excited about Navigable! We think it solves the problem of seperating business logic from the web interface, persistence layer, and even cross-cutting concerns in an elegant and simple way.
170
-
171
- We're thrilled you're checking out Navigable! If you have any questions or comments, please feel free to reach out to [navigable@firsttry.software][mail].
119
+ ## Rails Usage
172
120
 
173
- ## Installation
174
-
175
- Add this line to your application's Gemfile:
121
+ The code above can be integrated into Rails like this:
176
122
 
177
123
  ```ruby
178
- gem 'navigable'
179
- ```
124
+ # AlertsController
125
+ class AlertsController < ApplicationController
126
+ # ...
180
127
 
181
- And then execute:
128
+ def create
129
+ @alert = Navigable::Dispatcher.dispatch(:create_alert, params: alert_params)
182
130
 
183
- $ bundle install
131
+ if @alert.persisted?
132
+ redirect_to @alert, notice: 'Alert successfully created.'
133
+ else
134
+ render :new
135
+ end
136
+ end
184
137
 
185
- Or install it yourself as:
138
+ # ...
139
+ end
186
140
 
187
- $ gem install navigable
141
+ # Alert model
142
+ class Alert < ApplicationRecord
143
+ validates :title, presence: true
144
+ end
145
+ ```
188
146
 
189
147
  ## Contributing
190
148
 
@@ -198,7 +156,6 @@ The gem is available as open source under the terms of the [MIT License](https:/
198
156
 
199
157
  Everyone interacting in the Navigable project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/first-try-software/navigable/blob/main/CODE_OF_CONDUCT.md).
200
158
 
201
-
202
159
  [sandi]: https://thoughtbot.com/blog/sandi-metz-rules-for-developers
203
160
  [srp]: https://en.wikipedia.org/wiki/Single-responsibility_principle
204
161
  [ocp]: https://en.wikipedia.org/wiki/Open–closed_principle
@@ -1,6 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  require 'navigable/observable'
4
+ require 'navigable/input'
4
5
 
5
6
  module Navigable
6
7
  module Command
@@ -11,8 +12,8 @@ module Navigable
11
12
 
12
13
  def self.extended(base)
13
14
  base.extend(Manufacturable::Item)
15
+ base.extend(Observable)
14
16
  base.extend(ClassMethods)
15
- base.include(Observable)
16
17
  base.include(InstanceMethods)
17
18
  end
18
19
 
@@ -23,12 +24,11 @@ module Navigable
23
24
  end
24
25
 
25
26
  module InstanceMethods
26
- attr_reader :params, :observers, :resolver
27
+ attr_reader :input, :observers
27
28
 
28
- def inject(params: {}, observers: [], resolver: NullResolver.new)
29
- @params = params
29
+ def inject(input: Input.new, observers: [])
30
+ @input = input
30
31
  @observers = observers
31
- @resolver = resolver
32
32
  end
33
33
 
34
34
  def execute
@@ -0,0 +1,42 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'navigable/observer'
4
+ require 'navigable/command'
5
+
6
+ module Navigable
7
+ class CommandBuilder
8
+ attr_reader :key, :input
9
+
10
+ def initialize(key, input:)
11
+ @key = key
12
+ @input = input
13
+ end
14
+
15
+ def observed_by(observer)
16
+ observers << observer
17
+ self
18
+ end
19
+
20
+ def execute
21
+ command.execute
22
+ end
23
+
24
+ def observers
25
+ @observers ||= registered_observers
26
+ end
27
+
28
+ private
29
+
30
+ def registered_observers
31
+ Manufacturable.build_all(Observer::TYPE, key) { |observer| observer.inject(input: input) }
32
+ end
33
+
34
+ def command
35
+ build_command.tap { |command| raise Navigable::Command::NotFoundError unless command }
36
+ end
37
+
38
+ def build_command
39
+ Manufacturable.build_one(Command::TYPE, key) { |command| command.inject(input: input, observers: observers) }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Navigable
4
+ class Errors
5
+ def initialize()
6
+ @errors = Hash.new { |h, k| h[k] = [] }
7
+ end
8
+
9
+ def add(field, message)
10
+ @errors[field] << message
11
+ end
12
+
13
+ def empty?
14
+ @errors.empty?
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Navigable
4
+ class ImmutableObject
5
+ @attribute_names = []
6
+
7
+ class << self
8
+ attr_reader :attribute_names
9
+
10
+ def attribute(name)
11
+ @attribute_names << name
12
+ attr_reader(name)
13
+ end
14
+
15
+ private
16
+
17
+ def inherited(subclass)
18
+ super
19
+ subclass.instance_variable_set(:@attribute_names, @attribute_names.dup)
20
+ end
21
+ end
22
+
23
+ def initialize(**args)
24
+ absent_attributes = args.keys - self.class.attribute_names
25
+
26
+ if absent_attributes.any?
27
+ raise ArgumentError, "Unknown attribute #{absent_attributes}"
28
+ end
29
+
30
+ args.each do |name, value|
31
+ instance_variable_set("@#{name}", value)
32
+ end
33
+
34
+ freeze
35
+ end
36
+
37
+ def attributes
38
+ self.class.attribute_names.to_h { |name| [name, public_send(name)] }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Navigable
4
+ class ImmutableStruct < Struct
5
+ class << self
6
+ def new(*args, &block)
7
+ super(*args, keyword_init: true, &block)
8
+ end
9
+ end
10
+
11
+ def initialize(**args)
12
+ members.each { |key| args.fetch(key) }
13
+ super(**args)
14
+ freeze
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,42 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'navigable/immutable_object'
4
+ require 'navigable/errors'
5
+
6
+ module Navigable
7
+ class Input < ImmutableObject
8
+ class << self
9
+ def validates_presence_of(field)
10
+ validations << ->(subject) do
11
+ if subject.public_send(field).nil?
12
+ subject.errors.add(field, "not present")
13
+ end
14
+ end
15
+ end
16
+
17
+ def validates_max_length_of(field, max_length)
18
+ validations << ->(subject) do
19
+ if (subject.public_send(field) && subject.public_send(field).length > max_length)
20
+ subject.errors.add(field, "longer than #{max_length}")
21
+ end
22
+ end
23
+ end
24
+
25
+ def validations
26
+ @validations ||= []
27
+ end
28
+ end
29
+
30
+ attr_reader :errors
31
+
32
+ def initialize(attributes = {})
33
+ @errors = Errors.new
34
+ super(**attributes.to_hash.transform_keys(&:to_sym))
35
+ end
36
+
37
+ def valid?
38
+ self.class.validations.each { |validation| validation.call(self) }
39
+ @errors.empty?
40
+ end
41
+ end
42
+ end
@@ -2,27 +2,44 @@
2
2
 
3
3
  require 'navigable/observer_map'
4
4
  require 'navigable/executor'
5
+ require 'navigable/result'
5
6
 
6
7
  module Navigable
7
8
  module Observable
8
9
  OBSERVERS_NOT_IMPLEMENTED_MESSAGE = 'Class must implement `observers` method.'
9
- RESOLVER_NOT_IMPLEMENTED_MESSAGE = 'Class must implement `resolver` method.'
10
-
11
- def observers
12
- raise NotImplementedError.new(OBSERVERS_NOT_IMPLEMENTED_MESSAGE)
10
+
11
+ def self.extended(base)
12
+ base.extend(ClassMethods)
13
+ base.include(InstanceMethods)
13
14
  end
14
15
 
15
- def resolver
16
- raise NotImplementedError.new(RESOLVER_NOT_IMPLEMENTED_MESSAGE)
16
+ module ClassMethods
17
+ def result(*args)
18
+ @result_class = Navigable::Result.new(*args)
19
+ end
20
+
21
+ def result_class
22
+ @result_class ||= Navigable::Result.new
23
+ end
17
24
  end
25
+
26
+ module InstanceMethods
27
+ def observers
28
+ raise NotImplementedError.new(OBSERVERS_NOT_IMPLEMENTED_MESSAGE)
29
+ end
18
30
 
19
- ObserverMap::METHODS.each_pair do |observable_method, observer_method|
20
- define_method(observable_method) do |*args|
21
- observers.each do |observer|
22
- Navigable::Executor.execute { observer.send(observer_method, *args) }
23
- end
31
+ def result
32
+ self.class.result_class
33
+ end
24
34
 
25
- resolver.send(observer_method, *args)
35
+ ObserverMap::METHODS.each_pair do |observable_method, observer_method|
36
+ define_method(observable_method) do |*args|
37
+ observers.each do |observer|
38
+ Navigable::Executor.execute { observer.send(observer_method, *args) }
39
+ end
40
+
41
+ result.send(observer_method, *args)
42
+ end
26
43
  end
27
44
  end
28
45
  end
@@ -1,6 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  require 'navigable/observer_interface'
4
+ require 'navigable/input'
4
5
 
5
6
  module Navigable
6
7
  module Observer
@@ -24,10 +25,10 @@ module Navigable
24
25
  end
25
26
 
26
27
  module InstanceMethods
27
- attr_reader :params
28
+ attr_reader :input
28
29
 
29
- def inject(params: {})
30
- @params = params
30
+ def inject(input: Navigable::Input.new)
31
+ @input = input
31
32
  end
32
33
 
33
34
  def observed_command_key
@@ -5,7 +5,7 @@ require 'navigable/observer_map'
5
5
  module Navigable
6
6
  module ObserverInterface
7
7
  ObserverMap::METHODS.values.each do |observer_method|
8
- define_method(observer_method) { |*args| }
8
+ define_method(observer_method) { |*args| } unless method_defined?(observer_method)
9
9
  end
10
10
  end
11
11
  end
@@ -4,7 +4,6 @@ module Navigable
4
4
  class ObserverMap
5
5
  METHODS = {
6
6
  successfully: :on_success,
7
- successfully_created: :on_creation,
8
7
  failed_to_validate: :on_failure_to_validate,
9
8
  failed_to_find: :on_failure_to_find,
10
9
  failed_to_create: :on_failure_to_create,
@@ -0,0 +1,49 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'navigable/observer_interface'
4
+ require 'navigable/immutable_struct'
5
+
6
+ module Navigable
7
+ class Result < ImmutableStruct
8
+ extend ObserverInterface
9
+
10
+ class << self
11
+ def new(*members)
12
+ super(*members, :errors)
13
+ end
14
+
15
+ def on_success(*values)
16
+ new(*values)
17
+ end
18
+
19
+ def on_failure(errors)
20
+ values = members.to_h { |member| [member, nil] }
21
+ new(**values.merge(errors: errors))
22
+ end
23
+
24
+ alias_method :on_failure_to_validate, :on_failure
25
+ alias_method :on_failure_to_find, :on_failure
26
+ alias_method :on_failure_to_create, :on_failure
27
+ alias_method :on_failure_to_update, :on_failure
28
+ alias_method :on_failure_to_delete, :on_failure
29
+ end
30
+
31
+ def and_then(&block)
32
+ attributes = self.to_h.tap { |attributes| attributes.delete(:errors) }
33
+ block.call(**attributes) if errors.none?
34
+ self
35
+ end
36
+
37
+ def or_else(&block)
38
+ block.call(errors) if errors.any?
39
+ self
40
+ end
41
+
42
+ private
43
+
44
+ def initialize(*args)
45
+ values = { errors: [] }.merge(args.first || {})
46
+ super(**values)
47
+ end
48
+ end
49
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Navigable
4
- VERSION = "1.5.1"
4
+ VERSION = "1.5.2"
5
5
  end
data/lib/navigable.rb CHANGED
@@ -5,4 +5,4 @@ require 'manufacturable'
5
5
 
6
6
  require 'navigable/version'
7
7
  require 'navigable/command'
8
- require 'navigable/dispatcher'
8
+ require 'navigable/command_builder'
data/navigable.gemspec CHANGED
@@ -21,11 +21,11 @@ Gem::Specification.new do |spec|
21
21
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|assets)/}) }
22
22
  end
23
23
  spec.bindir = "exe"
24
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.executables = []
25
25
  spec.require_paths = ["lib"]
26
26
 
27
27
  spec.add_dependency "concurrent-ruby", "~> 1.1.7"
28
- spec.add_dependency "manufacturable", "~> 1.5"
28
+ spec.add_dependency "manufacturable", "~> 2"
29
29
 
30
30
  spec.add_development_dependency "bundler", "~> 2.0"
31
31
  spec.add_development_dependency "rake", "~> 12.0"
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: navigable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 1.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alan Ridlehoover
8
8
  - Fito von Zastrow
9
- autorequire:
9
+ autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2020-12-23 00:00:00.000000000 Z
12
+ date: 2022-04-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: concurrent-ruby
@@ -31,14 +31,14 @@ dependencies:
31
31
  requirements:
32
32
  - - "~>"
33
33
  - !ruby/object:Gem::Version
34
- version: '1.5'
34
+ version: '2'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: '1.5'
41
+ version: '2'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: bundler
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -132,14 +132,17 @@ files:
132
132
  - bin/setup
133
133
  - lib/navigable.rb
134
134
  - lib/navigable/command.rb
135
- - lib/navigable/dispatcher.rb
135
+ - lib/navigable/command_builder.rb
136
+ - lib/navigable/errors.rb
136
137
  - lib/navigable/executor.rb
137
- - lib/navigable/null_resolver.rb
138
+ - lib/navigable/immutable_object.rb
139
+ - lib/navigable/immutable_struct.rb
140
+ - lib/navigable/input.rb
138
141
  - lib/navigable/observable.rb
139
142
  - lib/navigable/observer.rb
140
143
  - lib/navigable/observer_interface.rb
141
144
  - lib/navigable/observer_map.rb
142
- - lib/navigable/resolver.rb
145
+ - lib/navigable/result.rb
143
146
  - lib/navigable/version.rb
144
147
  - navigable.gemspec
145
148
  homepage: https://firsttry.software
@@ -149,7 +152,7 @@ metadata:
149
152
  homepage_uri: https://firsttry.software
150
153
  source_code_uri: https://github.com/first-try-software/navigable
151
154
  bug_tracker_uri: https://github.com/first-try-software/navigable/issues
152
- post_install_message:
155
+ post_install_message:
153
156
  rdoc_options: []
154
157
  require_paths:
155
158
  - lib
@@ -164,8 +167,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
167
  - !ruby/object:Gem::Version
165
168
  version: '0'
166
169
  requirements: []
167
- rubygems_version: 3.1.2
168
- signing_key:
170
+ rubygems_version: 3.3.11
171
+ signing_key:
169
172
  specification_version: 4
170
173
  summary: Ahoy! Welcome aboard Navigable!
171
174
  test_files: []
@@ -1,39 +0,0 @@
1
- # frozen-string-literal: true
2
-
3
- require 'navigable/null_resolver'
4
- require 'navigable/observer'
5
- require 'navigable/command'
6
-
7
- module Navigable
8
- class Dispatcher
9
- def self.dispatch(key, params: {}, resolver: NullResolver.new)
10
- self.new(key, params: params, resolver: resolver).dispatch
11
- end
12
-
13
- def dispatch
14
- command.execute
15
- resolver.resolve
16
- end
17
-
18
- attr_reader :key, :params, :resolver
19
- private :key, :params, :resolver
20
-
21
- private
22
-
23
- def initialize(key, params:, resolver:)
24
- @key, @params, @resolver = key, params, resolver
25
- end
26
-
27
- def observers
28
- Manufacturable.build_all(Observer::TYPE, key) { |observer| observer.inject(params: params) }
29
- end
30
-
31
- def command
32
- build_command.tap { |command| raise Navigable::Command::NotFoundError unless command }
33
- end
34
-
35
- def build_command
36
- Manufacturable.build_one(Command::TYPE, key) { |command| command.inject(params: params, observers: observers, resolver: resolver) }
37
- end
38
- end
39
- end
@@ -1,19 +0,0 @@
1
- # frozen-string-literal: true
2
-
3
- require 'navigable/resolver'
4
-
5
- module Navigable
6
- class NullResolver
7
- extend Navigable::Resolver
8
-
9
- def resolve
10
- @result
11
- end
12
-
13
- ObserverMap::METHODS.values.each do |observer_method|
14
- define_method(observer_method) do |result|
15
- @result = result
16
- end
17
- end
18
- end
19
- end
@@ -1,33 +0,0 @@
1
- # frozen-string-literal: true
2
-
3
- require 'navigable/observer_interface'
4
-
5
- module Navigable
6
- module Resolver
7
- TYPE = :__resolver__
8
- RESOLVE_NOT_IMPLEMENTED_MESSAGE = 'Resolver classes must implement a `resolve` method.'
9
-
10
- def self.extended(base)
11
- base.extend(Manufacturable::Item)
12
- base.extend(ClassMethods)
13
- base.include(ObserverInterface)
14
- base.include(InstanceMethods)
15
- end
16
-
17
- module ClassMethods
18
- def default_resolver
19
- default_manufacturable(TYPE)
20
- end
21
-
22
- def resolves(key)
23
- corresponds_to(key, TYPE)
24
- end
25
- end
26
-
27
- module InstanceMethods
28
- def resolve
29
- raise NotImplementedError.new(RESOLVE_NOT_IMPLEMENTED_MESSAGE)
30
- end
31
- end
32
- end
33
- end