state_shifter 0.8.1 → 1.0.3

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 560af59d82afed84d4b51b47e7c5f2487acc86f4
4
+ data.tar.gz: 0f6b9926727a1543f9c11a0f1b50ffd6dcb7bafb
5
+ SHA512:
6
+ metadata.gz: 440824ce9be500d3ea26b8373916d020aad8c423a418fc6b491338e1681a8fcb11e46cffb2e8c27e85b2aef4b50624476131fb259392bb14ed2b2b7768e43e2d
7
+ data.tar.gz: dafc299c8726b62b3f55d832cec3eb9be8f38f475330727dd6ac960147a00ece293a7ef5f511282d96b1fff5135816fe5ba73c12fe4740701e894e3815650a06
data/Gemfile CHANGED
@@ -1,19 +1,17 @@
1
1
  source "http://rubygems.org"
2
- # Add dependencies required to use your gem here.
3
- # Example:
4
- # gem "activesupport", ">= 2.3.5"
5
2
 
6
- # Add dependencies to develop your gem here.
7
- # Include everything needed to run rake, tests, features, etc.
8
3
  group :development do
9
- gem 'activerecord', '~> 3.2.x'
10
- gem 'sqlite3'
11
- gem 'rspec-nc'
12
- gem "rspec", "~> 2.9.0"
4
+ gem 'activerecord', '~> 4.0.9'
5
+ gem 'sqlite3', '~> 1.3.10'
6
+ gem "rspec"
13
7
  gem "yard", "~> 0.7"
14
- gem 'redcarpet'
15
- gem "rdoc", "~> 3.12"
16
- gem "bundler", "~> 1.1.0"
8
+ gem 'redcarpet', '>= 3.2.3'
9
+ gem "rdoc", "~> 4.0"
10
+ gem "bundler"
17
11
  gem "jeweler", "~> 1.8.4"
18
12
  gem "simplecov", ">= 0"
13
+ gem 'pry'
14
+ gem 'ruby-graphviz'
15
+ gem 'json', ">= 1.8.2"
16
+ gem 'i18n', '>= 0.6.6'
19
17
  end
data/Gemfile.lock CHANGED
@@ -1,64 +1,110 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- activemodel (3.2.8)
5
- activesupport (= 3.2.8)
6
- builder (~> 3.0.0)
7
- activerecord (3.2.8)
8
- activemodel (= 3.2.8)
9
- activesupport (= 3.2.8)
10
- arel (~> 3.0.2)
11
- tzinfo (~> 0.3.29)
12
- activesupport (3.2.8)
13
- i18n (~> 0.6)
14
- multi_json (~> 1.0)
15
- arel (3.0.2)
16
- builder (3.0.0)
17
- diff-lcs (1.1.3)
18
- git (1.2.5)
19
- i18n (0.6.0)
20
- jeweler (1.8.4)
4
+ activemodel (4.0.13)
5
+ activesupport (= 4.0.13)
6
+ builder (~> 3.1.0)
7
+ activerecord (4.0.13)
8
+ activemodel (= 4.0.13)
9
+ activerecord-deprecated_finders (~> 1.0.2)
10
+ activesupport (= 4.0.13)
11
+ arel (~> 4.0.0)
12
+ activerecord-deprecated_finders (1.0.4)
13
+ activesupport (4.0.13)
14
+ i18n (~> 0.6, >= 0.6.9)
15
+ minitest (~> 4.2)
16
+ multi_json (~> 1.3)
17
+ thread_safe (~> 0.1)
18
+ tzinfo (~> 0.3.37)
19
+ addressable (2.3.8)
20
+ arel (4.0.2)
21
+ builder (3.1.4)
22
+ coderay (1.1.0)
23
+ diff-lcs (1.2.5)
24
+ docile (1.1.5)
25
+ faraday (0.8.9)
26
+ multipart-post (~> 1.2.0)
27
+ git (1.2.9.1)
28
+ github_api (0.10.1)
29
+ addressable
30
+ faraday (~> 0.8.1)
31
+ hashie (>= 1.2)
32
+ multi_json (~> 1.4)
33
+ nokogiri (~> 1.5.2)
34
+ oauth2
35
+ hashie (3.4.1)
36
+ highline (1.7.1)
37
+ i18n (0.7.0)
38
+ jeweler (1.8.8)
39
+ builder
21
40
  bundler (~> 1.0)
