ministry_of_state 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'rails', "3.0.3"
4
+ gem 'mysql2'
5
+
6
+ group :development do
7
+ gem "shoulda", ">= 0"
8
+ gem "bundler", "~> 1.0.0"
9
+ gem "jeweler", "~> 1.5.2"
10
+ end
@@ -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
@@ -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.
@@ -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
+
@@ -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,4 @@
1
+ module MinistryOfState
2
+ VERSION = "0.0.1"
3
+ require "ministry_of_state/railtie"
4
+ end
@@ -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
+
@@ -0,0 +1,4 @@
1
+ class Article < ActiveRecord::Base
2
+ include MinistryOfState
3
+ ministry_of_state(:initial_state => 'pending', :state_column => 'status' )
4
+ end
@@ -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
@@ -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
+
@@ -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
@@ -0,0 +1,11 @@
1
+ class Student < User
2
+ add_state :foo
3
+
4
+ add_event(:activate) do
5
+ transitions(:from => [:foo,:pending],:to => :active)
6
+ end
7
+
8
+ add_event(:make_foo) do
9
+ transitions(:from => [:pending,:active], :to => :foo)
10
+ end
11
+ end
@@ -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
@@ -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