ministry_of_state 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +82 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +44 -0
- data/VERSION +1 -0
- data/lib/ministry_of_state.rb +4 -0
- data/lib/ministry_of_state/ministry_of_state.rb +135 -0
- data/lib/ministry_of_state/railtie.rb +13 -0
- data/ministry_of_state.gemspec +78 -0
- data/test/article.rb +4 -0
- data/test/blog.rb +17 -0
- data/test/helper.rb +16 -0
- data/test/post.rb +25 -0
- data/test/student.rb +11 -0
- data/test/test_ministry_of_state.rb +189 -0
- data/test/user.rb +25 -0
- metadata +166 -0
data/.document
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
abstract (1.0.0)
|
5
|
+
actionmailer (3.0.3)
|
6
|
+
actionpack (= 3.0.3)
|
7
|
+
mail (~> 2.2.9)
|
8
|
+
actionpack (3.0.3)
|
9
|
+
activemodel (= 3.0.3)
|
10
|
+
activesupport (= 3.0.3)
|
11
|
+
builder (~> 2.1.2)
|
12
|
+
erubis (~> 2.6.6)
|
13
|
+
i18n (~> 0.4)
|
14
|
+
rack (~> 1.2.1)
|
15
|
+
rack-mount (~> 0.6.13)
|
16
|
+
rack-test (~> 0.5.6)
|
17
|
+
tzinfo (~> 0.3.23)
|
18
|
+
activemodel (3.0.3)
|
19
|
+
activesupport (= 3.0.3)
|
20
|
+
builder (~> 2.1.2)
|
21
|
+
i18n (~> 0.4)
|
22
|
+
activerecord (3.0.3)
|
23
|
+
activemodel (= 3.0.3)
|
24
|
+
activesupport (= 3.0.3)
|
25
|
+
arel (~> 2.0.2)
|
26
|
+
tzinfo (~> 0.3.23)
|
27
|
+
activeresource (3.0.3)
|
28
|
+
activemodel (= 3.0.3)
|
29
|
+
activesupport (= 3.0.3)
|
30
|
+
activesupport (3.0.3)
|
31
|
+
arel (2.0.7)
|
32
|
+
builder (2.1.2)
|
33
|
+
erubis (2.6.6)
|
34
|
+
abstract (>= 1.0.0)
|
35
|
+
git (1.2.5)
|
36
|
+
i18n (0.5.0)
|
37
|
+
jeweler (1.5.2)
|
38
|
+
bundler (~> 1.0.0)
|
39
|
+
git (>= 1.2.5)
|
40
|
+
rake
|
41
|
+
mail (2.2.14)
|
42
|
+
activesupport (>= 2.3.6)
|
43
|
+
i18n (>= 0.4.0)
|
44
|
+
mime-types (~> 1.16)
|
45
|
+
treetop (~> 1.4.8)
|
46
|
+
mime-types (1.16)
|
47
|
+
mysql2 (0.2.6)
|
48
|
+
polyglot (0.3.1)
|
49
|
+
rack (1.2.1)
|
50
|
+
rack-mount (0.6.13)
|
51
|
+
rack (>= 1.0.0)
|
52
|
+
rack-test (0.5.7)
|
53
|
+
rack (>= 1.0)
|
54
|
+
rails (3.0.3)
|
55
|
+
actionmailer (= 3.0.3)
|
56
|
+
actionpack (= 3.0.3)
|
57
|
+
activerecord (= 3.0.3)
|
58
|
+
activeresource (= 3.0.3)
|
59
|
+
activesupport (= 3.0.3)
|
60
|
+
bundler (~> 1.0)
|
61
|
+
railties (= 3.0.3)
|
62
|
+
railties (3.0.3)
|
63
|
+
actionpack (= 3.0.3)
|
64
|
+
activesupport (= 3.0.3)
|
65
|
+
rake (>= 0.8.7)
|
66
|
+
thor (~> 0.14.4)
|
67
|
+
rake (0.8.7)
|
68
|
+
shoulda (2.11.3)
|
69
|
+
thor (0.14.6)
|
70
|
+
treetop (1.4.9)
|
71
|
+
polyglot (>= 0.3.1)
|
72
|
+
tzinfo (0.3.24)
|
73
|
+
|
74
|
+
PLATFORMS
|
75
|
+
ruby
|
76
|
+
|
77
|
+
DEPENDENCIES
|
78
|
+
bundler (~> 1.0.0)
|
79
|
+
jeweler (~> 1.5.2)
|
80
|
+
mysql2
|
81
|
+
rails (= 3.0.3)
|
82
|
+
shoulda
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Hemant Kumar
|
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.rdoc
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
= ministry_of_state
|
2
|
+
|
3
|
+
Because State machines are simple, they do not have to be greater than 2000 lines of code (and complexity).
|
4
|
+
|
5
|
+
= Features
|
6
|
+
* Events are wrapped in a transaction and record is locked before going for a state change.
|
7
|
+
* Ignores dirty attributes (still on the fence with this one)
|
8
|
+
* Events and states can be inhereted across classes.
|
9
|
+
* Simple 136 lines of code, covered with close to > 98% test coverage.
|
10
|
+
|
11
|
+
= Using it
|
12
|
+
|
13
|
+
class User < ActiveRecord::Base
|
14
|
+
include MinistryOfState
|
15
|
+
ministry_of_state(:initial_state => 'pending', :state_column => 'status' )
|
16
|
+
add_state :active
|
17
|
+
add_state :pending_payment
|
18
|
+
|
19
|
+
add_event(:activate) do
|
20
|
+
transitions(:from => :pending, :to => :active)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
== Contributing to ministry_of_state
|
25
|
+
|
26
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
27
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
28
|
+
* Fork the project
|
29
|
+
* Start a feature/bugfix branch
|
30
|
+
* Commit and push until you are happy with your contribution
|
31
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
32
|
+
* 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.
|
33
|
+
|
34
|
+
== Copyright
|
35
|
+
|
36
|
+
Copyright (c) 2011 Hemant, Neeraj and Vishnu. See LICENSE.txt for
|
37
|
+
further details.
|
38
|
+
|
39
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "ministry_of_state"
|
16
|
+
gem.homepage = "http://github.com/gnufied/ministry_of_state"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{Handling state machines}
|
19
|
+
gem.description = %Q{A ActiveRecord plugin for working with state machines}
|
20
|
+
gem.email = "hkumar@crri.co.in"
|
21
|
+
gem.authors = ["Hemant Kumar"]
|
22
|
+
end
|
23
|
+
|
24
|
+
Jeweler::RubygemsDotOrgTasks.new
|
25
|
+
|
26
|
+
require 'rake/testtask'
|
27
|
+
Rake::TestTask.new(:test) do |test|
|
28
|
+
test.libs << 'lib' << 'test'
|
29
|
+
test.pattern = 'test/**/test_*.rb'
|
30
|
+
test.verbose = true
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
task :default => :test
|
35
|
+
|
36
|
+
require 'rake/rdoctask'
|
37
|
+
Rake::RDocTask.new do |rdoc|
|
38
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
39
|
+
|
40
|
+
rdoc.rdoc_dir = 'rdoc'
|
41
|
+
rdoc.title = "ministry_of_state #{version}"
|
42
|
+
rdoc.rdoc_files.include('README*')
|
43
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
44
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module MinistryOfState
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
|
5
|
+
class InvalidState < Exception; end
|
6
|
+
class NoInitialState < Exception; end
|
7
|
+
class TransitionNotAllowed < Exception; end
|
8
|
+
class InvalidStateColumn < Exception; end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def ministry_of_state(opts = {})
|
12
|
+
class_attribute :states
|
13
|
+
class_attribute :events
|
14
|
+
|
15
|
+
class_attribute :initial_state
|
16
|
+
class_attribute :state_column
|
17
|
+
|
18
|
+
raise NoInitialState.new("You need to specify initial state") unless opts[:initial_state]
|
19
|
+
|
20
|
+
self.initial_state = opts[:initial_state]
|
21
|
+
self.state_column = opts[:state_column] || 'status'
|
22
|
+
self.states = HashWithIndifferentAccess.new
|
23
|
+
self.events = HashWithIndifferentAccess.new
|
24
|
+
|
25
|
+
add_state(self.initial_state)
|
26
|
+
before_create :set_initial_state
|
27
|
+
after_create :run_initial_state_actions
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_state(name, options={})
|
31
|
+
self.states.merge!(name => options)
|
32
|
+
class_eval <<-RUBY,__FILE__,__LINE__+1
|
33
|
+
def #{name}?; check_state('#{name}'); end
|
34
|
+
RUBY
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_event(name, &block)
|
38
|
+
opts = class_eval(&block)
|
39
|
+
self.events.merge!(name => opts)
|
40
|
+
class_eval <<-RUBY,__FILE__,__LINE__+1
|
41
|
+
def #{name.to_s}!
|
42
|
+
fire('#{name}')
|
43
|
+
end
|
44
|
+
RUBY
|
45
|
+
end
|
46
|
+
|
47
|
+
def transitions(opts = {})
|
48
|
+
if opts[:from].blank? || opts[:to].blank?
|
49
|
+
raise TransitionNotAllowed.new("You need to specify from and to states")
|
50
|
+
end
|
51
|
+
opts[:from] = Array(opts[:from]).map(&:to_sym)
|
52
|
+
{:from => opts[:from], :to => opts[:to], :guard => opts[:guard] }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module InstanceMethods
|
57
|
+
|
58
|
+
def set_initial_state
|
59
|
+
begin
|
60
|
+
enter = states[initial_state][:enter]
|
61
|
+
invoke_callback(enter) if enter
|
62
|
+
true
|
63
|
+
rescue StandardError => e
|
64
|
+
errors.add(:base, e.to_s)
|
65
|
+
false
|
66
|
+
end
|
67
|
+
write_attribute state_machine_column, initial_state
|
68
|
+
end
|
69
|
+
|
70
|
+
def run_initial_state_actions
|
71
|
+
begin
|
72
|
+
after = states[initial_state][:after]
|
73
|
+
invoke_callback(after) if after
|
74
|
+
true
|
75
|
+
rescue StandardError => e
|
76
|
+
errors.add(:base, e.to_s)
|
77
|
+
false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def current_state
|
82
|
+
send("#{state_machine_column}_was").to_sym
|
83
|
+
end
|
84
|
+
|
85
|
+
def check_state(state)
|
86
|
+
send("#{state_machine_column}_was") == state.to_s
|
87
|
+
end
|
88
|
+
|
89
|
+
def fire(event)
|
90
|
+
options = events[event]
|
91
|
+
to_state = options[:to]
|
92
|
+
raise TransitionNotAllowed.new("Invalid 'to' state '#{to_state}'") unless states[to_state]
|
93
|
+
check_guard = options[:guard] ? invoke_callback(options[:guard]) : true
|
94
|
+
return unless check_guard
|
95
|
+
begin
|
96
|
+
transaction do
|
97
|
+
enter = states[to_state][:enter]
|
98
|
+
after = states[to_state][:after]
|
99
|
+
exit = states[to_state][:exit]
|
100
|
+
|
101
|
+
invoke_callback(enter) if enter
|
102
|
+
self.lock!
|
103
|
+
t_current_state = send(state_machine_column).try(:to_sym)
|
104
|
+
check_transitions?(t_current_state, options)
|
105
|
+
write_attribute(state_machine_column,to_state.to_s)
|
106
|
+
save
|
107
|
+
invoke_callback(exit) if exit
|
108
|
+
invoke_callback(after) if after
|
109
|
+
end
|
110
|
+
self.errors.empty?
|
111
|
+
rescue StandardError => e
|
112
|
+
errors.add(:base, e.to_s)
|
113
|
+
false
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def state_machine_column
|
118
|
+
if(self.class.column_names.include?(state_column))
|
119
|
+
state_column
|
120
|
+
else
|
121
|
+
raise InvalidStateColumn.new("State column '#{state_column}' does not exist in table '#{self.class.table_name}'")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def check_transitions?(t_current_state, opts)
|
126
|
+
unless opts[:from].include?(t_current_state)
|
127
|
+
raise InvalidState.new("Invalid from state '#{t_current_state}' for target state '#{opts[:to]}'")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def invoke_callback(callback)
|
132
|
+
callback.is_a?(Proc) ? callback.call(self) : send(callback)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "rails"
|
2
|
+
require "active_record"
|
3
|
+
require "active_support"
|
4
|
+
require "active_support/core_ext/class"
|
5
|
+
require "active_support/memoizable"
|
6
|
+
require File.dirname(__FILE__) + "/ministry_of_state"
|
7
|
+
|
8
|
+
module MinistryOfState
|
9
|
+
class Ralitie < Rails::Railtie
|
10
|
+
end
|
11
|
+
# instance methods
|
12
|
+
end
|
13
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{ministry_of_state}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Hemant Kumar"]
|
12
|
+
s.date = %q{2011-02-01}
|
13
|
+
s.description = %q{A ActiveRecord plugin for working with state machines}
|
14
|
+
s.email = %q{hkumar@crri.co.in}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"lib/ministry_of_state.rb",
|
28
|
+
"lib/ministry_of_state/ministry_of_state.rb",
|
29
|
+
"lib/ministry_of_state/railtie.rb",
|
30
|
+
"ministry_of_state.gemspec",
|
31
|
+
"test/article.rb",
|
32
|
+
"test/blog.rb",
|
33
|
+
"test/helper.rb",
|
34
|
+
"test/post.rb",
|
35
|
+
"test/student.rb",
|
36
|
+
"test/test_ministry_of_state.rb",
|
37
|
+
"test/user.rb"
|
38
|
+
]
|
39
|
+
s.homepage = %q{http://github.com/gnufied/ministry_of_state}
|
40
|
+
s.licenses = ["MIT"]
|
41
|
+
s.require_paths = ["lib"]
|
42
|
+
s.rubygems_version = %q{1.4.1}
|
43
|
+
s.summary = %q{Handling state machines}
|
44
|
+
s.test_files = [
|
45
|
+
"test/article.rb",
|
46
|
+
"test/blog.rb",
|
47
|
+
"test/helper.rb",
|
48
|
+
"test/post.rb",
|
49
|
+
"test/student.rb",
|
50
|
+
"test/test_ministry_of_state.rb",
|
51
|
+
"test/user.rb"
|
52
|
+
]
|
53
|
+
|
54
|
+
if s.respond_to? :specification_version then
|
55
|
+
s.specification_version = 3
|
56
|
+
|
57
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
58
|
+
s.add_runtime_dependency(%q<rails>, ["= 3.0.3"])
|
59
|
+
s.add_runtime_dependency(%q<mysql2>, [">= 0"])
|
60
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
61
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
62
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
63
|
+
else
|
64
|
+
s.add_dependency(%q<rails>, ["= 3.0.3"])
|
65
|
+
s.add_dependency(%q<mysql2>, [">= 0"])
|
66
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
67
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
68
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
69
|
+
end
|
70
|
+
else
|
71
|
+
s.add_dependency(%q<rails>, ["= 3.0.3"])
|
72
|
+
s.add_dependency(%q<mysql2>, [">= 0"])
|
73
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
74
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
75
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
data/test/article.rb
ADDED
data/test/blog.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
class Blog < ActiveRecord::Base
|
2
|
+
include MinistryOfState
|
3
|
+
validates_presence_of :text
|
4
|
+
|
5
|
+
ministry_of_state(:initial_state => 'pending', :state_column => 'status' )
|
6
|
+
attr_accessor :foo
|
7
|
+
add_state :approved, :enter => :test_method
|
8
|
+
|
9
|
+
add_event :approve do
|
10
|
+
transitions :from => 'pending', :to => 'approved'
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_method
|
14
|
+
@foo = true
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
require 'ministry_of_state'
|
16
|
+
|
data/test/post.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
class Post < ActiveRecord::Base
|
2
|
+
include MinistryOfState
|
3
|
+
ministry_of_state(:initial_state => 'pending', :state_column => 'status' )
|
4
|
+
|
5
|
+
add_state :published, :after => :make_public
|
6
|
+
add_state :archived, :enter => :make_private
|
7
|
+
|
8
|
+
attr_accessor :public, :private
|
9
|
+
|
10
|
+
add_event(:publish) do
|
11
|
+
transitions(:from => :pending, :to => :published)
|
12
|
+
end
|
13
|
+
|
14
|
+
add_event(:archive) do
|
15
|
+
transitions(:from => [:pending,:published], :to => :archived)
|
16
|
+
end
|
17
|
+
|
18
|
+
def make_public
|
19
|
+
@public = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def make_private
|
23
|
+
@private = true
|
24
|
+
end
|
25
|
+
end
|
data/test/student.rb
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
require File.dirname(__FILE__) + '/blog'
|
3
|
+
require File.dirname(__FILE__) + '/user'
|
4
|
+
require File.dirname(__FILE__) + '/article'
|
5
|
+
require File.dirname(__FILE__) + '/student'
|
6
|
+
require File.dirname(__FILE__) + '/post'
|
7
|
+
|
8
|
+
ActiveRecord::Base.configurations = {
|
9
|
+
'db1' => {
|
10
|
+
:adapter => 'mysql2',
|
11
|
+
:username => 'root',
|
12
|
+
:encoding => 'utf8',
|
13
|
+
:database => 'ministry_of_state_test',
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
ActiveRecord::Base.establish_connection('db1')
|
18
|
+
|
19
|
+
class TestMinistryOfState < ActiveSupport::TestCase
|
20
|
+
|
21
|
+
setup do
|
22
|
+
Blog.connection.drop_table('blogs') if Blog.connection.table_exists?(:blogs)
|
23
|
+
Blog.connection.create_table :blogs do |t|
|
24
|
+
t.column :text, :text
|
25
|
+
t.column :status, :string
|
26
|
+
t.timestamps
|
27
|
+
end
|
28
|
+
|
29
|
+
User.connection.drop_table('users') if User.connection.table_exists?(:users)
|
30
|
+
User.connection.create_table :users do |u|
|
31
|
+
u.string :status
|
32
|
+
u.string :firstname
|
33
|
+
u.string :lastname
|
34
|
+
u.string :gender
|
35
|
+
u.datetime :login_at
|
36
|
+
u.string :login
|
37
|
+
u.string :type
|
38
|
+
end
|
39
|
+
|
40
|
+
Article.connection.drop_table('articles') if Article.connection.table_exists?(:articles)
|
41
|
+
Article.connection.create_table :articles do |a|
|
42
|
+
a.string :state
|
43
|
+
a.string :title
|
44
|
+
a.text :content
|
45
|
+
end
|
46
|
+
|
47
|
+
Post.connection.drop_table('posts') if Post.connection.table_exists?(:posts)
|
48
|
+
Post.connection.create_table :posts do |p|
|
49
|
+
p.string :status
|
50
|
+
p.string :title
|
51
|
+
p.text :content
|
52
|
+
p.timestamps
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "User should be able to define state" do
|
57
|
+
setup do
|
58
|
+
@blog = Blog.create(:text => "Hello world")
|
59
|
+
end
|
60
|
+
should "have initial state set" do
|
61
|
+
assert @blog.pending?
|
62
|
+
end
|
63
|
+
should "be able to change state" do
|
64
|
+
assert_nil @blog.foo
|
65
|
+
assert @blog.approve!
|
66
|
+
assert @blog.foo
|
67
|
+
assert @blog.reload.approved?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "Error while creating records" do
|
72
|
+
setup do
|
73
|
+
@blog = Blog.create()
|
74
|
+
end
|
75
|
+
should "have error object" do
|
76
|
+
assert !@blog.errors.blank?
|
77
|
+
assert !@blog.errors[:text].blank?
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "With conditional validations" do
|
82
|
+
setup do
|
83
|
+
@user = User.create(:firstname => "Hemant", :lastname => "kumar")
|
84
|
+
end
|
85
|
+
should "create valid record" do
|
86
|
+
assert @user.valid?
|
87
|
+
assert @user.pending?
|
88
|
+
end
|
89
|
+
should "not kick if for pending users" do
|
90
|
+
assert @user.activate!
|
91
|
+
assert @user.errors.blank?
|
92
|
+
end
|
93
|
+
|
94
|
+
should "kick for active users" do
|
95
|
+
@user.activate!
|
96
|
+
assert !@user.pending_payment!
|
97
|
+
assert !@user.errors.blank?
|
98
|
+
end
|
99
|
+
|
100
|
+
should "throw error for non-existant to state" do
|
101
|
+
assert_raise(MinistryOfState::TransitionNotAllowed) do
|
102
|
+
@user.foobar!
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "For non-existant state columns" do
|
108
|
+
should "throw error if state column is invalid" do
|
109
|
+
assert_raise(MinistryOfState::InvalidStateColumn) do
|
110
|
+
@article = Article.create
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "Not specifying from and to " do
|
116
|
+
should "throw error" do
|
117
|
+
assert_raise(MinistryOfState::TransitionNotAllowed) do
|
118
|
+
class Foo < ActiveRecord::Base
|
119
|
+
include MinistryOfState
|
120
|
+
ministry_of_state(:state_column => 'status',:initial_state => :pending)
|
121
|
+
add_state :active
|
122
|
+
add_event(:activate) do
|
123
|
+
transitions(:to => :active)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context "Not specifying initial state" do
|
131
|
+
should "throw error" do
|
132
|
+
assert_raise(MinistryOfState::NoInitialState) do
|
133
|
+
class Note < ActiveRecord::Base
|
134
|
+
include MinistryOfState
|
135
|
+
ministry_of_state(:state_column => 'status' )
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context "calling enter and exit callbacks for normal events" do
|
142
|
+
setup do
|
143
|
+
@post = Post.create(:title => "Hello", :content => "Good world")
|
144
|
+
end
|
145
|
+
should "call proper callbacks" do
|
146
|
+
assert @post.pending?
|
147
|
+
assert_nil @post.public
|
148
|
+
|
149
|
+
assert @post.publish!
|
150
|
+
assert @post.published?
|
151
|
+
assert_equal :published, @post.current_state
|
152
|
+
assert @post.public
|
153
|
+
|
154
|
+
assert_nil @post.private
|
155
|
+
assert @post.archive!
|
156
|
+
assert @post.archived?
|
157
|
+
assert @post.private
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context "for STI classes child" do
|
162
|
+
setup do
|
163
|
+
@student = Student.create(:firstname => "Hemant", :lastname => "kumar")
|
164
|
+
end
|
165
|
+
|
166
|
+
should "inherit all the parent states and events" do
|
167
|
+
assert @student.pending?
|
168
|
+
|
169
|
+
assert @student.activate!
|
170
|
+
assert @student.active?
|
171
|
+
|
172
|
+
@student.gender = 'male'
|
173
|
+
@student.save
|
174
|
+
assert @student.pending_payment!
|
175
|
+
assert @student.pending_payment?
|
176
|
+
end
|
177
|
+
|
178
|
+
should "be able to override events" do
|
179
|
+
@student.gender = 'male'
|
180
|
+
@student.save
|
181
|
+
assert @student.make_foo!
|
182
|
+
assert @student.foo?
|
183
|
+
|
184
|
+
assert @student.activate!
|
185
|
+
assert @student.active?
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
data/test/user.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
class User < ActiveRecord::Base
|
2
|
+
include MinistryOfState
|
3
|
+
validates_presence_of :gender, :on => :update, :if => :allow_gender_validation
|
4
|
+
validates_presence_of :firstname, :lastname
|
5
|
+
|
6
|
+
ministry_of_state(:initial_state => 'pending', :state_column => 'status' )
|
7
|
+
add_state :active
|
8
|
+
add_state :pending_payment
|
9
|
+
|
10
|
+
add_event(:activate) do
|
11
|
+
transitions(:from => :pending, :to => :active)
|
12
|
+
end
|
13
|
+
|
14
|
+
add_event(:pending_payment) do
|
15
|
+
transitions(:from => :active, :to => :pending_payment)
|
16
|
+
end
|
17
|
+
|
18
|
+
add_event(:foobar) do
|
19
|
+
transitions(:from => :pending, :to => :foobar)
|
20
|
+
end
|
21
|
+
|
22
|
+
def allow_gender_validation
|
23
|
+
!pending?
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ministry_of_state
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Hemant Kumar
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-02-01 00:00:00 +05:30
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
type: :runtime
|
23
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - "="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 1
|
29
|
+
segments:
|
30
|
+
- 3
|
31
|
+
- 0
|
32
|
+
- 3
|
33
|
+
version: 3.0.3
|
34
|
+
requirement: *id001
|
35
|
+
prerelease: false
|
36
|
+
name: rails
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
type: :runtime
|
39
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
requirement: *id002
|
49
|
+
prerelease: false
|
50
|
+
name: mysql2
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
type: :development
|
53
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
requirement: *id003
|
63
|
+
prerelease: false
|
64
|
+
name: shoulda
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
type: :development
|
67
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ~>
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 23
|
73
|
+
segments:
|
74
|
+
- 1
|
75
|
+
- 0
|
76
|
+
- 0
|
77
|
+
version: 1.0.0
|
78
|
+
requirement: *id004
|
79
|
+
prerelease: false
|
80
|
+
name: bundler
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
type: :development
|
83
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ~>
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
hash: 7
|
89
|
+
segments:
|
90
|
+
- 1
|
91
|
+
- 5
|
92
|
+
- 2
|
93
|
+
version: 1.5.2
|
94
|
+
requirement: *id005
|
95
|
+
prerelease: false
|
96
|
+
name: jeweler
|
97
|
+
description: A ActiveRecord plugin for working with state machines
|
98
|
+
email: hkumar@crri.co.in
|
99
|
+
executables: []
|
100
|
+
|
101
|
+
extensions: []
|
102
|
+
|
103
|
+
extra_rdoc_files:
|
104
|
+
- LICENSE.txt
|
105
|
+
- README.rdoc
|
106
|
+
files:
|
107
|
+
- .document
|
108
|
+
- Gemfile
|
109
|
+
- Gemfile.lock
|
110
|
+
- LICENSE.txt
|
111
|
+
- README.rdoc
|
112
|
+
- Rakefile
|
113
|
+
- VERSION
|
114
|
+
- lib/ministry_of_state.rb
|
115
|
+
- lib/ministry_of_state/ministry_of_state.rb
|
116
|
+
- lib/ministry_of_state/railtie.rb
|
117
|
+
- ministry_of_state.gemspec
|
118
|
+
- test/article.rb
|
119
|
+
- test/blog.rb
|
120
|
+
- test/helper.rb
|
121
|
+
- test/post.rb
|
122
|
+
- test/student.rb
|
123
|
+
- test/test_ministry_of_state.rb
|
124
|
+
- test/user.rb
|
125
|
+
has_rdoc: true
|
126
|
+
homepage: http://github.com/gnufied/ministry_of_state
|
127
|
+
licenses:
|
128
|
+
- MIT
|
129
|
+
post_install_message:
|
130
|
+
rdoc_options: []
|
131
|
+
|
132
|
+
require_paths:
|
133
|
+
- lib
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
135
|
+
none: false
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
hash: 3
|
140
|
+
segments:
|
141
|
+
- 0
|
142
|
+
version: "0"
|
143
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
|
+
none: false
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
hash: 3
|
149
|
+
segments:
|
150
|
+
- 0
|
151
|
+
version: "0"
|
152
|
+
requirements: []
|
153
|
+
|
154
|
+
rubyforge_project:
|
155
|
+
rubygems_version: 1.4.1
|
156
|
+
signing_key:
|
157
|
+
specification_version: 3
|
158
|
+
summary: Handling state machines
|
159
|
+
test_files:
|
160
|
+
- test/article.rb
|
161
|
+
- test/blog.rb
|
162
|
+
- test/helper.rb
|
163
|
+
- test/post.rb
|
164
|
+
- test/student.rb
|
165
|
+
- test/test_ministry_of_state.rb
|
166
|
+
- test/user.rb
|