state_manager 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +74 -0
- data/LICENSE.txt +20 -0
- data/README.md +241 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/state_manager/adapters/active_record.rb +52 -0
- data/lib/state_manager/adapters/base.rb +49 -0
- data/lib/state_manager/adapters.rb +30 -0
- data/lib/state_manager/base.rb +169 -0
- data/lib/state_manager/core.rb +7 -0
- data/lib/state_manager/dsl.rb +77 -0
- data/lib/state_manager/helpers.rb +47 -0
- data/lib/state_manager/plugins/delayed_job.rb +54 -0
- data/lib/state_manager/plugins.rb +4 -0
- data/lib/state_manager/resource.rb +83 -0
- data/lib/state_manager/state.rb +146 -0
- data/lib/state_manager.rb +1 -0
- data/state_manager.gemspec +102 -0
- data/test/adapters/active_record_test.rb +112 -0
- data/test/basic_test.rb +132 -0
- data/test/definition_test.rb +132 -0
- data/test/helper.rb +29 -0
- data/test/helpers_test.rb +61 -0
- data/test/plugins/delayed_job_test.rb +131 -0
- data/test/transitions_test.rb +171 -0
- metadata +226 -0
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
gem 'activesupport'
|
4
|
+
|
5
|
+
group :development do
|
6
|
+
gem "rdoc", "~> 3.12"
|
7
|
+
gem 'pry'
|
8
|
+
gem 'pry-doc'
|
9
|
+
gem 'pry-remote'
|
10
|
+
gem 'pry-nav'
|
11
|
+
gem 'pry-stack_explorer'
|
12
|
+
gem "bundler", "~> 1.0.0"
|
13
|
+
gem "jeweler", "~> 1.8.3"
|
14
|
+
gem 'delayed_job_active_record'
|
15
|
+
gem 'activerecord'
|
16
|
+
gem 'sqlite3'
|
17
|
+
gem 'timecop'
|
18
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (3.2.3)
|
5
|
+
activesupport (= 3.2.3)
|
6
|
+
builder (~> 3.0.0)
|
7
|
+
activerecord (3.2.3)
|
8
|
+
activemodel (= 3.2.3)
|
9
|
+
activesupport (= 3.2.3)
|
10
|
+
arel (~> 3.0.2)
|
11
|
+
tzinfo (~> 0.3.29)
|
12
|
+
activesupport (3.2.3)
|
13
|
+
i18n (~> 0.6)
|
14
|
+
multi_json (~> 1.0)
|
15
|
+
arel (3.0.2)
|
16
|
+
binding_of_caller (0.6.7)
|
17
|
+
builder (3.0.0)
|
18
|
+
coderay (1.0.6)
|
19
|
+
delayed_job (3.0.1)
|
20
|
+
activesupport (~> 3.0)
|
21
|
+
delayed_job_active_record (0.3.2)
|
22
|
+
activerecord (> 2.1.0)
|
23
|
+
delayed_job (~> 3.0.0)
|
24
|
+
git (1.2.5)
|
25
|
+
i18n (0.6.0)
|
26
|
+
jeweler (1.8.3)
|
27
|
+
bundler (~> 1.0)
|
28
|
+
git (>= 1.2.5)
|
29
|
+
rake
|
30
|
+
rdoc
|
31
|
+
json (1.7.3)
|
32
|
+
method_source (0.7.1)
|
33
|
+
multi_json (1.3.5)
|
34
|
+
pry (0.9.9.6)
|
35
|
+
coderay (~> 1.0.5)
|
36
|
+
method_source (~> 0.7.1)
|
37
|
+
slop (>= 2.4.4, < 3)
|
38
|
+
pry-doc (0.4.2)
|
39
|
+
pry (>= 0.9.0)
|
40
|
+
yard (~> 0.8.1)
|
41
|
+
pry-nav (0.2.1)
|
42
|
+
pry (~> 0.9.9)
|
43
|
+
pry-remote (0.1.4)
|
44
|
+
pry (~> 0.9.9)
|
45
|
+
slop (~> 2.1)
|
46
|
+
pry-stack_explorer (0.4.2)
|
47
|
+
binding_of_caller (~> 0.6.2)
|
48
|
+
pry (~> 0.9.9)
|
49
|
+
rake (0.9.2.2)
|
50
|
+
rdoc (3.12)
|
51
|
+
json (~> 1.4)
|
52
|
+
slop (2.4.4)
|
53
|
+
sqlite3 (1.3.6)
|
54
|
+
timecop (0.3.5)
|
55
|
+
tzinfo (0.3.33)
|
56
|
+
yard (0.8.1)
|
57
|
+
|
58
|
+
PLATFORMS
|
59
|
+
ruby
|
60
|
+
|
61
|
+
DEPENDENCIES
|
62
|
+
activerecord
|
63
|
+
activesupport
|
64
|
+
bundler (~> 1.0.0)
|
65
|
+
delayed_job_active_record
|
66
|
+
jeweler (~> 1.8.3)
|
67
|
+
pry
|
68
|
+
pry-doc
|
69
|
+
pry-nav
|
70
|
+
pry-remote
|
71
|
+
pry-stack_explorer
|
72
|
+
rdoc (~> 3.12)
|
73
|
+
sqlite3
|
74
|
+
timecop
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Gordon Hempton
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
# State Manager
|
2
|
+
|
3
|
+
StateManager is a state machine implementation for Ruby that is heavily inspired by the FSM implementation in [Ember.js](http://emberjs.com). Compared to other FSM implementations, it has the following defining characteristics:
|
4
|
+
|
5
|
+
* Sub-states are supported (e.g. 'submitted.reviewing').
|
6
|
+
* State logic can be kept separate from model classes.
|
7
|
+
* State definitions are modular, underlying each state is a separate class definition.
|
8
|
+
* Supports integrations. Comes out of the box with an integration with active_record and delayed_job to support automatic delayed transtions.
|
9
|
+
|
10
|
+
We believe this is an improvement over existing state machines, but just for good measure, check out [state_machine](https://github.com/pluginaweek/state_machine) and [workflow](https://github.com/geekq/workflow).
|
11
|
+
|
12
|
+
## Getting Started
|
13
|
+
|
14
|
+
Install the `state_manager` gem. If you are using bundler, add the following to your Gemfile:
|
15
|
+
|
16
|
+
```
|
17
|
+
gem 'state_manager'
|
18
|
+
```
|
19
|
+
|
20
|
+
After the gem is installed, create a file to contain the definition of your state manager, e.g. `app/states/post_states.rb`. Edit this file and define your states:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
class PostStates < StateManager::Base
|
24
|
+
state :unsubmitted do
|
25
|
+
event :submit, :transitions_to => 'submitted.awaiting_review'
|
26
|
+
end
|
27
|
+
state :submitted
|
28
|
+
event :reject, :transitions_to => 'rejected'
|
29
|
+
state :awaiting_review do
|
30
|
+
event :review, :transitions_to => 'submitted.reviewing'
|
31
|
+
end
|
32
|
+
state :reviewing do
|
33
|
+
event :accept, :transitions_to => 'active'
|
34
|
+
event :clarify, :transitions_to => 'submitted.clarifying'
|
35
|
+
end
|
36
|
+
state :clarifying do
|
37
|
+
event :review, :transitions_to => 'submitted.reviewing'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
state :active
|
41
|
+
state :rejected
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
Once your states are defined, you need to extend the `StateManager::Resource` module on your resource class and define a state managed property:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
class Post
|
49
|
+
attr_accessor :state
|
50
|
+
extend StateManager::Resource
|
51
|
+
state_manager
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
The code above infers the existence of `PostStates` class and a `state` property. An alternate states definition and property could be specified as follows:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
class Post
|
59
|
+
attr_accessor :workflow_state
|
60
|
+
extend StateManager::Resource
|
61
|
+
state_manager :workflow_state, PostStates
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
## Helper Methods
|
66
|
+
|
67
|
+
Unless otherwise specified with `{:helpers => false}` as the third argument to the `state_manager` macro, helper methods will be added to the resource class. In the above example, some of the methods that will be available are:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
post = Post.new
|
71
|
+
post.unsubmitted? # true, the post will initially be in the 'unsubmitted' state
|
72
|
+
post.can_submit? # true, the 'submit' event is defined on the current state
|
73
|
+
post.can_review? # false, the 'review' event is not defined on the current state
|
74
|
+
post.submit! # invokes the submit event
|
75
|
+
post.submitted_awaiting_review? # true, the post is in the 'submitted.awaiting_review' state
|
76
|
+
post.submitted? # true, the 'submitted' state is a parent of the current state
|
77
|
+
```
|
78
|
+
|
79
|
+
## Handling Events
|
80
|
+
|
81
|
+
Most applications will require special logic to be performed during state transitions. Handlers for events can be defined as follows:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
class PostStates < StateManager::Base
|
85
|
+
state :unsubmitted do
|
86
|
+
event :submit, :transitions_to => 'submitted.awaiting_review'
|
87
|
+
end
|
88
|
+
state :submitted
|
89
|
+
event :reject, :transitions_to => 'rejected'
|
90
|
+
state :awaiting_review do
|
91
|
+
event :review, :transitions_to => 'submitted.reviewing'
|
92
|
+
end
|
93
|
+
state :reviewing do
|
94
|
+
event :accept, :transitions_to => 'active'
|
95
|
+
event :clarify, :transitions_to => 'submitted.clarifying'
|
96
|
+
end
|
97
|
+
state :clarifying do
|
98
|
+
event :review, :transitions_to => 'submitted.reviewing'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
state :active
|
102
|
+
state :rejected
|
103
|
+
|
104
|
+
# Under the hood, the `state` macro creates a class with the same name as the state. Here we add to the definition of that class.
|
105
|
+
class Unsubmitted
|
106
|
+
# Defines a handler for the submit event. Events can have arguments
|
107
|
+
def submit(reason=nil)
|
108
|
+
# Do something, the post is available as either the `resource` or `post` property
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class Submitted
|
113
|
+
class AwaitingReview
|
114
|
+
# Handles the review event. This will *not* handle the review event for the 'submitted.clarifying' state
|
115
|
+
def review
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Events on parent states are available to their children.
|
120
|
+
def reject
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
## Under the Hood
|
127
|
+
|
128
|
+
As suggested in the above example, states and events really just correspond to classes and methods of the state manager. In fact, the `state` macro is really just syntactic sugar around defining a `StateManager::State` subclass to the current state--the root state manager is also a state.
|
129
|
+
|
130
|
+
On the resource, the `state_manager` macro makes an instance of the specified `StateManager::Base` subclass available through the "#{property}_manager" attribute on the resource. The above examples of helper methods is essentially syntactic sugar on the following:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
post = Post.new
|
134
|
+
post.state_manager.in_state?('unsubmitted') # true, the post will initially be in the 'unsubmitted' state
|
135
|
+
post.state_manager.respond_to_event?('submit') # true, the 'submit' event is defined on the current state
|
136
|
+
post.state_manager.respond_to_event?('review')? # false, the 'review' event is not defined on the current state
|
137
|
+
post.state_manager.send_event!('submit') # invokes the submit event
|
138
|
+
post.state_manager.in_state?('submitted.awaiting_review')? # true, the post is in the 'submitted.awaiting_review' state
|
139
|
+
post.state_manager.in_state?('submitted')? # true, the 'submitted' state is a parent of the current state
|
140
|
+
```
|
141
|
+
|
142
|
+
Furthermore, only leaf states are valid states for a resource. The state manager can also be explicitly transitioned to a state, however this should normally only be used inside an event handler:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
post.state_manager.transition_to('submitted.awaiting_review') # puts the post is in the 'submitted.awaiting_review' state
|
146
|
+
post.state_manager.transition_to('submitted') # throws a StateManager::InvalidState error, 'submitted' is not a leaf state
|
147
|
+
```
|
148
|
+
|
149
|
+
By default, the initial state will be the first state that was defined. This can be customized by setting the initial state:
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
class PostStates < StateManager::Base
|
153
|
+
initial_state :rejected
|
154
|
+
...
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
The current state can also be accessed from the state manager:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
post.state_manager.current_state.name # 'awaiting_review'
|
162
|
+
post.state_manager.current_state.path # 'submitted.awaiting_review'
|
163
|
+
```
|
164
|
+
|
165
|
+
## Callbacks
|
166
|
+
|
167
|
+
StateManager has several callbacks that can be hooked into:
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
class PostStates < StateManager::Base
|
171
|
+
state :unsubmitted do
|
172
|
+
event :submit, :transitions_to => 'submitted.awaiting_review'
|
173
|
+
end
|
174
|
+
state :submitted
|
175
|
+
|
176
|
+
# Called when the 'submitted' state is being entered
|
177
|
+
def enter
|
178
|
+
end
|
179
|
+
|
180
|
+
# Called when the 'submitted' state is being exited.
|
181
|
+
def exit
|
182
|
+
end
|
183
|
+
|
184
|
+
# After it has been entered
|
185
|
+
def entered
|
186
|
+
end
|
187
|
+
|
188
|
+
# After it has been exited
|
189
|
+
def exited
|
190
|
+
end
|
191
|
+
|
192
|
+
event :reject, :transitions_to => 'rejected'
|
193
|
+
state :awaiting_review do
|
194
|
+
event :review, :transitions_to => 'submitted.reviewing'
|
195
|
+
end
|
196
|
+
|
197
|
+
...
|
198
|
+
|
199
|
+
# Called before every transition
|
200
|
+
def will_transition(from, to, event)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Called after ever transition
|
204
|
+
def did_transition(from, to, event)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
```
|
208
|
+
|
209
|
+
In the above example, transitioning between 'submitted.awaiting_review' and 'submitted.reviewing' will *not* trigger the the enter/exit callbacks for the 'submitted' state, however it will be called for the two sub-states.
|
210
|
+
|
211
|
+
## Delayed Job Integration
|
212
|
+
|
213
|
+
StateManager comes out of the box with support for [delayed_job](https://github.com/tobi/delayed_job). If delayed_job is available, events can be defined with a `:delay` property which indicates a delay after which the event should automatically be triggered:
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
class UserStates < StateManager::Base
|
217
|
+
state :submitted do
|
218
|
+
event :activate, :transitions_to => :active, :delay => 2.days
|
219
|
+
end
|
220
|
+
|
221
|
+
state :active
|
222
|
+
end
|
223
|
+
```
|
224
|
+
|
225
|
+
In this example, the `activate` event will be called by delayed_job automatically after 2 days unless it is called programatically before then.
|
226
|
+
|
227
|
+
## Contributing to statemanager
|
228
|
+
|
229
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
230
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
231
|
+
* Fork the project.
|
232
|
+
* Start a feature/bugfix branch.
|
233
|
+
* Commit and push until you are happy with your contribution.
|
234
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
235
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
236
|
+
|
237
|
+
## Copyright
|
238
|
+
|
239
|
+
Copyright (c) 2012 Gordon Hempton. See LICENSE.txt for
|
240
|
+
further details.
|
241
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "state_manager"
|
18
|
+
gem.homepage = "http://github.com/ghempton/statemanager"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = "%Q{Finite state machine implementation.}"
|
21
|
+
gem.description = %Q{Finite state machine implementation that keeps logic separate from model classes and supports sub-states.}
|
22
|
+
gem.email = "ghempton@gmail.com"
|
23
|
+
gem.authors = ["Gordon Hempton"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/*_test.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
task :default => :test
|
36
|
+
|
37
|
+
require 'rdoc/task'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "statemanager #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.3
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module StateManager
|
2
|
+
module Adapters
|
3
|
+
module ActiveRecord
|
4
|
+
include Base
|
5
|
+
|
6
|
+
def self.matching_ancestors
|
7
|
+
%w(ActiveRecord::Base)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ResourceMethods
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
# Make sure that the model is in a valid state before it is saved
|
14
|
+
base.before_validation :_validate_states
|
15
|
+
|
16
|
+
base.extend(ClassMethods)
|
17
|
+
end
|
18
|
+
|
19
|
+
def _validate_states
|
20
|
+
self.validate_states!
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
def state_manager_added(property, klass, options)
|
25
|
+
class_eval do
|
26
|
+
klass.specification.states.keys.each do |state|
|
27
|
+
# The connection might not be ready when defining this code is
|
28
|
+
# reached so we wrap in a lamda.
|
29
|
+
scope state, lambda {
|
30
|
+
conn = ::ActiveRecord::Base.connection
|
31
|
+
column = conn.quote_column_name klass._state_property
|
32
|
+
query = "#{column} = ? OR #{column} LIKE ?"
|
33
|
+
like_term = "#{state.to_s}.%"
|
34
|
+
where(query, state, like_term)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
module ManagerMethods
|
44
|
+
|
45
|
+
def write_state(value)
|
46
|
+
resource.send :update_attribute, self.class._state_property, value.path
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module StateManager
|
2
|
+
module Adapters
|
3
|
+
module Base
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# The name of the adapter
|
7
|
+
def adapter_name
|
8
|
+
@adapter_name ||= begin
|
9
|
+
name = self.name.split('::').last
|
10
|
+
name.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
11
|
+
name.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
12
|
+
name.downcase!
|
13
|
+
name.to_sym
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Whether this adapter is available for the current library. This
|
18
|
+
# is only true if the ORM that the adapter is for is currently
|
19
|
+
# defined.
|
20
|
+
def available?
|
21
|
+
matching_ancestors.any? && Object.const_defined?(matching_ancestors[0].split('::')[0])
|
22
|
+
end
|
23
|
+
|
24
|
+
# The list of ancestor names that cause this adapter to matched.
|
25
|
+
def matching_ancestors
|
26
|
+
[]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Whether the adapter should be used for the given class.
|
30
|
+
def matches?(klass)
|
31
|
+
matches_ancestors?(klass.ancestors.map {|ancestor| ancestor.name})
|
32
|
+
end
|
33
|
+
|
34
|
+
# Whether the adapter should be used for the given list of ancestors.
|
35
|
+
def matches_ancestors?(ancestors)
|
36
|
+
(ancestors & matching_ancestors).any?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.included(base)
|
41
|
+
return if base < StateManager::Base
|
42
|
+
base.class_eval { extend ClassMethods }
|
43
|
+
end
|
44
|
+
|
45
|
+
extend ClassMethods
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Load each available adapter
|
2
|
+
require 'state_manager/adapters/base'
|
3
|
+
Dir["#{File.dirname(__FILE__)}/adapters/*.rb"].sort.each do |path|
|
4
|
+
require "state_manager/adapters/#{File.basename(path)}"
|
5
|
+
end
|
6
|
+
|
7
|
+
module StateManager
|
8
|
+
|
9
|
+
class AdapterNotFound < StandardError; end;
|
10
|
+
|
11
|
+
module Adapters
|
12
|
+
def self.match(klass)
|
13
|
+
all.detect {|adapter| adapter.matches?(klass)}
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.match_ancestors(ancestors)
|
17
|
+
all.detect {|adapter| adapter.matches_ancestors?(ancestors)}
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.find_by_name(name)
|
21
|
+
all.detect {|adapter| adapter.integration_name == name} || raise(AdapterNotFound.new(name))
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.all
|
25
|
+
constants = self.constants.map {|c| c.to_s}.sort
|
26
|
+
constants.map {|c| const_get(c)}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|