state_shifter 0.8.1 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
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