onboardable 1.0.1 → 1.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9cc331c83492d5c21563a91644b3f2441b38d561ebde4d0690ba68813fadeb60
4
- data.tar.gz: 1e55f9c4913a17385c6e2ebe4c7d1dd58921a35b72d786171aa8a28efae09306
3
+ metadata.gz: ef1662a800c2b2c562af504026f4a84bd7a93c895236784e20e53659d7fdcaa3
4
+ data.tar.gz: 90df600da2944d056ca6d64891a311e4507f1227c2827b44485735451ccc329d
5
5
  SHA512:
6
- metadata.gz: '0456826a6d0b6bea084f95191240ea28776be3b4d8106a2f8055b259d24524055248143e01050be87e6c512084e9ddf7c576c594327ae9b617eb1db5e7068973'
7
- data.tar.gz: 2e35337681b26a2c09fa7ba1e7e8b5c505747a5c0144f3116214241c06aca3b04d76b72ce439d14ca717090caa01e95f7f219543784dcdbc07b354b66984686f
6
+ metadata.gz: 32e156cde48492f62a29a41b1c382c1be86bd5922b47ddcd32a5ca606127204002f8805da9c1adc087ea005c90a26a20de9081939617d9f623f9b5075fecd113
7
+ data.tar.gz: 3f8a70a79552741d71c0de140204220e66d70b5a43e5e7ce3c54c15b06900e55e15f5c840812c3463f3c10909bb693788e0aeaeb8039617a026b2fe2cef2bcc0
data/.rubocop.yml CHANGED
@@ -7,8 +7,5 @@ AllCops:
7
7
  NewCops: enable
8
8
  TargetRubyVersion: 3.0.0
9
9
 
10
- Style/Documentation:
11
- Enabled: false
12
-
13
10
  Gemspec/DevelopmentDependencies:
14
11
  EnforcedStyle: gemspec