22
41
  git (>= 1.2.5)
42
+ github_api (= 0.10.1)
43
+ highline (>= 1.6.15)
44
+ nokogiri (= 1.5.10)
23
45
  rake
24
46
  rdoc
25
- json (1.7.5)
26
- multi_json (1.3.6)
27
- rake (0.9.2.2)
28
- rdoc (3.12)
29
- json (~> 1.4)
30
- redcarpet (2.1.1)
31
- rspec (2.9.0)
32
- rspec-core (~> 2.9.0)
33
- rspec-expectations (~> 2.9.0)
34
- rspec-mocks (~> 2.9.0)
35
- rspec-core (2.9.0)
36
- rspec-expectations (2.9.1)
37
- diff-lcs (~> 1.1.3)
38
- rspec-mocks (2.9.0)
39
- rspec-nc (0.0.4)
40
- rspec (~> 2.9)
41
- terminal-notifier (~> 1.4.2)
42
- simplecov (0.6.4)
47
+ json (1.8.2)
48
+ jwt (1.4.1)
49
+ method_source (0.8.2)
50
+ minitest (4.7.5)
51
+ multi_json (1.11.0)
52
+ multi_xml (0.5.5)
53
+ multipart-post (1.2.0)
54
+ nokogiri (1.5.10)
55
+ oauth2 (1.0.0)
56
+ faraday (>= 0.8, < 0.10)
57
+ jwt (~> 1.0)
58
+ multi_json (~> 1.3)
59
+ multi_xml (~> 0.5)
60
+ rack (~> 1.2)
61
+ pry (0.10.1)
62
+ coderay (~> 1.1.0)
63
+ method_source (~> 0.8.1)
64
+ slop (~> 3.4)
65
+ rack (1.6.0)
66
+ rake (10.4.2)
67
+ rdoc (4.2.0)
68
+ redcarpet (3.2.3)
69
+ rspec (3.2.0)
70
+ rspec-core (~> 3.2.0)
71
+ rspec-expectations (~> 3.2.0)
72
+ rspec-mocks (~> 3.2.0)
73
+ rspec-core (3.2.3)
74
+ rspec-support (~> 3.2.0)
75
+ rspec-expectations (3.2.1)
76
+ diff-lcs (>= 1.2.0, < 2.0)
77
+ rspec-support (~> 3.2.0)
78
+ rspec-mocks (3.2.1)
79
+ diff-lcs (>= 1.2.0, < 2.0)
80
+ rspec-support (~> 3.2.0)
81
+ rspec-support (3.2.2)
82
+ ruby-graphviz (1.2.1)
83
+ simplecov (0.9.2)
84
+ docile (~> 1.1.0)
43
85
  multi_json (~> 1.0)
44
- simplecov-html (~> 0.5.3)
45
- simplecov-html (0.5.3)
46
- sqlite3 (1.3.6)
47
- terminal-notifier (1.4.2)
48
- tzinfo (0.3.33)
49
- yard (0.8.2.1)
86
+ simplecov-html (~> 0.9.0)
87
+ simplecov-html (0.9.0)
88
+ slop (3.6.0)
89
+ sqlite3 (1.3.10)
90
+ thread_safe (0.3.5)
91
+ tzinfo (0.3.43)
92
+ yard (0.8.7.6)
50
93
 
51
94
  PLATFORMS
52
95
  ruby
53
96
 
54
97
  DEPENDENCIES
55
- activerecord (~> 3.2.x)
56
- bundler (~> 1.1.0)
98
+ activerecord (~> 4.0.9)
99
+ bundler
100
+ i18n (>= 0.6.6)
57
101
  jeweler (~> 1.8.4)
