has_state_machine 0.1.0 → 0.3.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: 180ea0ce6e3e597405acc08441fe8ee1c0f530ccfd176950e8683196e9232695
4
- data.tar.gz: 97944638a358883f302463d830d506149fe169d9cf3a3c20d7ccd615724a4932
3
+ metadata.gz: 16d6a43019c37f17fc0efac4e344f1e9914b0cb923a439d1e799d690018dd0e6
4
+ data.tar.gz: 8bd1d5d4f6b72dcb9931ccbd0987b0fe2d4970e2476867fe930cf21a9b5d0da2
5
5
  SHA512:
6
- metadata.gz: c4e20717ca8de688ea7c766da83412b7bf0121d06d2cace551dfd1b5210632fd9840ca34d7293381431b2ff4afc27459a33bfd2711ef537ee3ca39c8598d8098
7
- data.tar.gz: 973704bfb4403dae0b5ff00a3fe3b2d0d6d84f96a5e706bd0dea8cda113bd7dbfc7bac97ce84ae310b4fb6613f287f257a1dfedea41794e06f7afbd4b5af3834
6
+ metadata.gz: 7c7bb13405d5f0ee66af21594e0e0633e7c5f746ea7b038e2c28f4d53670b138b702bbd85737eadf952cb2871a234374597a10279f6fd0b026d7465b3721de79
7
+ data.tar.gz: ecb5c2c8ad0f00d1eb1c3ddd709598aa8ac29210dbaffe722461ba02bd8c7bc5c328ff7c4e90bbd8fdae8cbea91d5efe50d79b2874ed84035bc6bdf9d5a42421
data/README.md CHANGED
@@ -3,8 +3,15 @@
3
3
  [![Build Status](https://github.com/bharget/has_state_machine/workflows/Tests/badge.svg)](https://github.com/bharget/has_state_machine/actions)
4
4
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
5
5
 
6
- ## Usage
7
- How to use my plugin.
6
+ HasStateMachine uses ruby classes to make creating a finite state machine for your ActiveRecord models a breeze.
7
+
8
+ ## Contents
9
+
10
+ - [Installation](#installation)
11
+ - [Usage](#usage)
12
+ - [Changelog](https://github.com/encampment/has_state_machine/blob/master/CHANGELOG.md)
13
+ - [Contributing](#contributing)
14
+ - [License](#license)
8
15
 
9
16
  ## Installation
10
17
  Add this line to your application's Gemfile:
@@ -23,8 +30,104 @@ Or install it yourself as:
23
30
  $ gem install has_state_machine
24
31
  ```
25
32
 
33
+ ## Usage
34
+
35
+ You must first use the `has_state_machine` macro to define your state machine at
36
+ a high level. This includes defining the possible states for your object as well
37
+ as some optional configuration should you want to change the default behavior of
38
+ the state machine.
39
+ ```ruby
40
+ # By default, it is assumed that the "state" of the object is
41
+ # stored in a string column named "status".
42
+ class Post < ApplicationRecord
43
+ has_state_machine states: %i[draft published archived]
44
+ end
45
+ ```
46
+
47
+ Now you must define the classes for the states in your state machine. By default,
48
+ `HasStateMachine` assumes that these will be under the `Workflow` namespace following
49
+ the pattern of `Workflow::#{ObjectClass}::#{State}`. The state objects must inherit
50
+ from `HasStateMachine::State`.
51
+
52
+ ```ruby
53
+ module Workflow
54
+ class Post::Draft < HasStateMachine::State
55
+ # Define the possible transitions from the "draft" state
56
+ transitions_to %i[published archived]
57
+ end
58
+ end
59
+
60
+ module Workflow
61
+ class Post::Published < HasStateMachine::State
62
+ transitions_to %i[archived]
63
+
64
+ # Custom validations can be added to the state to ensure a transition is "valid"
65
+ validate :title_exists?
66
+
67
+ def title_exists?
68
+ return if object.title.present?
69
+
70
+ # Errors get added to the ActiveRecord object
71
+ errors.add(:title, "can't be blank")
72
+ end
73
+ end
74
+ end
75
+
76
+ module Workflow
77
+ class Post::Archived < HasStateMachine::State
78
+ # There are callbacks for running logic before and after
79
+ # a transition occurs.
80
+ before_transition do
81
+ Rails.logger.info "== Post is being archived ==\n"
82
+ end
83
+
84
+ after_transition do
85
+ Rails.logger.info "== Post has been archived ==\n"
86
+
87
+ # You can access the previous state of the object in
88
+ # after_transition callbacks as well.
89
+ Rails.logger.info "== Transitioned from #{previous_state} ==\n"
90
+ end
91
+ end
92
+ end
93
+ ```
94
+
95
+ Some examples:
96
+
97
+ ```ruby
98
+ post = Post.create(status: "draft")
99
+
100
+ post.status.transition_to(:published) # => false
101
+ post.status # => "draft"
102
+
103
+ post.title = "Foobar"
104
+ post.status.transition_to(:published) # => true
105
+ post.status # => "published"
106
+
107
+ post.status.transition_to(:archived)
108
+ # == Post is being archived ==
109
+ # == Post has been archived ==
110
+ # == Transitioned from published ==
111
+ # => true
112
+ ```
113
+
26
114
  ## Contributing
27
- Contribution directions go here.
115
+
116
+ Anyone is encouraged to help improve this project. Here are a few ways you can help:
117
+
118
+ - [Report bugs](https://github.com/encampment/has_state_machine/issues)
119
+ - Fix bugs and [submit pull requests](https://github.com/encampment/has_state_machine/pulls)
120
+ - Write, clarify, or fix documentation
121
+ - Suggest or add new features
122
+
123
+ To get started with development:
124
+
125
+ ```
126
+ git clone https://github.com/encampment/has_state_machine.git
127
+ cd has_state_machine
128
+ bundle install
129
+ bundle exec rake test
130
+ ```
28
131
 
29
132
  ## License
30
133
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -36,7 +36,6 @@ module HasStateMachine
36
36
  def define_helper_methods(states:, options:)
37
37
  ##
38
38
  # The list of possible states in the state machine.
39
- # Can be overwritten to use a different column name.
40
39
  define_singleton_method "workflow_states" do
41
40
  states
42
41
  end
@@ -57,7 +56,7 @@ module HasStateMachine
57
56
 
58
57
  ##
59
58
  # Determines whether or not the state validations should be run
60
- # as part of the object validations.
59
+ # as part of the object validations; they are by default.
61
60
  define_singleton_method "state_validations_on_object?" do
62
61
  return true unless options.key?(:state_validations_on_object)
63
62
 
@@ -8,7 +8,7 @@ module HasStateMachine
8
8
  extend ActiveModel::Callbacks
9
9
  include ActiveModel::Validations
10
10
 
11
- attr_reader :object, :state, :options
11
+ attr_reader :object, :state
12
12
 
13
13
  ##
14
14
  # Defines the before_transition and after_transition callbacks
@@ -28,8 +28,7 @@ module HasStateMachine
28
28
  # Initializes the HasStateMachine::State instance.
29
29
  #
30
30
  # @example
31
- # state = HasStateMachine::State.new(post) #=> "draft"
32
- # state.class #=> Workflow::Post::Draft
31
+ # state = Workflow::Post::Draft.new(post) #=> "draft"
33
32
  def initialize(object)
34
33
  @object = object
35
34
 
@@ -47,11 +46,15 @@ module HasStateMachine
47
46
  #
48
47
  # @return [Boolean] whether or not the transition took place
49
48
  def transition_to(desired_state, **options)
49
+ transitioned = false
50
+
50
51
  with_transition_options(options) do
51
52
  return false unless valid_transition?(desired_state.to_s)
52
53
 
53
- state_instance(desired_state.to_s).perform_transition!
54
+ transitioned = state_instance(desired_state.to_s).perform_transition!
54
55
  end
56
+
57
+ transitioned
55
58
  end
56
59
 
57
60
  ##
@@ -72,13 +75,21 @@ module HasStateMachine
72
75
  possible_transitions.include? desired_state
73
76
  end
74
77
 
78
+ ##
79
+ # Helper method for grabbing the previous state of the object after
80
+ # it has been transitioned to the new state. Useful in
81
+ # after_transition blocks
82
+ def previous_state
83
+ object.previous_changes[object.state_attribute]&.first
84
+ end
85
+
75
86
  def state_instance(desired_state)
76
87
  klass = "#{object.workflow_namespace}::#{desired_state.to_s.classify}".safe_constantize
77
88
  klass&.new(object)
78
89
  end
79
90
 
80
91
  def valid_transition?(desired_state)
81
- return true if options[:skip_validations]
92
+ return true if object.skip_state_validations
82
93
 
83
94
  object.valid? &&
84
95
  can_transition?(desired_state) &&
@@ -86,7 +97,6 @@ module HasStateMachine
86
97
  end
87
98
 
88
99
  def with_transition_options(options, &block)
89
- @options = options
90
100
  object.skip_state_validations = options[:skip_validations]
91
101
  yield
92
102
  object.skip_state_validations = false
@@ -5,6 +5,10 @@ module HasStateMachine
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
+ ##
9
+ # Sometimes you may want to skip the validations defined on
10
+ # the state when validating your object; set this accessor
11
+ # to true to do so.
8
12
  attr_accessor :skip_state_validations
9
13
 
10
14
  delegate \
@@ -20,8 +24,9 @@ module HasStateMachine
20
24
  attribute state_attribute, :string, default: initial_state
21
25
 
22
26
  ##
23
- # Validating any changes to the status attribute are represented by
24
- # classes within the state machine and have a valid HasStateMachine::State class.
27
+ # Validate that the current state is a possible state, that there is a
28
+ # state class defined for it, and run the validations from the state
29
+ # class instance if need be.
25
30
  validates state_attribute, inclusion: {in: workflow_states}, presence: true
26
31
  validate :state_class_defined?
27
32
  validate :state_instance_validations, if: :should_validate_state?
@@ -44,13 +49,15 @@ module HasStateMachine
44
49
 
45
50
  workflow_states.each do |state|
46
51
  ##
47
- # Defines scopes based on the state machine possible states
52
+ # Defines scopes based on the state machine's possible states
48
53
  #
49
54
  # @return [ActiveRecord_Relation]
50
55
  # @example Retreiving a users published posts
51
56
  # > Post.published.where(user: user)
52
57
  # #=> [#<Post>]
53
- scope state, -> { where("#{state_attribute} = ?", state) }
58
+ if defined?(ActiveRecord) && (self < ActiveRecord::Base)
59
+ scope state, -> { where("#{state_attribute} = ?", state) }
60
+ end
54
61
 
55
62
  ##
56
63
  # Defines boolean helpers to determine if the active state matches
@@ -71,7 +78,7 @@ module HasStateMachine
71
78
  # Getter for the current state of the model based on the configured state
72
79
  # attribute.
73
80
  def current_state
74
- self[state_attribute]
81
+ attributes.with_indifferent_access[state_attribute]
75
82
  end
76
83
 
77
84
  ##
@@ -102,8 +109,8 @@ module HasStateMachine
102
109
  end
103
110
 
104
111
  ##
105
- # Runs the validations defined on the current HasStateMachine::State when calling
106
- # model.valid?
112
+ # Run the validations defined on the current HasStateMachine::State. Errors found there
113
+ # should be added to this object.
107
114
  def state_instance_validations
108
115
  return unless state_class.present?
109
116
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HasStateMachine
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.2"
5
5
  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: 0.1.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Hargett
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-10-22 00:00:00.000000000 Z
12
+ date: 2021-01-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -133,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
133
  - !ruby/object:Gem::Version
134
134
  version: '0'
135
135
  requirements: []
136
- rubygems_version: 3.0.3
136
+ rubygems_version: 3.1.4
137
137
  signing_key:
138
138
  specification_version: 4
139
139
  summary: Class based state machine for ActiveRecord models.