has_state_machine 1.1.0 → 1.2.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: 902498121bd57b97de373beaa68557c3591de8a221d524af5b9033be1cca8a73
4
- data.tar.gz: fb4806d204772d488c4fb2bc69360cd989edf4c8a5312508b37868d961adf059
3
+ metadata.gz: d311fd40183ff49f406df6216c0a48f910f05bae0d91dc8a77d1456cfbafadd5
4
+ data.tar.gz: a08e788ca3012cb4a74570d463f0c30344b476732702848184439efaa3a6b79a
5
5
  SHA512:
6
- metadata.gz: 1baeee9d74f64325427b52e2e867cab05db7848f384316b8175cbb5d49a2c4030dcf905020ac8091c495f6cfa20f8dcae657bcffd78b86987e9c867665f5cf42
7
- data.tar.gz: 7e998865b342452f802266b3408bd673b26fb622672ed48ff18cb384259c34a7ec1f591a9ed99efb12f33364e5a1c9bff33d9b95bd4fbac3794ff9d3f2ccdd25
6
+ metadata.gz: a6cd82cab96a81efd97f3495770baa6542c65f23ae602d00362c27b6517779df747b330460f833eb318e313e23e40318ef32c0fe4c9685a3c587df6912a4e6ab
7
+ data.tar.gz: 86620a6b8db88cfd2a267c06197269cea8f7320f2cc0cc70829d04e9d00736b276609af2bf0ba93d376d079aa2e6afcd16bb4bad0059f707a1bd63c7edff0856
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # HasStateMachine
2
2
 