58
- rdoc (~> 3.12)
59
- redcarpet
60
- rspec (~> 2.9.0)
61
- rspec-nc
102
+ json (>= 1.8.2)
103
+ pry
104
+ rdoc (~> 4.0)
105
+ redcarpet (>= 3.2.3)
106
+ rspec
107
+ ruby-graphviz
62
108
  simplecov
63
- sqlite3
109
+ sqlite3 (~> 1.3.10)
64
110
  yard (~> 0.7)
data/README.md CHANGED
@@ -1,11 +1,172 @@
1
1
  state\_shifter
2
2
  ==============
3
3
 
4
- Description goes here.
4
+ This gem makes it easy to incorporate state machine behavior in a Ruby class.
5
+
6
+ Features include:
7
+
8
+ * on\_entry and on\_transition handlees
9
+ * ActiveRecord integration
10
+ * event guards and easy event handlers
11
+ * graphViz visualization creator
12
+ * flexible machine syntax
13
+
14
+ Usage
15
+ -----
16
+
17
+ An example of state machine definition possible with this gem:
18
+
19
+ ```ruby
20
+ class Simple
21
+ include StateShifter::Definition
22
+
23
+ state_machine do
24
+
25
+ # first state to be defined is the initial one
26
+ state :new do
27
+ event :submit => :awaiting_review
28
+ end
29
+
30
+ state :awaiting_review do
31
+ event :review => :being_reviewed
32
+ end
33
+
34
+ state :being_reviewed do
35
+ event :accept => :accepted, :if => :cool_article?
36
+ event :reject => :rejected, :if => :bad_article?
37
+ end
38
+
39
+ state :accepted
40
+ state :rejected
41
+
42
+ end
43
+
44
+ def cool_article?
45
+ true
46
+ end
47
+
48
+ def bad_article?
49
+ false
50
+ end
51
+
52
+ end
53
+ ```
54
+
55
+ Basically, you need to have a ```state_machine``` block with a collection of states and events. The initial state is the first one on the definition, and events are in the form of ```event :event_name => :next_state_name```. Events can have guards, and also refer back to the same state, in which case you simple omit the ```next_state_name``` - mostly to have "touch-and-go" events that just execute a method specified in the ```:call``` option passed to it, and remain in the same state. The next example shows relevant usage of it.
56
+
57
+ ```ruby
58
+ class Advanced
59
+ include StateShifter::Definition
60
+
61
+ ###
62
+
63
+ state_machine do
64
+
65
+ state :initialized do
66
+
67
+ event :start_date_changed, :call => :handle_start_date_changed
68
+ event :forced_start => :running
69
+ event :start_date_reached => :running, :if => :start_date_reached?
70
+ event :abort_initialized_contest => :finalized
71
+ end
72
+
73
+ state :running do
74
+
75
+ on_entry do |previous_state, trigger_event|
76
+ running_entry previous_state, trigger_event
77
+ end
78
+
79
+ event :abort_running_contest => :notify_stakeholders
80
+ event :deadline_reached => :notify_organizers, :if => :entries_deadline_reached?
81
+ event :spots_filled => :notify_organizers, :if => :spots_filled?
82
+ event :deadline_reached_without_approvals => :notify_pending_users, :if => :entries_deadline_reached_without_approvals?
83
+ event :deadline_reached_without_entries => :finalized, :if => :entries_deadline_reached_without_entries?
84
+ end
85
+
86
+ state :notify_organizers do
87
+ on_entry :send_notification_to_organizers
88
+ event :organizers_notified => :awaiting_organizer_reply
89
+ end
90
+
91
+ state :awaiting_organizer_reply do
92
+ event :organizer_confirmation_missing => :notify_stakeholders, :if => :organizer_confirmation_deadline_reached?
93
+ event :organizer_confirmation_received => :notify_approved_users
94
+ event :organizer_has_more_tickets => :running
95
+ end
96
+
97
+ state :notify_stakeholders do
98
+ on_entry :send_notification, :stakeholders, :organizers
99
+ event :stakeholders_notified => :cancelled
100
+ end
101
+
102
+ state :cancelled
103
+
104
+ state :notify_pending_users do
105
+ on_entry :send_notification, :pending_users
106
+ event :pending_users_notified => :finalized
107
+ end
108
+
109
+ state :notify_approved_users do
110
+ on_entry :send_notification_to_approved_users
111
+ event :approved_users_notified => :send_list_to_organizers
112
+ end
113
+
114
+ state :send_list_to_organizers do
115
+ on_entry :send_guestlist_to_organizers
116
+ event :list_sent_to_organizers => :awaiting_attendance
117
+ end
118
+
119
+ state :awaiting_attendance do
120
+ event :remind_to_fill_in_report => :create_report_filling_requests
121
+ end
122
+
123
+ state :create_report_filling_requests do
124
+ on_entry :send_report_filling_requests
125
+ event :finalize => :finalized
126
+ end
127
+
128
+ state :finalized
129
+
130
+ on_transition do |from,to,trigger_event, duration|
131
+ benchmark from, to, trigger_event, duration
132
+ end
133
+ end
134
+
135
+ ###
136
+
137
+ def send_notification to
138
+ #
139
+ end
140
+
141
+ def entries_deadline_reached?
142
+ true
143
+ end
144
+
145
+ def running_entry previous_state, trigger_event
146
+ #
147
+ end
148
+
149
+ def benchmark from, to, trigger_event, duration
150
+ #
151
+ end
152
+
153
+ end
154
+ ```
155
+
156
+ Plagiarism alert
157
+ ----------------
158
+
159
+ This gem draws _heavy_ inspiration from both [pluginaweek's state_machine](https://github.com/pluginaweek/state_machine) and [mdh's ssm](https://github.com/mdh/ssm) gems. I liked both of them, but the DSL syntax was not 100% to my liking. Kudos to them.
160
+
161
+ Future
162
+ ------
163
+
164
+ I want to add "mountable" state machines as per ssm's gem, to have a clear separation which would ease testing, but haven't had the chance yet.
165
+
5
166
 
6
167
  Contributing to state\_shifter
7
168
  ------------------------------
8
-
169
+
9
170
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
10
171
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
11
172
  * Fork the project.
@@ -19,4 +180,3 @@ Copyright
19
180
 
20
181
  Copyright (c) 2012 Bruno Antunes. See LICENSE.txt for
21
182
  further details.
22
-
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.1
1
+ 1.0.3
data/examples/advanced.rb CHANGED
@@ -1,16 +1,22 @@
1
1
  class Advanced
2
2
  include StateShifter::Definition
3
3
 
4
- ###
4
+ ###
5
5
 
6
6
  state_machine do
7
7
 
8
8
  state :initialized do
9
-
10
9
  event :start_date_changed, :call => :handle_start_date_changed
11
10
  event :forced_start => :running
12
11
  event :start_date_reached => :running, :if => :start_date_reached?
13
12
  event :abort_initialized_contest => :finalized
13
+ event :event_associated => :preparing
14
+ end
15
+
16
+ state :preparing do
17
+ on_entry :prepare
18
+
19
+ event :all_done => :running
14
20
  end
15
21
 
16
22
  state :running do
@@ -19,7 +25,7 @@ class Advanced
19
25
  running_entry previous_state, trigger_event
20
26
  end
21
27
 
22
- event :abort_running_contest => :notify_stakeholders
28
+ event :abort_running_contest => :notify_stakeholders
23
29
  event :changed_properties
24
30
  event :keep_users_engaged
25
31
  event :deadline_reached => :notify_organizers, :if => :entries_deadline_reached?
@@ -30,6 +36,7 @@ class Advanced
30
36
 
31
37
  state :notify_organizers do
32
38
  on_entry :send_notification_to_organizers
39
+
33
40
  event :organizers_notified => :awaiting_organizer_reply
34
41
  end
35
42
 
@@ -42,6 +49,7 @@ class Advanced
42
49
 
43
50
  state :notify_stakeholders do
44
51
  on_entry :send_notification, :stakeholders, :organizers
52
+
45
53
  event :stakeholders_notified => :cancelled
46
54
  end
47
55
 
@@ -80,6 +88,10 @@ class Advanced
80
88
 
81
89
  ###
82
90
 
91
+ def prepare
92
+ all_done!
93
+ end
94
+
83
95
  def send_notification to
84
96
  #
85
97
  end
data/examples/review.rb CHANGED
@@ -1,18 +1,21 @@
1
1
  class Review < ActiveRecord::Base
2
2
  include StateShifter::Definition
3
3
 
4
- state_machine do
4
+ state_machine do
5
5
 
6
6
  # first state to be defined is the initial one
7
7
  state :new do
8
+ tags :reviewable
8
9
  event :submit => :awaiting_review
9
10
  end
10
11
 
11
12
  state :awaiting_review do
13
+ tags :reviewable, :processing
12
14
  event :review => :being_reviewed
13
15
  end
14
16
 
15
17
  state :being_reviewed do
18
+ tags :reviewable, :processing
16
19
  event :accept => :accepted, :if => :cool_article?
17
20
  event :reject => :rejected, :if => :bad_article?
18
21
  end
@@ -2,7 +2,21 @@ module StateShifter
2
2
  module Definition
3
3
  module ActiveRecordIntegrationMethods
4
4
 
5
- class ::StateShifter::Definition::StatePersistenceAttributeNotPresent < RuntimeError; end
5
+ class ::StateShifter::Definition::StatePersistenceAttributeNotPresent < RuntimeError; end
6
+
7
+ def self.include_state_scopes(base)
8
+ base.state_machine_definition.states.each do |name, definition|
9
+ base.class_eval do
10
+ scope name, -> { where(persist_attr_name => name) } unless respond_to?(name)
11
+ end
12
+ end
13
+
14
+ base.state_machine_definition.state_tags.each do |name, states|
15
+ base.class_eval do
16
+ scope name, -> { where(persist_attr_name => states) } unless respond_to?(name)
17
+ end
18
+ end
19
+ end
6
20
 
7
21
  def check_attr_presence
8
22
  raise StatePersistenceAttributeNotPresent unless self.attribute_names.include? self.class.persist_attr_name.to_s
@@ -2,86 +2,106 @@ module StateShifter
2
2
  module Definition
3
3
  module ClassMethods
4
4
 
5
- attr_accessor :state_machine_definition, :persist_attr_name
5
+ attr_accessor :state_machine_definition, :persist_attr_name, :_include_state_scopes
6
6
 
7
7
  def persist_attribute attr_name
8
- raise ::StateShifter::PersistenceAttributeAlreadyDefined if @persist_attr_name
8
+ raise PersistenceAttributeAlreadyDefined if @persist_attr_name
9
9
  @persist_attr_name = attr_name.to_sym
10
10
  end
11
11
 
12
12
  def state_machine &definition
13
13
  @persist_attr_name ||= :current_state
14
14
  @state_machine_definition = Contents.new(&definition)
15
-
16
- @state_machine_definition.states.each do |state_name, state_definition|
17
-
18
- module_eval do
19
15
 
20
- define_method "_next_states" do |from_state, *options|
21
- options = options.first || {}
22
- next_states_hash = {}
23
- check_guards = options.has_key?(:check_guards)
16
+ @state_machine_definition.states.each { |name, definition| _load_state(name, definition) }
17
+ @state_machine_definition.state_tags.each { |tag, states| _load_tag(tag, states) }
18
+ ActiveRecordIntegrationMethods.include_state_scopes(self) if _include_state_scopes
19
+ end
24
20
 
25
- state_machine_definition.get(:state, from_state).events.each do |event_name, event_def|
26
- if event_def.has_guards? && check_guards
27
- next if self.send(:check_guards, event_name).is_a?(Array)
28
- end
21
+ private
22
+
23
+ def _load_state(state_name, state_definition)
24
+ module_eval do
25
+
26
+ define_method "_next_states" do |from_state, *options|
27
+ options = options.first || {}
28
+ next_states_hash = {}
29
+ check_guards = options.has_key?(:check_guards)
29
30
 
30
- next_states_hash.merge!( event_def.to.nil? ? { from_state.to_sym => event_name } : { event_def.to => event_def.name } )
31
+ state_machine_definition.get(:state, from_state).events.each do |event_name, event_def|
32
+ if event_def.has_guards? && check_guards
33
+ next if self.send(:check_guards, event_name).is_a?(Array)
31
34
  end
32
35
 
33
- next_states_hash.keys.uniq.sort
36
+ next_states_hash.merge!( event_def.to.nil? ? { from_state.to_sym => event_name } : { event_def.to => event_def.name } )
34
37
  end
35
38
 
36
- define_method "#{state_name}?" do
37
- current_state == state_name
38
- end
39
+ next_states_hash.keys.uniq.sort
40
+ end
39
41
 
40
- state_machine_definition.events.each do |event_name, event_definition|
42
+ define_method "#{state_name}?" do
43
+ current_state.to_sym == state_name
44
+ end
41
45
 
42
- define_method "can_#{event_name}?" do
43
-
44
- this_event = state_machine_definition.get(:event, event_name)
45
-
46
- current_state.to_sym == this_event.from.to_sym && !check_guards(event_name).is_a?(Array)
47
-
48
- end
46
+ state_machine_definition.events.each do |event_name, event_definition|
49
47
 
50
- define_method "#{event_name}!" do
48
+ define_method "can_#{event_name}?" do
51
49
 
52
- self.send event_name.to_sym, true
53
-
54
- end
50
+ this_event = state_machine_definition.get(:event, event_name)
55
51
 
56
- define_method "#{event_name}" do |bang=false|
52
+ current_state.to_sym == this_event.from.to_sym && !check_guards(event_name).is_a?(Array)
57
53
 
58
- if current_state != event_definition.from
59
- if bang
60
- halt("you cannot transition from #{current_state} via #{event_name}")
61
- else
62
- return false
63
- end
64
- end
65
-
66
- if (failed_guards = check_guards(event_name)).is_a?(Array)
67
- if bang
68
- failed_guards.delete_at(0)
69
- raise ::StateShifter::GuardNotSatisfied, "#{failed_guards.join}"
70
- else
71
- return false
72
- end
54
+ end
55
+
56
+ define_method "#{event_name}!" do
57
+
58
+ self.send event_name.to_sym, true
59
+
60
+ end
61
+
62
+ define_method "#{event_name}" do |bang=false|
63
+
64
+ if current_state.to_sym != event_definition.from.to_sym
65
+ if bang
66
+ halt("you cannot transition from #{current_state} via #{event_name}")
67
+ else
68
+ return false
73
69
  end
70
+ end
74
71
 
75
- transition :to => ( event_definition.to.nil? ? current_state : event_definition.to ), :trigger => ( bang ? "#{event_name}!" : event_name )
76
-
72
+ if (failed_guards = check_guards(event_name)).is_a?(Array)
73
+ if bang
74
+ failed_guards.delete_at(0)
75
+ raise GuardNotSatisfied, "#{failed_guards.join}"
76
+ else
77
+ return false
78
+ end
77
79
  end
78
80
 
81
+ transition :to => ( event_definition.to.nil? ? current_state : event_definition.to ), :trigger => ( bang ? "#{event_name}!" : event_name )
82
+
79
83
  end
80
84
 
81
85
  end
86
+
82
87
  end
83
- end
84
88
 
89
+ def _load_tag(tag, states)
90
+ module_eval do
91
+
92
+ define_singleton_method "#{tag}_states" do
93
+ states
94
+ end
95
+
96
+ define_method "#{tag}?" do
97
+ states.map(&:to_s).include? current_state.to_s
98
+ end
99
+
100
+ end
101
+
102
+ end
103
+
104
+ end
85
105
  end
86
106
  end
87
107
  end