big_machine 1.0.0
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.
- data/MIT-LICENSE +20 -0
- data/README.md +121 -0
- data/Rakefile +38 -0
- data/lib/big_machine.rb +63 -0
- data/lib/big_machine/active_record.rb +32 -0
- data/lib/big_machine/lock.rb +26 -0
- data/lib/big_machine/state.rb +29 -0
- data/lib/big_machine/transition_methods.rb +9 -0
- data/lib/big_machine/version.rb +3 -0
- data/lib/tasks/big_machine_tasks.rake +4 -0
- data/test/big_machine_test.rb +126 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +65 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +31 -0
- data/test/dummy/config/environments/production.rb +64 -0
- data/test/dummy/config/environments/test.rb +35 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +10 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +58 -0
- data/test/dummy/log/test.log +0 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/test_helper.rb +36 -0
- metadata +126 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
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,121 @@
|
|
1
|
+
# Big machine [][travis] [][gemnasium] [][codeclimate]
|
2
|
+
|
3
|
+
[travis]: http://travis-ci.org/alaibe/big_machine
|
4
|
+
[gemnasium]: https://gemnasium.com/alaibe/big_machine
|
5
|
+
[codeclimate]: https://codeclimate.com/github/alaibe/big_machine
|
6
|
+
|
7
|
+
Big machine is a Gem which give state to your object
|
8
|
+
|
9
|
+
## Resources
|
10
|
+
Bugs
|
11
|
+
|
12
|
+
* http://github.com/alaibe/big_machine/issues
|
13
|
+
|
14
|
+
Development
|
15
|
+
|
16
|
+
* http://github.com/alaibe/big_machine
|
17
|
+
|
18
|
+
Testing
|
19
|
+
|
20
|
+
* http://travis-ci.org/alaibe/big_machine
|
21
|
+
|
22
|
+
Source
|
23
|
+
|
24
|
+
* git://github.com/alaibe/big_machine.git
|
25
|
+
|
26
|
+
## Install
|
27
|
+
|
28
|
+
Add this to your Gemfile
|
29
|
+
``` ruby
|
30
|
+
gem 'big_machine'
|
31
|
+
```
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
Create your first states
|
36
|
+
``` ruby
|
37
|
+
class Draft < BigMachine::State
|
38
|
+
def publish
|
39
|
+
transition_to Online
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Online < BigMachine::State
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
Make your object stateful
|
48
|
+
``` ruby
|
49
|
+
class Car
|
50
|
+
include BigMachine
|
51
|
+
|
52
|
+
big_machine initial_state: :draft
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
Now it's possible to publish your car object:
|
57
|
+
``` ruby
|
58
|
+
car = Car.new
|
59
|
+
car.current_state # => Draft
|
60
|
+
car.publish
|
61
|
+
car.current_state # => Online
|
62
|
+
```
|
63
|
+
|
64
|
+
Of course your object can be an ActiveRecord::Base object. In this case, the object must have a state column. ( if not, see the next section )
|
65
|
+
|
66
|
+
## Big machine options
|
67
|
+
|
68
|
+
big_machine method can take several options:
|
69
|
+
* initial_state is the only one necessary option
|
70
|
+
* state_attribute is available only if the object is an active record object
|
71
|
+
* workflow if you want to change the normal worklow
|
72
|
+
It's possible to call workflow_is method in your different states
|
73
|
+
|
74
|
+
###example
|
75
|
+
|
76
|
+
``` ruby
|
77
|
+
big_machine initial_state: :dradt, state_attribute: :big_state, workflow: small
|
78
|
+
|
79
|
+
class Draft < BigMachine::State
|
80
|
+
|
81
|
+
def publish
|
82
|
+
return if workflow_is :small
|
83
|
+
|
84
|
+
transition_to Online
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
## Lock module
|
91
|
+
|
92
|
+
A state can include lock module, it will lock the state of your object when your enter in it.
|
93
|
+
The unlock method should be call to unlock the module
|
94
|
+
|
95
|
+
``` ruby
|
96
|
+
class Draft < BigMachine::State
|
97
|
+
include BigMachine::Lock
|
98
|
+
|
99
|
+
def publish
|
100
|
+
transition_to Online
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
class Article
|
106
|
+
include BigMachine
|
107
|
+
|
108
|
+
big_machine initial_state: :dradt
|
109
|
+
end
|
110
|
+
|
111
|
+
article = Article.new
|
112
|
+
article.publish
|
113
|
+
article.current_state # => Draft
|
114
|
+
article.unlock
|
115
|
+
article.publish
|
116
|
+
article.current_state # => Online
|
117
|
+
```
|
118
|
+
|
119
|
+
## Contributors
|
120
|
+
|
121
|
+
*Anthony Laibe
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'BigMachine'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
|
30
|
+
Rake::TestTask.new(:test) do |t|
|
31
|
+
t.libs << 'lib'
|
32
|
+
t.libs << 'test'
|
33
|
+
t.pattern = 'test/**/*_test.rb'
|
34
|
+
t.verbose = false
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
task :default => :test
|
data/lib/big_machine.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require "active_support"
|
2
|
+
require "forwardable"
|
3
|
+
require "big_machine/state"
|
4
|
+
require "big_machine/active_record"
|
5
|
+
require "big_machine/transition_methods"
|
6
|
+
require "big_machine/lock"
|
7
|
+
|
8
|
+
module BigMachine
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
class_attribute :initial_state
|
13
|
+
class_attribute :workflow
|
14
|
+
|
15
|
+
if active_record_model?
|
16
|
+
include BigMachine::ActiveRecord
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
def active_record_model?
|
22
|
+
defined?(::ActiveRecord::Base) && self.ancestors.include?(::ActiveRecord::Base)
|
23
|
+
end
|
24
|
+
|
25
|
+
def big_machine(options)
|
26
|
+
self.initial_state = options[:initial_state]
|
27
|
+
self.workflow = options[:workflow]
|
28
|
+
set_initial_state_class
|
29
|
+
end
|
30
|
+
|
31
|
+
def initial_state_class
|
32
|
+
@initial_state_class
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_initial_state_class
|
36
|
+
@initial_state_class = self.initial_state.to_s.camelize.constantize
|
37
|
+
include ::BigMachine::TransitionMethods
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def current_state
|
42
|
+
set_current_state(self.class.initial_state_class) unless @current_state
|
43
|
+
|
44
|
+
@current_state
|
45
|
+
end
|
46
|
+
|
47
|
+
def set_current_state(new_state_class)
|
48
|
+
@current_state = new_state_class.new self
|
49
|
+
|
50
|
+
forward_current_state
|
51
|
+
end
|
52
|
+
|
53
|
+
def forward_current_state
|
54
|
+
extend SingleForwardable
|
55
|
+
def_delegators :current_state, *current_state.class.transition_methods
|
56
|
+
end
|
57
|
+
|
58
|
+
def transition_to(next_state_class)
|
59
|
+
current_state.exit
|
60
|
+
set_current_state next_state_class
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module BigMachine
|
2
|
+
module ActiveRecord
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :state_attribute
|
7
|
+
after_initialize :set_current_state_from_db
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def big_machine(options = {})
|
12
|
+
super options
|
13
|
+
self.state_attribute = options[:state_attribute] || 'state'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_current_state_from_db
|
18
|
+
attribute = send state_attribute
|
19
|
+
set_current_state(attribute.constantize) if attribute
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_current_state(new_state_class)
|
23
|
+
super(new_state_class)
|
24
|
+
send "#{state_attribute}=", new_state_class.name
|
25
|
+
end
|
26
|
+
|
27
|
+
def transition_to(next_state_class)
|
28
|
+
super(next_state_class)
|
29
|
+
save!
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module BigMachine
|
2
|
+
module Lock
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
end
|
7
|
+
|
8
|
+
def locked?
|
9
|
+
@locked
|
10
|
+
end
|
11
|
+
|
12
|
+
def enter
|
13
|
+
@locked = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def unlock
|
17
|
+
@locked = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def transition_to(state_class)
|
21
|
+
return if @locked
|
22
|
+
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module BigMachine
|
2
|
+
class State
|
3
|
+
|
4
|
+
attr_reader :stateful
|
5
|
+
|
6
|
+
def initialize(stateful)
|
7
|
+
@stateful = stateful
|
8
|
+
enter
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.transition_methods
|
12
|
+
public_instance_methods - State.public_instance_methods
|
13
|
+
end
|
14
|
+
|
15
|
+
def transition_to(state_class)
|
16
|
+
@stateful.transition_to(state_class)
|
17
|
+
end
|
18
|
+
|
19
|
+
def workflow_is(name)
|
20
|
+
@stateful.workflow == name
|
21
|
+
end
|
22
|
+
|
23
|
+
def enter
|
24
|
+
end
|
25
|
+
|
26
|
+
def exit
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Draft < BigMachine::State
|
4
|
+
def publish
|
5
|
+
return if workflow_is :small
|
6
|
+
|
7
|
+
transition_to Online
|
8
|
+
end
|
9
|
+
|
10
|
+
def lock
|
11
|
+
transition_to LockState
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class LockState < BigMachine::State
|
16
|
+
include BigMachine::Lock
|
17
|
+
|
18
|
+
def back_to_draft
|
19
|
+
transition_to Draft
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Online < BigMachine::State
|
24
|
+
def back_to_draft
|
25
|
+
transition_to Draft
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class DummyMachine
|
30
|
+
include BigMachine
|
31
|
+
|
32
|
+
big_machine initial_state: :draft
|
33
|
+
end
|
34
|
+
|
35
|
+
class DummyWithWorkflow
|
36
|
+
include BigMachine
|
37
|
+
|
38
|
+
big_machine initial_state: :draft, workflow: :small
|
39
|
+
end
|
40
|
+
|
41
|
+
class DummyWithActiveRecord < ActiveRecord::Base
|
42
|
+
include BigMachine
|
43
|
+
|
44
|
+
attr_accessor :state
|
45
|
+
|
46
|
+
big_machine initial_state: :draft
|
47
|
+
|
48
|
+
def initialize(state)
|
49
|
+
@state = state
|
50
|
+
end
|
51
|
+
|
52
|
+
def state
|
53
|
+
@state
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class DummyWithActiveRecordAndOtherState < ActiveRecord::Base
|
58
|
+
include BigMachine
|
59
|
+
|
60
|
+
attr_accessor :other_state
|
61
|
+
|
62
|
+
big_machine initial_state: :draft, state_attribute: :other_state
|
63
|
+
|
64
|
+
def other_state
|
65
|
+
'Online'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class BigMachineTest < ActiveSupport::TestCase
|
70
|
+
setup do
|
71
|
+
@dummy = DummyMachine.new
|
72
|
+
@dummyAR = DummyWithActiveRecord.new('Online')
|
73
|
+
end
|
74
|
+
|
75
|
+
test "big_machine set initial state" do
|
76
|
+
assert_equal 'Draft', @dummy.current_state.class.name
|
77
|
+
end
|
78
|
+
|
79
|
+
test "big_machine create new state when start transition" do
|
80
|
+
@dummy.publish
|
81
|
+
assert_equal 'Online', @dummy.current_state.class.name
|
82
|
+
@dummy.back_to_draft
|
83
|
+
assert_equal 'Draft', @dummy.current_state.class.name
|
84
|
+
end
|
85
|
+
|
86
|
+
test "big machine must refuse unavailable action" do
|
87
|
+
@dummy.publish
|
88
|
+
assert_raise NoMethodError do
|
89
|
+
@dummy.publish
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
test "big machine can lock state" do
|
94
|
+
@dummy.lock
|
95
|
+
assert @dummy.locked?
|
96
|
+
@dummy.back_to_draft
|
97
|
+
assert_equal 'LockState', @dummy.current_state.class.name
|
98
|
+
assert @dummy.current_state.locked?
|
99
|
+
@dummy.unlock
|
100
|
+
assert !@dummy.current_state.locked?
|
101
|
+
assert_equal 'LockState', @dummy.current_state.class.name
|
102
|
+
@dummy.back_to_draft
|
103
|
+
assert_equal 'Draft', @dummy.current_state.class.name
|
104
|
+
end
|
105
|
+
|
106
|
+
test "big_machine can have workflow to refuse some action" do
|
107
|
+
@dummyW = DummyWithWorkflow.new
|
108
|
+
@dummyW.publish
|
109
|
+
assert_equal 'Draft', @dummyW.current_state.class.name
|
110
|
+
end
|
111
|
+
|
112
|
+
test "big_machine read state from database" do
|
113
|
+
assert_equal 'Online', @dummyAR.current_state.class.name
|
114
|
+
end
|
115
|
+
|
116
|
+
test "big_machine read state from specify column in database" do
|
117
|
+
@dummyAROS = DummyWithActiveRecordAndOtherState.new
|
118
|
+
assert_equal 'Online', @dummyAROS.current_state.class.name
|
119
|
+
end
|
120
|
+
|
121
|
+
test "big_machine must update attribute when state change" do
|
122
|
+
@dummyAR.back_to_draft
|
123
|
+
assert_equal 'Draft', @dummyAR.current_state.class.name
|
124
|
+
assert_equal 'Draft', @dummyAR.state
|
125
|
+
end
|
126
|
+
end
|