3
3
  [![CI](https://github.com/beehiiv/has_state_machine/actions/workflows/ci.yml/badge.svg)](https://github.com/beehiiv/has_state_machine/actions/workflows/ci.yml)
4
- [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
4
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop/rubocop)
5
5
 
6
6
  HasStateMachine uses ruby classes to make creating a finite state machine for your ActiveRecord models a breeze.
7
7
 
@@ -98,8 +98,10 @@ module Workflow
98
98
  end
99
99
 
100
100
  # after_transition_commit runs only once the transition has been
101
- # committed: after the record is saved for normal transitions, and
102
- # outside the transaction for transactional transitions (see below).
101
+ # committed to the database: after the OUTERMOST database transaction
102
+ # commits, or immediately after the transition when no transaction is
103
+ # open. It never runs if the transition or a surrounding transaction
104
+ # rolls back. (Requires Rails 7.2+; see Advanced Usage below.)
103
105
  after_transition_commit do
104
106
  MyJob.perform_later(object)
105
107
  end
@@ -232,6 +234,20 @@ module Workflow
232
234
  end
233
235
  ```
234
236
 
237
+ #### `after_transition_commit` Semantics
238
+
239
+ `after_transition_commit` callbacks run after the outermost database transaction commits. In detail:
240
+
241
+ - If no transaction is open when `transition_to` is called, they run immediately after the transition commits.
242
+ - If the transition happens inside an open transaction — an app-level `ActiveRecord::Base.transaction`, or a nested transition triggered from another state's `before_transition`/`after_transition` — they are deferred until that outermost transaction commits.
243
+ - They never run if the transition fails, if `rollback_transition` is raised, or if a surrounding transaction rolls back after the transition. Multiple transitions inside one transaction each fire their callbacks after commit, in transition order.
244
+
245
+ This makes `after_transition_commit` the safe place to enqueue background jobs, call external APIs, etc.: the work only happens once the new state is actually visible to other database connections.
246
+
247
+ **Note on Rails versions:** deferral to an outer transaction requires Rails 7.2+. On older Rails versions the callbacks fire as soon as the transition itself completes, which may be inside an uncommitted outer transaction.
248
+
249
+ **Footgun: `ActiveRecord.after_all_transactions_commit`** does not work as a substitute inside `before_transition`/`after_transition`. Rails skips non-joinable transactions when deciding whether any transaction is open, and transactional transitions run inside the gem's own non-joinable transaction — so the block yields *immediately*, while the transition is still uncommitted. Use `after_transition_commit` instead.
250
+
235
251
  #### Transient Transition Variables
236
252
 
237
253
  Sometimes you may may want to pass additional arguments to a state transition for additional context in your transition callbacks. To do this, add the `transients` option to the `state_options` declaration. This allows you to define any additional attributes you want to be able to pass along during a state transition to that state.
data/Rakefile CHANGED
@@ -17,11 +17,11 @@ namespace :test do
17
17
  end
18
18
 
19
19
  task :test do
20
- sh "bin/test"
20
+ sh "bin/rspec"
21
21
  end
22
22
 
23
23
  task :lint do
24
- sh "bin/standardrb --no-fix"
24
+ sh "bin/rubocop --no-server"
25
25
  end
26
26
 
27
27
  namespace :changelog do
@@ -10,7 +10,9 @@ class String
10
10
  #
11
11
  # @example
12
12
  # "some random string".transition_to("draft") #=> false
13
- def transition_to(_desired_state)
13
+ # rubocop:disable Naming/PredicateMethod -- mirrors HasStateMachine::State#transition_to API
14
+ def transition_to(_desired_state, **_options)
14
15
  false
15
16
  end
17
+ # rubocop:enable Naming/PredicateMethod
16
18
  end
@@ -16,8 +16,8 @@ module HasStateMachine
16
16
  define_model_callbacks :transition, only: %i[before after]
17
17
 
18
18
  ##
19
- # Defines the after_transition_commit callback. It runs only after a
20
- # transition has committed.
19
+ # Defines the after_transition_commit callback, which runs once a
20
+ # successful transition is committed to the database.
21
21
  define_model_callbacks :transition_commit, only: %i[after]
22
22
 
23
23
  ##
@@ -63,7 +63,7 @@ module HasStateMachine
63
63
  # @return [Boolean] whether or not the transition took place
64
64
  def transition_to(desired_state, **options)
65
65
  transitioned = false
66
- options = options.transform_keys(&:to_sym)
66
+ options = options.symbolize_keys
67
67
  desired_state_instance = state_instance(desired_state, options)
68
68
 
69
69
  with_transition_options(options) do
@@ -78,45 +78,66 @@ module HasStateMachine
78
78
 
79
79
  transitioned
80
80
  ensure
81
- (desired_state_instance&.errors || []).each do |error|
81
+ desired_state_instance&.errors&.each do |error|
82
82
  object.errors.add(error.attribute, error.type)
83
83
  end
84
84
  end
85
85
 
86
86
  ##
87
87
  # Makes the actual transition from one state to the next and
88
- # runs the before and after transition callbacks. The
89
- # after_transition_commit callbacks run after the update completes
90
- # and only when it succeeds.
91
- def perform_transition!
92
- run_callbacks :transition_commit do
93
- run_callbacks :transition do
94
- object.update("#{object.state_attribute}": state)
95
- end
88
+ # runs the before and after transition callbacks.
89
+ #
90
+ # @return [Boolean] whether or not the transition succeeded
91
+ def perform_transition! # rubocop:disable Naming/PredicateMethod -- public API
92
+ transitioned = run_callbacks :transition do
93
+ object.update("#{object.state_attribute}": state)
96
94
  end
95
+ return false unless transitioned
96
+
97
+ @previous_state = previous_state
98
+ enqueue_transition_commit_callbacks
99
+ true
97
100
  end
98
101
 
99
102
  ##
100
- # Makes the actual transition from one state to the next and
101
- # runs the before and after transition callbacks in a transaction
102
- # to allow for roll backs. The after_transition_commit callbacks run
103
- # outside the transaction and only when it commits (not on rollback).
104
- def perform_transactional_transition!
105
- run_callbacks :transition_commit do
106
- ActiveRecord::Base.transaction(requires_new: true, joinable: false) do
107
- run_callbacks :transition do
108
- rollback_transition unless object.update("#{object.state_attribute}": state)
109
- end
103
+ # Same as {#perform_transition!}, but wrapped in a transaction so
104
+ # callbacks can roll the transition back.
105
+ #
106
+ # @return [Boolean] whether or not the transition succeeded
107
+ def perform_transactional_transition! # rubocop:disable Naming/PredicateMethod -- public API
108
+ ActiveRecord::Base.transaction(requires_new: true, joinable: false) do
109
+ run_callbacks :transition do
110
+ rollback_transition unless object.update("#{object.state_attribute}": state)
110
111
  end
112
+ end
111
113
 
112
- @previous_state = previous_state
114
+ @previous_state = previous_state
115
+ return false unless object.reload.public_send(object.state_attribute) == state
113
116
 
114
- object.reload.public_send(object.state_attribute) == state
115
- end
117
+ enqueue_transition_commit_callbacks
118
+ true
116
119
  end
117
120
 
118
121
  private
119
122
 
123
+ ##
124
+ # Runs the after_transition_commit callbacks once the outermost open
125
+ # transaction commits, discarding them on rollback.
126
+ #
127
+ # @note Registers on the connection's current transaction because
128
+ # +ActiveRecord.after_all_transactions_commit+ ignores non-joinable
129
+ # transactions (like the gem's own) and would fire immediately.
130
+ # @return [void]
131
+ def enqueue_transition_commit_callbacks
132
+ current_transaction = object.class.connection.current_transaction
133
+
134
+ # Rails < 7.2 has no Transaction#after_commit, so callbacks fire
135
+ # immediately with no deferral. Drop this guard at Rails 7.2+.
136
+ return run_callbacks(:transition_commit) { true } unless current_transaction.respond_to?(:after_commit)
137
+
138
+ current_transaction.after_commit { run_callbacks(:transition_commit) { true } }
139
+ end
140
+
120
141
  def rollback_transition
121
142
  raise ActiveRecord::Rollback
122
143
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HasStateMachine
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.1"
5
5
  end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby_lsp/addon"
4
+
5
+ require "has_state_machine/version"
6
+ require_relative "definition"
7
+
8
+ ::RubyLsp::Addon.depend_on_ruby_lsp!(">= 0.18.0", "< 1.0") if ::RubyLsp::Addon.respond_to?(:depend_on_ruby_lsp!)
9
+
10
+ module RubyLsp
11
+ module HasStateMachine
12
+ class Addon < ::RubyLsp::Addon
13
+ def activate(global_state, outgoing_queue)
14
+ @global_state = global_state
15
+ @rails_client = register_rails_server_addon(outgoing_queue)
16
+ end
17
+
18
+ def deactivate
19
+ @global_state = nil
20
+ @rails_client = nil
21
+ end
22
+
23
+ def name
24
+ "Has State Machine"
25
+ end
26
+
27
+ def version
28
+ ::HasStateMachine::VERSION
29
+ end
30
+
31
+ def create_definition_listener(response_builder, uri, node_context, dispatcher)
32
+ Definition.new(
33
+ response_builder,
34
+ uri,
35
+ node_context,
36
+ dispatcher,
37
+ index: @global_state&.index,
38
+ rails_client: @rails_client
39
+ )
40
+ end
41
+
42
+ private
43
+
44
+ def register_rails_server_addon(outgoing_queue)
45
+ register_rails_runner_client
46
+ rescue => error
47
+ handle_rails_registration_error(outgoing_queue, error)
48
+ end
49
+
50
+ def register_rails_runner_client
51
+ rails_addon = ::RubyLsp::Addon.get("Ruby LSP Rails", ">= 0") if ::RubyLsp::Addon.respond_to?(:get)
52
+ return unless rails_addon&.respond_to?(:rails_runner_client)
53
+
54
+ client = rails_addon.rails_runner_client
55
+ return unless client.respond_to?(:register_server_addon)
56
+
57
+ client.register_server_addon(File.expand_path("rails_server_addon.rb", __dir__))
58
+ client
59
+ end
60
+
61
+ def handle_rails_registration_error(outgoing_queue, error)
62
+ unless addon_not_found?(error)
63
+ log(outgoing_queue, "Has State Machine Ruby LSP Rails integration unavailable: #{error.message}")
64
+ end
65
+
66
+ nil
67
+ end
68
+
69
+ def addon_not_found?(error)
70
+ if defined?(::RubyLsp::Addon::AddonNotFoundError)
71
+ return true if error.is_a?(::RubyLsp::Addon::AddonNotFoundError)
72
+ end
73
+
74
+ error.class.name&.end_with?("AddonNotFoundError")
75
+ end
76
+
77
+ def log(outgoing_queue, message)
78
+ return if outgoing_queue.nil? || outgoing_queue.closed?
79
+ return unless defined?(::RubyLsp::Notification)
80
+
81
+ outgoing_queue << ::RubyLsp::Notification.window_log_message(message)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "workflow_resolver"
4
+
5
+ module RubyLsp
6
+ module HasStateMachine
7
+ class Definition
8
+ SERVER_ADDON_NAME = "has_state_machine"
9
+
10
+ def initialize(response_builder, _uri, node_context, dispatcher, index: nil, rails_client: nil,
11
+ model_name_cache: nil)
12
+ @response_builder = response_builder
13
+ @node_context = node_context
14
+ @index = index
15
+ @rails_client = rails_client
16
+ @model_name_cache = model_name_cache
17
+
18
+ dispatcher.register(self, :on_call_node_enter)
19
+ end
20
+
21
+ def on_call_node_enter(node)
22
+ return unless current_target?(node)
23
+
24
+ # Check the node shape first: resolving the model name may require a
25
+ # round-trip to the Rails runner, so only pay it for `object` calls.
26
+ if object_call?(node)
27
+ return unless resolved_model_name
28
+
29
+ push_entries(constant_entries(resolved_model_name))
30
+ elsif object_method_call?(node)
31
+ return unless resolved_model_name
32
+
33
+ method_name = message(node)
34
+ entries = method_entries(resolved_model_name, method_name)
35
+ entries = association_entries(resolved_model_name, method_name) if entries.empty?
36
+
37
+ push_entries(entries)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :index, :model_name_cache, :node_context, :rails_client, :response_builder
44
+
45
+ def current_target?(node)
46
+ target = node_context.node if node_context.respond_to?(:node)
47
+ call_node = node_context.call_node if node_context.respond_to?(:call_node)
48
+
49
+ node.equal?(target) || node.equal?(call_node)
50
+ end
51
+
52
+ def resolved_model_name
53
+ return @resolved_model_name if defined?(@resolved_model_name)
54
+ return @resolved_model_name = nil unless current_class_name
55
+
56
+ @resolved_model_name = model_name_from_rails(workflow_namespace) || convention_model_name
57
+ end
58
+
59
+ def convention_model_name
60
+ return @convention_model_name if defined?(@convention_model_name)
61
+
62
+ @convention_model_name = current_class_name && WorkflowResolver.model_name_for(current_class_name)
63
+ end
64
+
65
+ def workflow_namespace
66
+ return @workflow_namespace if defined?(@workflow_namespace)
67
+
68
+ @workflow_namespace = current_class_name && WorkflowResolver.workflow_namespace_for(current_class_name)
69
+ end
70
+
71
+ def current_class_name
72
+ return @current_class_name if defined?(@current_class_name)
73
+ return @current_class_name = nil unless node_context.respond_to?(:nesting)
74
+
75
+ @current_class_name = class_name_from_nesting(node_context.nesting)
76
+ end
77
+
78
+ def class_name_from_nesting(nesting)
79
+ parts = nesting.map(&:to_s).reject(&:empty?)
80
+ return if parts.empty?
81
+
82
+ # Nesting entries may already contain "::" (e.g. "Post::Draft").
83
+ parts.join("::").gsub(/:{3,}/, "::")
84
+ end
85
+
86
+ def model_name_from_rails(workflow_namespace)
87
+ return unless workflow_namespace && rails_client
88
+
89
+ # Cache hits and misses for the life of the LSP process; the set of
90
+ # models rarely changes mid-session. Failures raise past the cache
91
+ # write, so a slow-to-boot Rails runner can succeed on a later request.
92
+ cache = model_name_cache.to_h
93
+ cache.fetch(workflow_namespace) do
94
+ cache[workflow_namespace] = request_model_name(workflow_namespace)
95
+ end
96
+ rescue
97
+ nil
98
+ end
99
+
100
+ def request_model_name(workflow_namespace)
101
+ result = rails_client.delegate_request(
102
+ server_addon_name: SERVER_ADDON_NAME,
103
+ request_name: "model_for_workflow_namespace",
104
+ workflow_namespace: workflow_namespace
105
+ )
106
+
107
+ response_name(result)
108
+ end
109
+
110
+ def association_entries(model_name, association_name)
111
+ association_model_name = association_model_name(model_name, association_name)
112
+ return [] unless association_model_name
113
+
114
+ constant_entries(association_model_name)
115
+ end
116
+
117
+ def association_model_name(model_name, association_name)
118
+ return unless rails_client&.respond_to?(:association_target)
119
+
120
+ result = rails_client.association_target(model_name: model_name, association_name: association_name)
121
+ response_name(result)
122
+ rescue
123
+ nil
124
+ end
125
+
126
+ def response_name(result)
127
+ return unless result
128
+
129
+ result[:name] || result["name"]
130
+ end
131
+
132
+ def constant_entries(name)
133
+ return [] unless name && index
134
+
135
+ Array(index[name])
136
+ end
137
+
138
+ def method_entries(model_name, method_name)
139
+ return [] unless index && method_name
140
+
141
+ if index.respond_to?(:resolve_method)
142
+ entries = index.resolve_method(method_name, model_name)
143
+ return Array(entries)
144
+ end
145
+
146
+ Array(index[method_name]).select { |entry| entry_owner_name(entry) == model_name }
147
+ end
148
+
149
+ def entry_owner_name(entry)
150
+ owner = entry.owner if entry.respond_to?(:owner)
151
+ owner.name if owner.respond_to?(:name)
152
+ end
153
+
154
+ def push_entries(entries)
155
+ entries.each do |entry|
156
+ response_builder << location_for(entry)
157
+ end
158
+ end
159
+
160
+ def location_for(entry)
161
+ return entry unless defined?(::RubyLsp::Interface::Location)
162
+
163
+ location = entry_location(entry)
164
+ return entry unless location
165
+
166
+ ::RubyLsp::Interface::Location.new(
167
+ uri: entry.uri.to_s,
168
+ range: range_for(location)
169
+ )
170
+ end
171
+
172
+ def entry_location(entry)
173
+ return entry.location if entry.respond_to?(:location)
174
+
175
+ entry.name_location if entry.respond_to?(:name_location)
176
+ end
177
+
178
+ def range_for(location)
179
+ ::RubyLsp::Interface::Range.new(
180
+ start: ::RubyLsp::Interface::Position.new(
181
+ line: location.start_line - 1,
182
+ character: location.start_column
183
+ ),
184
+ end: ::RubyLsp::Interface::Position.new(
185
+ line: location.end_line - 1,
186
+ character: location.end_column
187
+ )
188
+ )
189
+ end
190
+
191
+ def object_method_call?(node)
192
+ message(node) && object_call?(receiver(node))
193
+ end
194
+
195
+ def object_call?(node)
196
+ node && receiver(node).nil? && message(node) == "object"
197
+ end
198
+
199
+ def receiver(node)
200
+ node.receiver if node.respond_to?(:receiver)
201
+ end
202
+
203
+ def message(node)
204
+ node.message.to_s if node.respond_to?(:message) && node.message
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby_lsp/ruby_lsp_rails/server"
4
+
5
+ module RubyLsp
6
+ module HasStateMachine
7
+ class RailsServerAddon < ::RubyLsp::Rails::ServerAddon
8
+ def name
9
+ "has_state_machine"
10
+ end
11
+
12
+ def execute(request, params)
13
+ with_request_error_handling(request) do
14
+ case request
15
+ when "model_for_workflow_namespace"
16
+ send_result(model_for_workflow_namespace(params.fetch("workflow_namespace")))
17
+ else
18
+ raise NotImplementedError, "Unknown request: #{request}"
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def model_for_workflow_namespace(workflow_namespace)
26
+ model = conventional_model_for(workflow_namespace) || models_by_workflow_namespace[workflow_namespace]
27
+ return unless model
28
+
29
+ {name: model.name}
30
+ end
31
+
32
+ ##
33
+ # Fast path: for the default "Workflow::<Model>" namespace, autoload just
34
+ # that one constant instead of eager loading the whole application. Only
35
+ # custom workflow_namespace configurations need the full scan below.
36
+ def conventional_model_for(workflow_namespace)
37
+ workflow_namespace = workflow_namespace.to_s
38
+ return unless workflow_namespace.start_with?("Workflow::")
39
+
40
+ model = workflow_namespace.delete_prefix("Workflow::").safe_constantize
41
+ model if model.try(:workflow_namespace) == workflow_namespace
42
+ end
43
+
44
+ def models_by_workflow_namespace
45
+ @models_by_workflow_namespace ||= active_record_models.each_with_object({}) do |model, index|
46
+ next unless model.respond_to?(:workflow_namespace)
47
+
48
+ index[model.workflow_namespace] = model
49
+ end
50
+ end
51
+
52
+ def active_record_models
53
+ @active_record_models ||= begin
54
+ ::Rails.application&.eager_load!
55
+ ::ActiveRecord::Base.descendants.reject(&:abstract_class?)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLsp
4
+ module HasStateMachine
5
+ module WorkflowResolver
6
+ WORKFLOW_PREFIX = "Workflow::"
7
+
8
+ module_function
9
+
10
+ def model_name_for(workflow_class_name)
11
+ namespace = workflow_namespace_for(workflow_class_name)
12
+ return unless namespace&.start_with?(WORKFLOW_PREFIX)
13
+
14
+ name = namespace.delete_prefix(WORKFLOW_PREFIX)
15
+ return if name.empty?
16
+
17
+ name
18
+ end
19
+
20
+ def workflow_namespace_for(workflow_class_name)
21
+ namespace = workflow_class_name.to_s.delete_prefix("::").sub(/::[^:]+\z/, "")
22
+ return if namespace.empty?
23
+
24
+ namespace
25
+ end
26
+ end
27
+ end
28
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: has_state_machine
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Hargett
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: nokogiri
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "<"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.19'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "<"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.19'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: pry
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -67,7 +81,21 @@ dependencies:
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
- name: standard
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.74'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.74'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-performance
71
99
  requirement: !ruby/object:Gem::Requirement
72
100
  requirements:
73
101
  - - ">="
@@ -80,6 +108,20 @@ dependencies:
80
108
  - - ">="
81
109
  - !ruby/object:Gem::Version
82
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: parallel
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "<"
116
+ - !ruby/object:Gem::Version
117
+ version: '2.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "<"
123
+ - !ruby/object:Gem::Version
124
+ version: '2.0'
83
125
  - !ruby/object:Gem::Dependency
84
126
  name: appraisal
85
127
  requirement: !ruby/object:Gem::Requirement
@@ -95,19 +137,117 @@ dependencies:
95
137
  - !ruby/object:Gem::Version
96
138
  version: '0'
97
139
  - !ruby/object:Gem::Dependency
98
- name: minitest
140
+ name: rspec-rails
99
141
  requirement: !ruby/object:Gem::Requirement
100
142
  requirements:
101
- - - "~>"
143
+ - - ">="
102
144
  - !ruby/object:Gem::Version
103
- version: '5.1'
145
+ version: '6.1'
104
146
  type: :development
105
147
  prerelease: false
106
148
  version_requirements: !ruby/object:Gem::Requirement
107
149
  requirements:
108
- - - "~>"
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '6.1'
153
+ - !ruby/object:Gem::Dependency
154
+ name: ruby-lsp
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0.18'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0.18'
167
+ - !ruby/object:Gem::Dependency
168
+ name: ruby-lsp-rails
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: 0.3.17
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: 0.3.17
181
+ - !ruby/object:Gem::Dependency
182
+ name: ruby-lsp-rspec
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
109
186
  - !ruby/object:Gem::Version
110
- version: '5.1'
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: base64
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ - !ruby/object:Gem::Dependency
210
+ name: bigdecimal
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
223
+ - !ruby/object:Gem::Dependency
224
+ name: drb
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
237
+ - !ruby/object:Gem::Dependency
238
+ name: mutex_m
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ type: :development
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - ">="
249
+ - !ruby/object:Gem::Version
250
+ version: '0'
111
251
  description: HasStateMachine uses ruby classes to make creating a finite state machine
112
252
  in your ActiveRecord models a breeze.
113
253
  email:
@@ -128,6 +268,10 @@ files:
128
268
  - lib/has_state_machine/state.rb
129
269
  - lib/has_state_machine/state_helpers.rb
130
270
  - lib/has_state_machine/version.rb
271
+ - lib/ruby_lsp/has_state_machine/addon.rb
272
+ - lib/ruby_lsp/has_state_machine/definition.rb
273
+ - lib/ruby_lsp/has_state_machine/rails_server_addon.rb
274
+ - lib/ruby_lsp/has_state_machine/workflow_resolver.rb
131
275
  homepage: https://www.github.com/encampment/has_state_machine
132
276
  licenses:
133
277
  - MIT
@@ -146,7 +290,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
146
290
  - !ruby/object:Gem::Version
147
291
  version: '0'
148
292
  requirements: []
149
- rubygems_version: 3.6.7
293
+ rubygems_version: 3.6.9
150
294
  specification_version: 4
151
295
  summary: Class based state machine for ActiveRecord models.
152
296
  test_files: []