big_machine 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +121 -0
  3. data/Rakefile +38 -0
  4. data/lib/big_machine.rb +63 -0
  5. data/lib/big_machine/active_record.rb +32 -0
  6. data/lib/big_machine/lock.rb +26 -0
  7. data/lib/big_machine/state.rb +29 -0
  8. data/lib/big_machine/transition_methods.rb +9 -0
  9. data/lib/big_machine/version.rb +3 -0
  10. data/lib/tasks/big_machine_tasks.rake +4 -0
  11. data/test/big_machine_test.rb +126 -0
  12. data/test/dummy/README.rdoc +261 -0
  13. data/test/dummy/Rakefile +7 -0
  14. data/test/dummy/app/assets/javascripts/application.js +15 -0
  15. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  16. data/test/dummy/app/controllers/application_controller.rb +3 -0
  17. data/test/dummy/app/helpers/application_helper.rb +2 -0
  18. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  19. data/test/dummy/config.ru +4 -0
  20. data/test/dummy/config/application.rb +65 -0
  21. data/test/dummy/config/boot.rb +10 -0
  22. data/test/dummy/config/environment.rb +5 -0
  23. data/test/dummy/config/environments/development.rb +31 -0
  24. data/test/dummy/config/environments/production.rb +64 -0
  25. data/test/dummy/config/environments/test.rb +35 -0
  26. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  27. data/test/dummy/config/initializers/inflections.rb +15 -0
  28. data/test/dummy/config/initializers/mime_types.rb +5 -0
  29. data/test/dummy/config/initializers/secret_token.rb +7 -0
  30. data/test/dummy/config/initializers/session_store.rb +8 -0
  31. data/test/dummy/config/initializers/wrap_parameters.rb +10 -0
  32. data/test/dummy/config/locales/en.yml +5 -0
  33. data/test/dummy/config/routes.rb +58 -0
  34. data/test/dummy/log/test.log +0 -0
  35. data/test/dummy/public/404.html +26 -0
  36. data/test/dummy/public/422.html +26 -0
  37. data/test/dummy/public/500.html +25 -0
  38. data/test/dummy/public/favicon.ico +0 -0
  39. data/test/dummy/script/rails +6 -0
  40. data/test/test_helper.rb +36 -0
  41. metadata +126 -0
@@ -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.
@@ -0,0 +1,121 @@
1
+ # Big machine [![Build Status](https://secure.travis-ci.org/alaibe/big_machine.png)][travis] [![Dependency Status](https://gemnasium.com/alaibe/big_machine.png)][gemnasium] [![Code Climate](https://codeclimate.com/badge.png)][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
@@ -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
@@ -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,9 @@
1
+ module BigMachine
2
+ module TransitionMethods
3
+ extend Forwardable
4
+
5
+ def self.included(base)
6
+ def_delegators :current_state, *base.initial_state_class.transition_methods
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module BigMachine
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :big_machine do
3
+ # # Task goes here
4
+ # 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