data/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ - Added `onboarding` class method to define the onboarding steps.
6
+ - Added [documentation link](https://rubydoc.info/gems/onboardable) to gemspec.
7
+
8
+ ## [1.1.0] - 2024-05-21
9
+
10
+ - Introduced `step_from` method for adding steps from external sources.
11
+ - Added warn_about_override method to alert on step overrides.
12
+ - Added YARD documentation to the project for improved code documentation.
13
+
14
+ ## [1.0.1] - 2024-05-09
15
+
5
16
  - Added `first_step` and `last_step` methods to easily access
6
17
  the boundaries of step lists.
7
18
  - Added `progress` method for calculating onboarding completion percentage.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- onboardable (1.0.1)
4
+ onboardable (1.1.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -15,11 +15,12 @@ GEM
15
15
  parser (3.3.1.0)
16
16
  ast (~> 2.4.1)
17
17
  racc
18
- racc (1.7.3)
18
+ racc (1.8.0)
19
19
  rainbow (3.1.1)
20
20
  rake (13.2.1)
21
- regexp_parser (2.9.0)
22
- rexml (3.2.6)
21
+ regexp_parser (2.9.2)
22
+ rexml (3.2.8)
23
+ strscan (>= 3.0.9)
23
24
  rspec (3.13.0)
24
25
  rspec-core (~> 3.13.0)
25
26
  rspec-expectations (~> 3.13.0)
@@ -33,7 +34,7 @@ GEM
33
34
  diff-lcs (>= 1.2.0, < 2.0)
34
35
  rspec-support (~> 3.13.0)
35
36
  rspec-support (3.13.1)
36
- rubocop (1.63.4)
37
+ rubocop (1.63.5)
37
38
  json (~> 2.3)
38
39
  language_server-protocol (>= 3.17.0)
39
40
  parallel (~> 1.10)
@@ -69,6 +70,7 @@ GEM
69
70
  simplecov_json_formatter (~> 0.1)
70
71
  simplecov-html (0.12.3)
71
72
  simplecov_json_formatter (0.1.4)
73
+ strscan (3.1.0)
72
74
  unicode-display_width (2.5.0)
73
75
 
74
76
  PLATFORMS
@@ -79,7 +81,7 @@ DEPENDENCIES
79
81
  onboardable!
80
82
  rake (~> 13.2, >= 13.2.1)
81
83
  rspec (~> 3.13)
82
- rubocop (~> 1.63, >= 1.63.4)
84
+ rubocop (~> 1.63, >= 1.63.5)
83
85
  rubocop-performance (~> 1.21)
84
86
  rubocop-rake (~> 0.6.0)
85
87
  rubocop-rspec (~> 2.29, >= 2.29.2)
data/README.md CHANGED
@@ -31,32 +31,31 @@ project as per the installation guide provided earlier.
31
31
 
32
32
  ### Basic Configuration
33
33
 
34
- 1. **Include Onboardable in Your Class**
35
-
36
- Decide which Ruby class should have the onboarding process.
37
- For example, if you want to add an onboarding process to a `User`
38
- class, you would modify the class as follows:
34
+ To incorporate an onboarding process into your Ruby User class, start by
35
+ including the Onboardable module to add onboarding functionality. Then,
36
+ define the onboarding steps with the has_onboarding method, detailing each
37
+ step with helpful tooltips and descriptions. Here's how you can set it up:
39
38
 
40
39
  ```ruby
41
40
  class User
42
41
  include Onboardable
43
- end
44
- ```
45
42
 
46
- 1. **Define Onboarding Steps**
43
+ has_onboarding do
44
+ # Use the `step` method to steps with a name and optional data
45
+ step 'welcome', message: 'Welcome to your new account!'
46
+ step 'account_setup', task: 'Create credentials'
47
+ step 'confirmation'
47
48
 
48
- You can define the steps involved in the onboarding process using
49
- the `has_onboarding` method provided by the gem. Here's an example of
50
- how to define a simple onboarding process with custom steps,
51
- potentially including specific data for each step:
49
+ # Use the `step_from` method to define steps from external providers
50
+ step_from ExternalStepProvider
51
+ end
52
+ end
52
53
 
53
- ```ruby
54
- class User
55
- has_onboarding do
56
- step 'Create Account', tooltip: 'Minimum 8 characters.'
57
- step('Verify Email', Class.new { def self.to_hash = { required: true } })
58
- step 'Complete Profile' # This step does not include specific action data
59
- step 'Introduction Tour', description: 'Get to know your new workspace!'
54
+ # External class for providing a reusable onboarding step
55
+ class ExternalStepProvider
56
+ # Define class method to return an onboarding step
57
+ def self.to_onboarding_step
58
+ Onboardable::Step.new('external_step', info: 'This is an external step.')
60
59
  end
61
60
  end
62
61
  ```
@@ -79,7 +78,6 @@ that allow step navigation and state verification:
79
78
 
80
79
  ```ruby
81
80
  onboarding = User.new.onboarding
82
- # Initializes the onboarding process for a new user instance
83
81
  ```
84
82
 
85
83
  1. **Navigating Through Steps**
@@ -93,11 +91,8 @@ that allow step navigation and state verification:
93
91
  what's next or advance to it, updating the current step status.
94
92
 
95
93
  ```ruby
96
- onboarding.next_step
97
- # Returns the next step without changing the current step
98
-
99
- onboarding.next_step!
100
- # Advances to the next step, updating the current step
94
+ onboarding.next_step # Preview the next step
95
+ onboarding.next_step! # Advance to the next step
101
96
  ```
102
97
 
103
98
  - **Previous Step**
@@ -106,11 +101,8 @@ that allow step navigation and state verification:
106
101
  making changes or updates to revert to the previous step.
107
102
 
108
103
  ```ruby
109
- onboarding.prev_step
110
- # Returns the previous step without changing the current step
111
-
112
- onboarding.prev_step!
113
- # Reverts to the previous step, updating the current step
104
+ onboarding.prev_step # Preview the previous step
105
+ onboarding.prev_step! # Move back to the previous step
114
106
  ```
115
107
 
116
108
  1. **Check Step Position**
@@ -119,11 +111,8 @@ that allow step navigation and state verification:
119
111
  to manage UI elements like 'Next' or 'Back' buttons appropriately.
120
112
 
121
113
  ```ruby
122
- onboarding.first_step?
123
- # Returns true if the current step is the first
124
-
125
- onboarding.last_step?
126
- # Returns true if the current step is the last
114
+ onboarding.first_step? # Is the first step?
115
+ onboarding.last_step? # Is the last step?
127
116
  ```
128
117
 
129
118
  1. **Monitor Progress**
@@ -132,8 +121,7 @@ that allow step navigation and state verification:
132
121
  to provide users with an indication of how far they have progressed.
133
122
 
134
123
  ```ruby
135
- onboarding.progress
136
- # Returns the percentage of onboarding completion
124
+ onboarding.progress # Returns the completion percentage
137
125
  ```
138
126
 
139
127
  1. **Access Current Step Details**
@@ -143,17 +131,10 @@ that allow step navigation and state verification:
143
131
  the user complete tasks associated with the step.
144
132
 
145
133
  ```ruby
146
- onboarding.current_step
147
- # Returns the current step in the onboarding process
148
-
149
- onboarding.current_step.name
150
- # Returns the name of the current step
151
-
152
- onboarding.current_step.data
153
- # Returns the custom data associated with the step or an empty hash if not specified
154
-
155
- onboarding.current_step.status
156
- # Provides the current status or progress of the step
134
+ onboarding.current_step # Current step details
135
+ onboarding.current_step.name # Step name
136
+ onboarding.current_step.data # Step custom data
137
+ onboarding.current_step.status # Step status
157
138
  ```
158
139
 
159
140
  1. **Complete the Onboarding Process**
@@ -1,33 +1,79 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Onboardable
4
+ # Base error class for Onboardable-related exceptions.
4
5
  class Error < StandardError; end
5
6
 
6
- class EmptyListError < Error
7
+ # Error raised when a method is called on an object that does not define it.
8
+ class UndefinedMethodError < Error
9
+ # Initializes a new instance of UndefinedMethodError.
10
+ #
11
+ # @param klass [Class] The class that does not have the method defined.
12
+ # @param method [Symbol, String] The name of the method that is not defined.
13
+ def initialize(klass, method)
14
+ super("Method `#{method}` is not defined for `#{klass}`.")
15
+ end
16
+ end
17
+
18
+ # Error raised when an object cannot be converted to a Step.
19
+ class StepConversionError < Error
20
+ # Initializes a new instance of StepConversionError.
21
+ #
22
+ # @param klass [Class] The class that failed to convert.
23
+ # @param step [Object] The object returned by the failed conversion.
24
+ def initialize(klass, step)
25
+ super("can't convert #{klass} to Step (#{klass}#to_onboarding_step gives #{step.class}).")
26
+ end
27
+ end
28
+
29
+ # Error raised when an operation is attempted on an empty steps collection.
30
+ class EmptyStepsError < Error
31
+ # Initializes a new instance of EmptyStepsError.
32
+ # This error indicates that an operation requiring non-empty steps was attempted on an empty collection.
7
33
  def initialize
8
- super('Cannot be performed because the list is empty.')
34
+ super('Cannot be performed because the steps is empty.')
9
35
  end
10
36
  end
11
37
 
38
+ # Error raised when an invalid step is encountered within the onboarding process.
12
39
  class InvalidStepError < Error
40
+ # Initializes a new InvalidStepError with details about the issue.
41
+ #
42
+ # @param step [String] The invalid step that triggered the error.
43
+ # @param expected_steps [Array<String>] The list of valid steps expected at this point.
13
44
  def initialize(step, expected_steps)
14
45
  super("Invalid step: `#{step}`. Must be one of: `#{expected_steps.join('`, `')}`.")
15
46
  end
16
47
  end
17
48
 
18
- class InvalidComparisonResultError < Error
49
+ # Error raised when an invalid comparison result is encountered.
50
+ class ComparisonResultError < Error
51
+ # Initializes a new ComparisonResultError with details about the issue.
52
+ #
53
+ # @param comparison [Integer] The invalid comparison result that triggered the error.
54
+ # @param expected_comparisons [Array<Integer>] The list of valid comparison results expected.
19
55
  def initialize(comparison, expected_comparisons)
20
56
  super("Invalid comparison result: `#{comparison}`. Must be one of: #{expected_comparisons.join('`, `')}.")
21
57
  end
22
58
  end
23
59
 
60
+ # Error raised when attempting to navigate beyond the last step in the onboarding process.
24
61
  class LastStepError < Error
62
+ # Initializes a new LastStepError indicating that the end of the step sequence has been reached.
63
+ #
64
+ # @param step [String] The last step that was attempted to be surpassed.
65
+ # @param expected_steps [Array<String>] The complete list of valid steps in the onboarding process.
25
66
  def initialize(step, expected_steps)
26
67
  super("Currently `#{step}` the last step. Available steps are: `#{expected_steps.join('`, `')}`.")
27
68
  end
28
69
  end
29
70
 
71
+ # Error raised when attempting to navigate before the first step in the onboarding process.
30
72
  class FirstStepError < Error
73
+ # Initializes a new FirstStepError indicating that the beginning of the step sequence has been reached.
74
+ #
75
+ # @param step [String] The first step that was attempted to be preceded.
76
+ # @param expected_steps [Array<String>] The complete list of valid steps in the onboarding process.
31
77
  def initialize(step, expected_steps)
32
78
  super("Currently `#{step}` the first step. Available steps are: `#{expected_steps.join('`, `')}`.")
33
79
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Onboardable
4
+ module List
5
+ # The List class manages a sequence of steps in an onboarding process, tracking progress and current state.
6
+ class Base
7
+ include Navigation
8
+
9
+ # @return [Array<Step>] the steps in the list
10
+ attr_reader :steps
11
+
12
+ # @return [Step] the current step in the list
13
+ attr_reader :current_step
14
+
15
+ # Initializes a new instance of List with steps and a current step.
16
+ #
17
+ # @param steps [Array<Step>] An array of steps comprising the onboarding process.
18
+ # @param current_step [Step] The step currently active in the process.
19
+ def initialize(steps, current_step)
20
+ self.steps = steps
21
+ self.current_step = current_step
22
+ end
23
+
24
+ # Calculates and returns the onboarding progress as a percentage.
25
+ #
26
+ # @return [Float] The completion percentage of the onboarding process.
27
+ def progress
28
+ (step_index!(current_step).to_f / steps.size) * 100
29
+ end
30
+
31
+ private
32
+
33
+ # Sets and validates the steps array, ensuring it is an Array of Step objects.
34
+ #
35
+ # @param steps [Array<Step>] The steps to be assigned to the list.
36
+ def steps=(steps)
37
+ @steps = Array(Array.try_convert(steps)).freeze
38
+ end
39
+
40
+ # Updates the current step and recalibrates the status of all steps in the list.
41
+ #
42
+ # @param raw_current_step [Step] The new current step to set.
43
+ def current_step=(raw_current_step)
44
+ current_step_index = step_index!(raw_current_step)
45
+ steps.each_with_index { |step, index| step.update_status!(index <=> current_step_index) }
46
+ @current_step = steps.fetch(current_step_index)
47
+ end
48
+
49
+ # Determines the index of a given step in the list, ensuring the step exists.
50
+ #
51
+ # @param raw_step [Step] The step for which the index is requested.
52
+ # @return [Integer] The index of the step within the list.
53
+ # @raise [InvalidStepError] Raises an error if the step does not exist in the list.
54
+ def step_index!(raw_step)
55
+ steps.index { |step| step == raw_step } || raise(InvalidStepError.new(raw_step.to_str, steps.map(&:to_str)))
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Onboardable
4
+ module List
5
+ # The Builder class constructs and manages an onboarding step list, adding steps and building the final list.
6
+ class Builder
7
+ include Utils::Warnings
8
+
9
+ # @return [Hash] A hash where keys are step names and values are Step objects.
10
+ attr_reader :steps
11
+
12
+ # @return [Step] The current step in the building process, defaulting to the first added step.
13
+ attr_accessor :current_step
14
+
15
+ # Initializes a new instance of ListBuilder.
16
+ def initialize
17
+ self.steps = {}
18
+ end
19
+
20
+ # Creates a new Step object and adds it to the builder.
21
+ #
22
+ # @param name [String] The name of the step.
23
+ # @param data [Hash] The data associated with the step.
24
+ # @return [Step] The created step.
25
+ def create_step(name, data = {})
26
+ Step.new(name, data).tap { |step| add_step(step) }
27
+ end
28
+ alias step create_step
29
+
30
+ # Converts a class to a Step object and adds it to the builder.
31
+ #
32
+ # @param klass [Class] The class to be converted to a step.
33
+ # @raise [UndefinedMethodError] if the conversion method is not defined for the class.
34
+ def create_step_from!(klass)
35
+ Step.try_convert(klass).tap do |step|
36
+ add_step(step || raise(UndefinedMethodError.new(klass, Step::CONVERSION_METHOD)))
37
+ end
38
+ end
39
+ alias step_from create_step_from!
40
+
41
+ # Constructs a new List object from the steps added to the builder.
42
+ #
43
+ # @param current_step_name [String, nil] The name of the step to mark as current in the built list. Can be nil.
44
+ # @return [Base] A new List object initialized with the steps and the specified current step.
45
+ # @raise [EmptyStepsError] if no steps have been added to the builder.
46
+ def build!(current_step_name)
47
+ Base.new(convert_to_steps!, convert_to_step!(current_step_name || current_step.name))
48
+ end
49
+
50
+ private
51
+
52
+ # Adds a step to the builder.
53
+ #
54
+ # @param step [Step] The step to be added.
55
+ def add_step(step)
56
+ step.name.then do |name|
57
+ warn_about_override(name) if steps.key?(name)
58
+ steps[name] = step
59
+ end
60
+
61
+ self.current_step ||= step
62
+ end
63
+
64
+ # Assigns a hash of steps to the builder.
65
+ #
66
+ # @param raw_steps [Hash] The hash of steps to be assigned.
67
+ def steps=(raw_steps)
68
+ @steps = Hash(Hash.try_convert(raw_steps))
69
+ end
70
+
71
+ # Converts the internal hash of steps to an array of Step objects.
72
+ #
73
+ # @return [Array<Step>] An array of steps.
74
+ # @raise [EmptyStepsError] Raises if there are no steps to convert.
75
+ def convert_to_steps!
76
+ raise EmptyStepsError if steps.empty?
77
+
78
+ steps.values
79
+ end
80
+
81
+ # Retrieves a Step object from the builder's steps based on the step name.
82
+ #
83
+ # @param name [String] The name of the step to be converted to a Step object.
84
+ # @return [Step] The corresponding Step object.
85
+ # @raise [InvalidStepError] Raises if the specified step name is not present in the steps hash.
86
+ def convert_to_step!(name)
87
+ steps[name] || raise(InvalidStepError.new(name, steps.keys))
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Onboardable
4
+ module List
5
+ # The Navigation module provides methods for navigating through the steps of the onboarding process.
6
+ module Navigation
7
+ # Returns the next step in the onboarding process.
8
+ #
9
+ # @return [Step, nil] The next step in the list or nil if the current step is the last one.
10
+ def next_step
11
+ current_index = step_index!(current_step)
12
+ steps[current_index.next]
13
+ end
14
+
15
+ # Moves the current step pointer to the next step in the onboarding process.
16
+ #
17
+ # @raise [LastStepError] if the current step is the last step and there is no next step to move to.
18
+ def next_step!
19
+ self.current_step = next_step || raise(LastStepError.new(current_step, steps.map(&:to_str)))
20
+ end
21
+
22
+ # Returns the previous step in the onboarding process.
23
+ #
24
+ # @return [Step, nil] The previous step in the list or nil if the current step is the first one.
25
+ def prev_step
26
+ current_index = step_index!(current_step)
27
+ current_index.positive? ? steps[current_index.pred] : nil
28
+ end
29
+
30
+ # Moves the current step pointer to the previous step in the onboarding process.
31
+ #
32
+ # @raise [FirstStepError] if the current step is the first step and there is no previous step to move to.
33
+ def prev_step!
34
+ self.current_step = prev_step || raise(FirstStepError.new(current_step, steps.map(&:to_str)))
35
+ end
36
+
37
+ # Checks if the specified step is the first step in the onboarding process.
38
+ #
39
+ # @param step [Step] The step to check (defaults to the current step if not specified).
40
+ # @return [Boolean] True if the specified step is the first step, false otherwise.
41
+ def first_step?(step = current_step)
42
+ step == first_step
43
+ end
44
+
45
+ # Checks if the specified step is the last step in the onboarding process.
46
+ #
47
+ # @param step [Step] The step to check (defaults to the current step if not specified).
48
+ # @return [Boolean] True if the specified step is the last step, false otherwise.
49
+ def last_step?(step = current_step)
50
+ step == last_step
51
+ end
52
+
53
+ # Retrieves the first step in the onboarding process.
54
+ #
55
+ # @return [Step] The first step in the list.
56
+ def first_step
57
+ steps.fetch(0)
58
+ end
59
+
60
+ # Retrieves the last step in the onboarding process.
61
+ #
62
+ # @return [Step] The last step in the list.
63
+ def last_step
64
+ steps.fetch(-1)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,56 +1,105 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Onboardable
4
+ # Represents a single step within an onboarding process, including its status and associated data.
4
5
  class Step
6
+ CONVERSION_METHOD = :to_onboarding_step
7
+
5
8
  PENDING_STATUS = :pending
6
9
  CURRENT_STATUS = :current
7
10
  COMPLETED_STATUS = :completed
11
+
8
12
  DEFAULT_STATUS = PENDING_STATUS
13
+
9
14
  STATUSES = [PENDING_STATUS, CURRENT_STATUS, COMPLETED_STATUS].freeze
10
15
 
11
- attr_reader :name, :data, :status
16
+ class << self
17
+ def try_convert(value)
18
+ return unless value.respond_to?(CONVERSION_METHOD)
19
+
20
+ value.public_send(CONVERSION_METHOD).then do |step|
21
+ step.is_a?(Step) ? step : raise(StepConversionError.new(value, step))
22
+ end
23
+ end
24
+ end
25
+
26
+ # @return [String] the name of the step
27
+ attr_reader :name
28
+
29
+ # @return [Hash] custom data associated with the step
30
+ attr_reader :data
12
31
 
32
+ # @return [Symbol] the current status of the step
33
+ attr_reader :status
34
+
35
+ # Initializes a new Step with a name, optional custom data, and a default status.
36
+ #
37
+ # @param name [String] the name of the step
38
+ # @param data [Hash] the custom data associated with the step, defaults to an empty hash
13
39
  def initialize(name, data = {})
14
40
  self.name = name
15
41
  self.data = data
16
-
17
42
  self.status = DEFAULT_STATUS
18
43
  end
19
44
 
20
45
  STATUSES.each do |status_method|
46
+ # @!method {status_method}?
47
+ # Checks if the step is in a specific status.
48
+ #
49
+ # @return [Boolean] true if the step is currently in the {status_method} status, false otherwise.
21
50
  define_method :"#{status_method}?" do
22
51
  status == status_method
23
52
  end
24
53
  end
25
54
 
55
+ # Compares this step to another to determine if they are equivalent, based on the step name.
56
+ #
57
+ # @param other [Step] the step to compare with
58
+ # @return [Boolean] true if both steps have the same name, false otherwise
26
59
  def ==(other)
27
60
  to_str == other.to_str
28
61
  end
29
62
 
63
+ # Provides a string representation of the step, using its name.
64
+ #
65
+ # @return [String] the name of the step
30
66
  def to_str
31
67
  name
32
68
  end
33
69
 
70
+ # Updates the status of the step based on a specified comparison result.
71
+ #
72
+ # @param comparison_result [Integer] the result of a comparison with the current step (-1, 0, or 1)
73
+ # @raise [ComparisonResultError] if the comparison result is not -1, 0, or 1
34
74
  def update_status!(comparison_result)
35
75
  self.status = case comparison_result
36
76
  when -1 then COMPLETED_STATUS
37
77
  when 0 then CURRENT_STATUS
38
78
  when 1 then PENDING_STATUS
39
79
  else
40
- raise InvalidComparisonResultError.new(comparison_result, [-1, 0, 1])
80
+ raise ComparisonResultError.new(comparison_result, [-1, 0, 1])
41
81
  end
42
82
  end
43
83
 
44
84
  private
45
85
 
86
+ # Sets the name of the step, ensuring it is a valid String.
87
+ #
88
+ # @param raw_name [String] the raw name of the step
46
89
  def name=(raw_name)
47
90
  @name = String.new(String.try_convert(raw_name)).freeze
48
91
  end
49
92
 
93
+ # Sets the custom data for the step, ensuring it is a valid Hash.
94
+ #
95
+ # @param raw_data [Hash] the raw custom data
50
96
  def data=(raw_data)
51
97
  @data = Hash(Hash.try_convert(raw_data)).freeze
52
98
  end
53
99
 
100
+ # Sets the status of the step.
101
+ #
102
+ # @param status [Symbol] the new status of the step
54
103
  attr_writer :status
55
104
  end
56
105
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Onboardable
4
+ module Utils
5
+ # The Warnings module provides utility methods for issuing warnings.
6
+ module Warnings
7
+ private
8
+
9
+ # Issues a warning when a step with the same name already exists and will be overridden.
10
+ #
11
+ # @param name [String] The name of the step that will be overridden.
12
+ # @return [void]
13
+ def warn_about_override(name)
14
+ warn "Step `#{name}` already exists and will be overridden.", uplevel: 1
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Onboardable
4
- VERSION = '1.0.1'
4
+ VERSION = '1.1.1'
5
5
  end
data/lib/onboardable.rb CHANGED
@@ -1,32 +1,59 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'onboardable/utils/navigation'
4
3
  require_relative 'onboardable/errors'
5
- require_relative 'onboardable/list'
6
- require_relative 'onboardable/list_builder'
4
+ require_relative 'onboardable/utils/warnings'
5
+ require_relative 'onboardable/list/navigation'
6
+ require_relative 'onboardable/list/builder'
7
+ require_relative 'onboardable/list/base'
7
8
  require_relative 'onboardable/step'
8
9
  require_relative 'onboardable/version'
9
10
 
11
+ # The Onboardable module provides a DSL for defining and navigating onboarding steps.
10
12
  module Onboardable
13
+ # Initializes the Onboardable module when included in a class, extending it with class and instance methods.
14
+ #
15
+ # @param klass [Module] the class including the Onboardable module
16
+ # @return [untyped]
11
17
  def self.included(klass)
12
18
  klass.extend ClassMethods
13
19
  klass.include InstanceMethods
14
20
  end
15
21
 
22
+ # Class methods for managing the onboarding process, added to the class that includes the Onboardable module.
16
23
  module ClassMethods
24
+ # Retrieves or initializes a ListBuilder for onboarding steps at the class level.
25
+ #
26
+ # @return [List::Builder] the ListBuilder associated with the class
17
27
  def list_builder
18
- @list_builder ||= ListBuilder.new
28
+ @list_builder ||= List::Builder.new
19
29
  end
20
30
 
31
+ # Configures onboarding steps via a ListBuilder with a provided block.
32
+ #
33
+ # @yield [List::Builder] executes block in the context of List::Builder
34
+ # @return [Step] the current step in the building process
21
35
  def list_builder=(&block)
22
36
  list_builder.instance_eval(&block)
23
37
  end
24
38
  alias has_onboarding list_builder=
39
+
40
+ # Builds the onboarding list and optionally sets the current step.
41
+ #
42
+ # @param current_step_name [String, nil] the name of the current step, if specified
43
+ # @return [List::Base] the List built from the class's ListBuilder
44
+ def onboarding(current_step_name = nil)
45
+ list_builder.build!(current_step_name)
46
+ end
25
47
  end
26
48
 
49
+ # Instance methods for onboarding navigation, added to classes including Onboardable.
27
50
  module InstanceMethods
51
+ # Builds the onboarding list and optionally sets the current step.
52
+ #
53
+ # @param current_step_name [String, nil] the name of the current step, if specified
54
+ # @return [List::Base] the List built from the class's ListBuilder
28
55
  def onboarding(current_step_name = nil)
29
- self.class.list_builder.build!(current_step_name)
56
+ self.class.onboarding(current_step_name)
30
57
  end
31
58
  end
32
59
  end
@@ -0,0 +1,32 @@
1
+ module Onboardable
2
+ class Error < StandardError
3
+ end
4
+
5
+ class UndefinedMethodError < Error
6
+ def initialize: (Class, Symbol | String) -> void
7
+ end
8
+
9
+ class StepConversionError < Error
10
+ def initialize: (Class, untyped) -> void
11
+ end
12
+
13
+ class EmptyStepsError < Error
14
+ def initialize: () -> void
15
+ end
16
+
17
+ class InvalidStepError < Error
18
+ def initialize: (String, Array[String]) -> void
19
+ end
20
+
21
+ class ComparisonResultError < Error
22
+ def initialize: (Integer, Array[Integer]) -> void
23
+ end
24
+
25
+ class LastStepError < Error
26
+ def initialize: (String, Array[String]) -> void
27
+ end
28
+
29
+ class FirstStepError < Error
30
+ def initialize: (String, Array[String]) -> void
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ module Onboardable
2
+ module List
3
+ class Base
4
+ attr_reader steps: Array[Step]
5
+ attr_reader current_step: Step
6
+
7
+ def initialize: (Array[Step], Step) -> instance
8
+
9
+ def progress: -> Float
10
+
11
+ private
12
+
13
+ attr_writer steps: Array[Step]
14
+ attr_writer current_step: Step
15
+
16
+ def step_index!: (Step)-> Integer
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ module Onboardable
2
+ module List
3
+ class Builder
4
+ attr_reader steps: Hash[String, Step]
5
+ attr_accessor current_step: Step
6
+
7
+ def initialize: () -> Hash[String, Step]
8
+
9
+ def create_step: (String, Hash[Symbol, untyped]) -> Step
10
+ alias step create_step
11
+
12
+ def create_step_from!: (Class) -> Step
13
+ alias step_from create_step_from!
14
+
15
+ def build!: (String?) -> Base
16
+
17
+ private
18
+
19
+ attr_writer steps: Hash[String, Step]
20
+
21
+ def add_step: (Step) -> Step
22
+
23
+ def convert_to_step!: (String) -> Step
24
+
25
+ def convert_to_steps!: -> Array[Step]
26
+ end
27
+ end
28
+ end
@@ -1,5 +1,5 @@
1
1
  module Onboardable
2
- module Utils
2
+ module List
3
3
  module Navigation
4
4
  def next_step: () -> Step?
5
5
  def next_step!: () -> Step
@@ -1,11 +1,14 @@
1
1
  module Onboardable
2
2
  class Step
3
+ CONVERSION_METHOD: Symbol
3
4
  PENDING_STATUS: Symbol
4
5
  CURRENT_STATUS: Symbol
5
6
  COMPLETED_STATUS: Symbol
6
7
  DEFAULT_STATUS: Symbol
7
8
  STATUSES: Array[Symbol]
8
9
 
10
+ def self.try_convert: -> Step?
11
+
9
12
  attr_reader name: String
10
13
  attr_reader data: Hash[Symbol, untyped]
11
14
  attr_reader status: Symbol
@@ -0,0 +1,9 @@
1
+ module Onboardable
2
+ module Utils
3
+ module Warnings
4
+ private
5
+
6
+ def warn_about_override: (Hash[String, Step]) -> void
7
+ end
8
+ end
9
+ end
data/sig/onboardable.rbs CHANGED
@@ -3,13 +3,15 @@ module Onboardable
3
3
  # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
4
 
5
5
  module ClassMethods
6
- attr_reader list_builder: ListBuilder
6
+ attr_reader list_builder: List::Builder
7
7
 
8
- def list_builder=: () { () -> ListBuilder } -> Step
8
+ def list_builder=: () { () -> List::Builder } -> Step
9
9
  alias has_onboarding list_builder=
10
+
11
+ def onboarding: (String?) -> List::Base
10
12
  end
11
13
 
12
14
  module InstanceMethods
13
- def onboarding: (String?) -> List
15
+ def onboarding: (String?) -> List::Base
14
16
  end
15
17
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: onboardable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Artem Skrynnyk
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-05-09 00:00:00.000000000 Z
11
+ date: 2024-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -53,7 +53,7 @@ dependencies:
53
53
  version: '1.63'
54
54
  - - ">="
55
55
  - !ruby/object:Gem::Version
56
- version: 1.63.4
56
+ version: 1.63.5
57
57
  type: :development
58
58
  prerelease: false
59
59
  version_requirements: !ruby/object:Gem::Requirement
@@ -63,7 +63,7 @@ dependencies:
63
63
  version: '1.63'
64
64
  - - ">="
65
65
  - !ruby/object:Gem::Version
66
- version: 1.63.4
66
+ version: 1.63.5
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: rubocop-performance
69
69
  requirement: !ruby/object:Gem::Requirement
@@ -144,16 +144,19 @@ files:
144
144
  - Rakefile
145
145
  - lib/onboardable.rb
146
146
  - lib/onboardable/errors.rb
147
- - lib/onboardable/list.rb
148
- - lib/onboardable/list_builder.rb
147
+ - lib/onboardable/list/base.rb
148
+ - lib/onboardable/list/builder.rb
149
+ - lib/onboardable/list/navigation.rb
149
150
  - lib/onboardable/step.rb
150
- - lib/onboardable/utils/navigation.rb
151
+ - lib/onboardable/utils/warnings.rb
151
152
  - lib/onboardable/version.rb
152
153
  - sig/onboardable.rbs
153
- - sig/onboardable/list.rbs
154
- - sig/onboardable/list_builder.rbs
154
+ - sig/onboardable/errors.rbs
155
+ - sig/onboardable/list/base.rbs
156
+ - sig/onboardable/list/builder.rbs
157
+ - sig/onboardable/list/navigation.rbs
155
158
  - sig/onboardable/step.rbs
156
- - sig/onboardable/utils/navigation.rbs
159
+ - sig/onboardable/utils/warnings.rbs
157
160
  homepage: https://github.com/dmrAnderson/onboardable
158
161
  licenses:
159
162
  - MIT
@@ -161,7 +164,7 @@ metadata:
161
164
  homepage_uri: https://github.com/dmrAnderson/onboardable
162
165
  changelog_uri: https://github.com/dmrAnderson/onboardable/blob/main/CHANGELOG.md
163
166
  bug_tracker_uri: https://github.com/dmrAnderson/onboardable/issues
164
- documentation_uri: https://github.com/dmrAnderson/onboardable/blob/main/README.md
167
+ documentation_uri: https://rubydoc.info/gems/onboardable
165
168
  rubygems_mfa_required: 'true'
166
169
  post_install_message:
167
170
  rdoc_options: []
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Onboardable
4
- class List
5
- include Utils::Navigation
6
-
7
- attr_reader :steps, :current_step
8
-
9
- def initialize(steps, current_step)
10
- self.steps = steps
11
- self.current_step = current_step
12
- end
13
-
14
- def progress
15
- (step_index!(current_step).to_f / steps.size) * 100
16
- end
17
-
18
- private
19
-
20
- def steps=(raw_steps)
21
- Array(Array.try_convert(raw_steps)).then do |converted_steps|
22
- raise EmptyListError if converted_steps.empty?
23
-
24
- @steps = converted_steps.freeze
25
- end
26
- end
27
-
28
- def current_step=(raw_current_step)
29
- current_step_index = step_index!(raw_current_step)
30
- steps.each_with_index { |step, index| step.update_status!(index <=> current_step_index) }
31
- @current_step = steps.fetch(current_step_index)
32
- end
33
-
34
- def step_index!(raw_step)
35
- steps.index { |step| step == raw_step } || raise(InvalidStepError.new(raw_step, steps.map(&:to_str)))
36
- end
37
- end
38
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Onboardable
4
- class ListBuilder
5
- attr_reader :steps
6
- attr_accessor :current_step
7
-
8
- def initialize
9
- self.steps = {}
10
- end
11
-
12
- def add_step(name, data = {})
13
- Step.new(name, data).tap do |step|
14
- steps[name] = step
15
- self.current_step ||= step
16
- end
17
- end
18
- alias step add_step
19
-
20
- def build!(current_step_name)
21
- List.new(steps.values, convert_to_step!(current_step_name))
22
- end
23
-
24
- private
25
-
26
- def steps=(raw_steps)
27
- @steps = Hash(Hash.try_convert(raw_steps))
28
- end
29
-
30
- def convert_to_step!(name)
31
- return current_step unless name
32
-
33
- steps[name] || raise(InvalidStepError.new(name, steps.keys))
34
- end
35
- end
36
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Onboardable
4
- module Utils
5
- module Navigation
6
- def next_step
7
- current_index = step_index!(current_step)
8
-
9
- steps[current_index.next]
10
- end
11
-
12
- def next_step!
13
- self.current_step = next_step || raise(LastStepError.new(current_step, steps))
14
- end
15
-
16
- def prev_step
17
- current_index = step_index!(current_step)
18
-
19
- current_index.positive? ? steps[current_index.pred] : nil
20
- end
21
-
22
- def prev_step!
23
- self.current_step = prev_step || raise(FirstStepError.new(current_step, steps))
24
- end
25
-
26
- def first_step?(step = current_step)
27
- step == first_step
28
- end
29
-
30
- def last_step?(step = current_step)
31
- step == last_step
32
- end
33
-
34
- def first_step
35
- steps.fetch(0)
36
- end
37
-
38
- def last_step
39
- steps.fetch(-1)
40
- end
41
- end
42
- end
43
- end
@@ -1,17 +0,0 @@
1
- module Onboardable
2
- class List
3
- attr_reader steps: Array[Step]
4
- attr_reader current_step: Step
5
-
6
- def initialize: (Array[Step], Step) -> instance
7
-
8
- def progress: -> Float
9
-
10
- private
11
-
12
- attr_writer steps: Array[Step]
13
- attr_writer current_step: Step
14
-
15
- def step_index!: (Step)-> Integer
16
- end
17
- end
@@ -1,19 +0,0 @@
1
- module Onboardable
2
- class ListBuilder
3
- attr_reader steps: Hash[String, Step]
4
- attr_accessor current_step: Step
5
-
6
- def initialize: () -> Hash[String, Step]
7
-
8
- def add_step: (String, Hash[Symbol, untyped]) -> Step
9
- alias step add_step
10
-
11
- def build!: (String?) -> List
12
-
13
- private
14
-
15
- attr_writer steps: Hash[String, Step]
16
-
17
- def convert_to_step!: (String?) -> Step
18
- end
19
- end