openhood-simple_state_machine 1.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ ._*
2
+ .DS_Store
3
+ *.orig
4
+ Thumbs.db
5
+ *.sqlite3.db
6
+ *.log
7
+ coverage/*
8
+ log/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in simple_state_machine.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,53 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ openhood-simple_state_machine (2.0.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ activemodel (3.0.9)
10
+ activesupport (= 3.0.9)
11
+ builder (~> 2.1.2)
12
+ i18n (~> 0.5.0)
13
+ activerecord (3.0.9)
14
+ activemodel (= 3.0.9)
15
+ activesupport (= 3.0.9)
16
+ arel (~> 2.0.10)
17
+ tzinfo (~> 0.3.23)
18
+ activesupport (3.0.9)
19
+ arel (2.0.10)
20
+ bson (1.3.1)
21
+ builder (2.1.2)
22
+ diff-lcs (1.1.2)
23
+ i18n (0.5.0)
24
+ mongo (1.3.1)
25
+ bson (>= 1.3.1)
26
+ mongo_mapper (0.9.1)
27
+ activemodel (~> 3.0)
28
+ activesupport (~> 3.0)
29
+ plucky (~> 0.3.8)
30
+ plucky (0.3.8)
31
+ mongo (~> 1.3)
32
+ rake (0.8.7)
33
+ rspec (2.6.0)
34
+ rspec-core (~> 2.6.0)
35
+ rspec-expectations (~> 2.6.0)
36
+ rspec-mocks (~> 2.6.0)
37
+ rspec-core (2.6.4)
38
+ rspec-expectations (2.6.0)
39
+ diff-lcs (~> 1.1.2)
40
+ rspec-mocks (2.6.0)
41
+ sqlite3 (1.3.3)
42
+ tzinfo (0.3.28)
43
+
44
+ PLATFORMS
45
+ ruby
46
+
47
+ DEPENDENCIES
48
+ activerecord (>= 3.0.0)
49
+ mongo_mapper (~> 0.9.0)
50
+ openhood-simple_state_machine!
51
+ rake (~> 0.8.7)
52
+ rspec (~> 2.6.0)
53
+ sqlite3
data/MIT-LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009 Joseph HALTER
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,50 @@
1
+ h2. State Machine
2
+
3
+ Allow an Active Record or MongoMapper model to act as a finite state machine just as the popular plugin acts_as_state_machine but with many enhancements such as the possibility to have multiple states per object.
4
+
5
+ It can work together with the classic act_as_state_machine on the same project but both cannot be used at the same time for one given Active Record model. Moreover, simple_state_machine work standalone and doesn't require acts_as_state_machine.
6
+
7
+ You also get magic methods to check current state and a way to revert to previous state in database (or initial value for a new record) if something went wrong.
8
+
9
+ h3. Example for ActiveRecord
10
+
11
+ require "simple_state_machine/active_record"
12
+
13
+ class Chicken < ActiveRecord::Base
14
+ state_machine :user_state, [:pending, :active, :removed, :on_hold]
15
+ state_machine :validation_state, [:waiting, :reviewed, :validated, :invalid]
16
+ def user_activate!
17
+ return false if !user_state_pending?
18
+ self.user_state = :active
19
+ save! rescue user_state_revert
20
+ user_state_active?
21
+ end
22
+ end
23
+
24
+ And then you can do:
25
+
26
+ c = Chicken.create # c.user_state = :pending
27
+ c.user_activate! # c.user_state = :active
28
+
29
+ h3. Example for MongoMapper
30
+
31
+ require "simple_state_machine/mongo_mapper"
32
+
33
+ class Chicken
34
+ include MongoMapper::Document
35
+ plugin SimpleStateMachine::MongoMapper
36
+
37
+ state_machine :user_state, [:pending, :active, :removed, :on_hold]
38
+ state_machine :validation_state, [:waiting, :reviewed, :validated, :invalid]
39
+ def user_activate!
40
+ return false if !user_state_pending?
41
+ self.user_state = :active
42
+ save! rescue user_state_revert
43
+ user_state_active?
44
+ end
45
+ end
46
+
47
+ And then you can do:
48
+
49
+ c = Chicken.create # c.user_state = :pending
50
+ c.user_activate! # c.user_state = :active
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new('spec')
7
+
8
+ task :default => :spec
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require "simple_state_machine/active_record"
@@ -0,0 +1,61 @@
1
+ require "active_record"
2
+
3
+ module SimpleStateMachine
4
+ module ActiveRecord
5
+ extend ActiveSupport::Concern
6
+
7
+ included do |base|
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ def state_machine(column, states)
13
+ create_empty_state_machine unless inheritable_attributes.key? :states
14
+ inheritable_attributes[:states][column.to_sym] = states
15
+ validates_inclusion_of column, :in => states
16
+ # should also override getter/setter to convert to strings
17
+ self.class_eval <<-eos
18
+ def #{column.to_s}=(value)
19
+ self[:#{column.to_s}] = value.to_s
20
+ end
21
+ def #{column.to_s}
22
+ self[:#{column.to_s}].to_sym
23
+ end
24
+ def #{column.to_s}_revert
25
+ self[:#{column.to_s}] = self.new_record? ? states[:#{column.to_s}].first.to_s : self.#{column.to_s}_was
26
+ end
27
+ eos
28
+
29
+ # define a method {state_column}_{state}? for each state
30
+ states.each do |state|
31
+ self.class_eval <<-eos
32
+ def #{column.to_s}_#{state.to_s}?
33
+ self[:#{column.to_s}] === "#{state.to_s}"
34
+ end
35
+ eos
36
+ end
37
+
38
+ end
39
+
40
+ private
41
+
42
+ def create_empty_state_machine
43
+ write_inheritable_attribute :states, {} # add a class variable
44
+ class_inheritable_reader :states # make it read-only
45
+
46
+ after_initialize :set_initial_states
47
+ self.class_eval do
48
+ def set_initial_states
49
+ states.each {|column, states|
50
+ self[column] = states.first.to_s
51
+ } if new_record?
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ ActiveRecord::Base.class_eval do
60
+ include SimpleStateMachine::ActiveRecord
61
+ end
@@ -0,0 +1,55 @@
1
+ module SimpleStateMachine
2
+ module MongoMapper
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_eval do
7
+ write_inheritable_attribute :states, {}
8
+ class_inheritable_reader :states
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ def state_machine(column, column_states)
14
+ inheritable_attributes[:states][column.to_sym] = column_states
15
+
16
+ key column, String
17
+ validates_inclusion_of column, :in => column_states
18
+
19
+ define_method :"#{column}_revert" do
20
+ write_key column, new? ? states[column].first : send(:"#{column}_was")
21
+ end
22
+
23
+ # define a method {state_column}_{state}? for each state
24
+ column_states.each do |state|
25
+ define_method :"#{column}_#{state}?" do
26
+ send(column) === state
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ module InstanceMethods
33
+ def initialize(*args)
34
+ super(*args)
35
+ set_initial_state
36
+ end
37
+
38
+ private
39
+ def write_key(key, value)
40
+ super(key, states.keys.include?(key) ? value.to_s : value)
41
+ end
42
+
43
+ def read_key(key)
44
+ value = super(key)
45
+ value && states.keys.include?(key) ? value.to_sym : value
46
+ end
47
+
48
+ def set_initial_state
49
+ states.each do |column, states|
50
+ write_key column, states.first
51
+ end if new?
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+ module SimpleStateMachine
2
+ VERSION = "2.0.0"
3
+ end
@@ -1,63 +1,5 @@
1
1
  module SimpleStateMachine
2
-
3
- def self.included(base)
4
- base.extend Macro
5
- end
6
-
7
- module Macro
8
- def state_machine(column, states)
9
- create_empty_state_machine unless inheritable_attributes.key? :states
10
- inheritable_attributes[:states][column.to_sym] = states
11
- validates_inclusion_of column, :in => states
12
- # should also override getter/setter to convert to strings
13
- self.class_eval <<-eos
14
- def #{column.to_s}=(value)
15
- self[:#{column.to_s}] = value.to_s
16
- end
17
- def #{column.to_s}
18
- self[:#{column.to_s}].to_sym
19
- end
20
- def #{column.to_s}_revert
21
- self[:#{column.to_s}] = self.new_record? ? states[:#{column.to_s}].first.to_s : self.#{column.to_s}_was
22
- end
23
- eos
24
-
25
- # define a method {state_column}_{state}? for each state
26
- states.each do |state|
27
- self.class_eval <<-eos
28
- def #{column.to_s}_#{state.to_s}?
29
- self[:#{column.to_s}] === "#{state.to_s}"
30
- end
31
- eos
32
- end
33
-
34
- end
35
-
36
- private
37
-
38
- def create_empty_state_machine
39
- write_inheritable_attribute :states, {} # add a class variable
40
- class_inheritable_reader :states # make it read-only
41
-
42
- # set initial states on new objects
43
- if(!instance_methods.include?("after_initialize") && ActiveRecord::VERSION::MAJOR < 3)
44
- self.class_eval do
45
- def after_initialize # ActiveRecord::Base requires explicit definition of this function to use the callback
46
- end
47
- end
48
- end
49
- after_initialize :set_initial_states
50
- self.class_eval do
51
- def set_initial_states
52
- states.each {|column, states|
53
- self[column] = states.first.to_s
54
- } if new_record?
55
- end
56
- end
57
- end
58
- end
59
- end
60
-
61
- ActiveRecord::Base.class_eval do
62
- include SimpleStateMachine
2
+ autoload :ActiveRecord, "simple_state_machine/active_record"
3
+ autoload :MongoMapper, "simple_state_machine/mongo_mapper"
4
+ autoload :Version, "simple_state_machine/version"
63
5
  end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "simple_state_machine/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "openhood-simple_state_machine"
7
+ s.version = SimpleStateMachine::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Joseph HALTER", "Jonathan TRON"]
10
+ s.email = "team@openhood.com"
11
+ s.homepage = "http://github.com/openhood/simple_state_machine"
12
+ s.summary = %q{Same as acts_as_state_machine but on multiple columns and with more strict validation, allow creation of complex events with parameters, used successfully on critical financial applications for quite a long time}
13
+ s.description = s.summary
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_development_dependency("rake", ["~> 0.8.7"])
21
+ s.add_development_dependency("rspec", ["~> 2.6.0"])
22
+ s.add_development_dependency("sqlite3")
23
+ s.add_development_dependency("activerecord", [">= 3.0.0"])
24
+ s.add_development_dependency("mongo_mapper", ["~> 0.9.0"])
25
+ end
data/spec/db/schema.rb ADDED
@@ -0,0 +1,8 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :chickens, :force => true do |t|
3
+ t.string :name
4
+ t.integer :age
5
+ t.string :user_state
6
+ t.string :validation_state
7
+ end
8
+ end
@@ -0,0 +1,133 @@
1
+ require "spec_helper"
2
+
3
+ class Chicken < ActiveRecord::Base
4
+ state_machine :user_state, [:pending, :active, :removed, :on_hold]
5
+ state_machine :validation_state, [:waiting, :reviewed, :validated, :invalid]
6
+ def user_activate!
7
+ return false if !user_state_pending?
8
+ self.user_state = :active
9
+ save! rescue user_state_revert
10
+ user_state_active?
11
+ end
12
+ end
13
+
14
+ describe Chicken do
15
+
16
+ it "should set initial user_state" do
17
+ c = Chicken.new
18
+ c.user_state.should === :pending
19
+ end
20
+
21
+ it "should set initial validation_state" do
22
+ c = Chicken.new
23
+ c.validation_state.should === :waiting
24
+ end
25
+
26
+ it "should store state value as string" do
27
+ c = Chicken.new
28
+ c[:user_state].should === 'pending'
29
+ end
30
+
31
+ it "should also accept strings as state values" do
32
+ c = Chicken.new
33
+ c.user_state = 'active'
34
+ c.user_state.should === :active
35
+ end
36
+
37
+ it "should validate user_state stay inside possible states" do
38
+ c = Chicken.new
39
+ c.valid?.should === true
40
+ c.user_state = 'waiting'
41
+ c.valid?.should === false
42
+ end
43
+
44
+ it "should show all possible states in .states method" do
45
+ Chicken.states.should === {
46
+ :user_state => [:pending, :active, :removed, :on_hold],
47
+ :validation_state => [:waiting, :reviewed, :validated, :invalid]
48
+ }
49
+ end
50
+
51
+ it "should return be able to return to previous state" do
52
+ c = Chicken.new
53
+ c.user_state.should === :pending
54
+ c.user_state = 'active'
55
+ c.user_state.should === :active
56
+ c.user_state_revert
57
+ c.user_state.should === :pending
58
+ end
59
+
60
+ describe "custom event user_activate!" do
61
+
62
+ it "should return true if on pending state" do
63
+ c = Chicken.new
64
+ c.user_activate!.should === true
65
+ end
66
+
67
+ it "should effectively change user_state" do
68
+ c = Chicken.new
69
+ c.user_activate!
70
+ c.user_state.should === :active
71
+ end
72
+
73
+ it "should not affect validation_state" do
74
+ c = Chicken.new
75
+ c.user_activate!
76
+ c.validation_state.should === :waiting
77
+ end
78
+
79
+ it "should return false if user_state is not pending" do
80
+ c = Chicken.new
81
+ c.user_state = :on_hold
82
+ c.user_activate!.should === false
83
+ end
84
+
85
+ end
86
+
87
+ describe "magic methods to test current state" do
88
+
89
+ it "should handle the user_state_pending? method" do
90
+ c = Chicken.new
91
+ lambda {c.user_state_pending?}.should_not raise_error(NoMethodError)
92
+ end
93
+
94
+ it "should evaluate user_state_pending? to true initially" do
95
+ c = Chicken.new
96
+ c.user_state_pending?.should === true
97
+ end
98
+
99
+ it "should evaluate user_state_pending? to false after activation" do
100
+ c = Chicken.new
101
+ c.user_activate!
102
+ c.user_state_pending?.should === false
103
+ end
104
+
105
+ it "should evaluate user_state_active? to false initially" do
106
+ c = Chicken.new
107
+ c.user_state_active?.should === false
108
+ end
109
+
110
+ it "should evaluate user_state_active? to true after activation" do
111
+ c = Chicken.new
112
+ c.user_activate!
113
+ c.user_state_active?.should === true
114
+ end
115
+
116
+ it "should not catch user_state_invalid?" do
117
+ c = Chicken.new
118
+ lambda {c.user_state_invalid?}.should raise_error(NoMethodError)
119
+ end
120
+
121
+ it "should handle validation_state_waiting?" do
122
+ c = Chicken.new
123
+ lambda {c.validation_state_waiting?}.should_not raise_error(NoMethodError)
124
+ end
125
+
126
+ it "should evaluate validation_state_waiting? to true initially" do
127
+ c = Chicken.new
128
+ c.validation_state_waiting?.should === true
129
+ end
130
+
131
+ end
132
+
133
+ end
@@ -0,0 +1,119 @@
1
+ require "spec_helper"
2
+
3
+ describe SimpleStateMachine::MongoMapper do
4
+ before do
5
+ @doc = Doc("Order") do
6
+ plugin SimpleStateMachine::MongoMapper
7
+ end
8
+ end
9
+
10
+ describe ".state_machine" do
11
+ it "should be defined" do
12
+ @doc.should respond_to(:state_machine)
13
+ end
14
+ it "should take 2 arguments" do
15
+ lambda do
16
+ @doc.state_machine
17
+ end.should raise_error(ArgumentError)
18
+ lambda do
19
+ @doc.state_machine :state, [:initial]
20
+ end.should_not raise_error(ArgumentError)
21
+ lambda do
22
+ @doc.state_machine 1, 1, 1
23
+ end.should raise_error(ArgumentError)
24
+ end
25
+ end
26
+
27
+ describe "when defining a state machine with :state, [:initial, :final]" do
28
+ before do
29
+ @doc.class_eval do
30
+ state_machine :state, [:initial, :final]
31
+ end
32
+ end
33
+ subject { @doc.new }
34
+ it "should allow accessing states via states[:state]" do
35
+ subject.states[:state].should == [:initial, :final]
36
+ end
37
+ it "should add a :state key of type String on model" do
38
+ subject.key_names.should include("state")
39
+ subject.keys["state"].type.should == String
40
+ end
41
+ it "should set state on object initialization with first state" do
42
+ subject.state.should == :initial
43
+ end
44
+ it do
45
+ subject.should be_state_initial
46
+ end
47
+ it do
48
+ subject.should_not be_state_final
49
+ end
50
+ it "should allow reverting to previous state" do
51
+ subject.state = :final
52
+ subject.should be_state_final
53
+ subject.state_revert
54
+ subject.should be_state_initial
55
+ end
56
+ it "should allow setting state via a String" do
57
+ subject.state = "final"
58
+ subject.should be_state_final
59
+ end
60
+ it "should allow setting state via a Symbol" do
61
+ subject.state = :final
62
+ subject.should be_state_final
63
+ end
64
+ it "should return a symbol for state" do
65
+ subject.state = "final"
66
+ subject.state.should == :final
67
+ subject.state = :final
68
+ subject.state.should == :final
69
+ end
70
+ it "should validate state is included in allowed state" do
71
+ subject.state = :unknown_state
72
+ subject.valid?
73
+ subject.errors[:state].should == ["is not included in the list"]
74
+ subject.state = :final
75
+ subject.valid?
76
+ subject.errors[:state].should == []
77
+ end
78
+
79
+ describe "when defining a second state machine with :delivery_state, [:initial, :final]" do
80
+ before do
81
+ @doc.class_eval do
82
+ state_machine :delivery_state, [:initial, :final]
83
+ end
84
+ end
85
+ subject { @doc.new }
86
+ it "should allow accessing states via states[:delivery_state]" do
87
+ subject.states[:delivery_state].should == [:initial, :final]
88
+ end
89
+ it "should add a :delivery_state key of type String on model" do
90
+ subject.key_names.should include("delivery_state")
91
+ subject.keys["delivery_state"].type.should == String
92
+ end
93
+ it "should set delivery_state on object initialization with first delivery_state" do
94
+ subject.delivery_state.should == :initial
95
+ end
96
+ it do
97
+ subject.should be_delivery_state_initial
98
+ end
99
+ it do
100
+ subject.should_not be_delivery_state_final
101
+ end
102
+ it "should allow reverting to previous state" do
103
+ subject.delivery_state = :final
104
+ subject.should be_delivery_state_final
105
+ subject.delivery_state_revert
106
+ subject.should be_delivery_state_initial
107
+ end
108
+ it "should not revert other state machine on reverting to previous state" do
109
+ subject.state = :final
110
+ subject.delivery_state = :final
111
+ subject.should be_state_final
112
+ subject.should be_delivery_state_final
113
+ subject.delivery_state_revert
114
+ subject.should be_state_final
115
+ subject.should be_delivery_state_initial
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,26 @@
1
+ require "active_record"
2
+ require "mongo_mapper"
3
+ require "simple_state_machine"
4
+ require "simple_state_machine/active_record"
5
+ require "simple_state_machine/mongo_mapper"
6
+
7
+ Dir[File.expand_path("../support/*.rb", __FILE__)].each{|f| require f}
8
+
9
+ log_dir = File.expand_path('../../log', __FILE__)
10
+ FileUtils.mkdir_p(log_dir) unless File.exist?(log_dir)
11
+ logger = Logger.new(log_dir + '/test.log')
12
+
13
+ ActiveRecord::Base.logger = logger
14
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
15
+ load File.expand_path("../db/schema.rb", __FILE__)
16
+
17
+
18
+ MongoMapper.connection = Mongo::Connection.new('127.0.0.1', 27017, :logger => logger)
19
+ MongoMapper.database = "simple_state_machine_test"
20
+
21
+ RSpec.configure do |config|
22
+ config.before(:each) do
23
+ MongoMapper.database.collections.each { |c| c.drop_indexes }
24
+ end
25
+ config.include SimpleStateMachine::RSpec::Helpers
26
+ end
@@ -0,0 +1,268 @@
1
+ module SimpleStateMachine
2
+ module RSpec
3
+ module Helpers
4
+ def Doc(name=nil, &block)
5
+ klass = Class.new do
6
+ include ::MongoMapper::Document
7
+ set_collection_name "test#{rand(20)}"
8
+
9
+ if name
10
+ class_eval "def self.name; '#{name}' end"
11
+ class_eval "def self.to_s; '#{name}' end"
12
+ end
13
+ end
14
+
15
+ klass.class_eval(&block) if block_given?
16
+ klass.collection.remove
17
+ klass
18
+ end
19
+
20
+ def EDoc(name=nil, &block)
21
+ klass = Class.new do
22
+ include ::MongoMapper::EmbeddedDocument
23
+
24
+ if name
25
+ class_eval "def self.name; '#{name}' end"
26
+ class_eval "def self.to_s; '#{name}' end"
27
+ end
28
+ end
29
+
30
+ klass.class_eval(&block) if block_given?
31
+ klass
32
+ end
33
+
34
+ ::RSpec::Matchers.define :have_timestamps do |name, states = []|
35
+ description { "have keys created_at/updated_at of type Time" }
36
+ match do |actual|
37
+ keys = actual.keys.select{|key_name, key| ["created_at", "updated_at"].include?(key_name) && key.type==Time}
38
+ keys.size == 2
39
+ end
40
+ end
41
+
42
+ ::RSpec::Matchers.define :have_simple_state_machine do |name, states = []|
43
+ description { "define a state machine \"#{name}\" with #{states.join(",")} states" }
44
+ match do |actual|
45
+ actual.states[name].eql?(states)
46
+ end
47
+ end
48
+
49
+ ::RSpec::Matchers.define :have_scope do |name, options = {}|
50
+ query = Plucky::Query.new(actual.class, options)
51
+ description do
52
+ s = "have a scope #{name} with"
53
+ s << " #{query.criteria.source} criterias" if query.criteria.source.present?
54
+ s << " and " if query.criteria.source.present? && query.options.source.present?
55
+ s << " #{query.options.source.inspect} options" if query.options.source.present?
56
+ s
57
+ end
58
+ match do |actual|
59
+ actual.class.scopes[name] &&
60
+ actual.class.scopes[name].call.criteria == query.criteria &&
61
+ actual.class.scopes[name].call.options == query.options
62
+ end
63
+ end
64
+
65
+ ::RSpec::Matchers.define :have_key do |name, klass, options = {}|
66
+ description do
67
+ s = "have key #{name} of class #{klass.name}"
68
+ s << " with #{options[:default]} as default value" if options.has_key?(:default)
69
+ s
70
+ end
71
+ match do |actual|
72
+ key = actual.keys.detect{|key_name, key| key_name==name.to_s && key.type==klass}
73
+ valid = !!key
74
+ valid &&= key.last.default_value.eql?(options[:default]) if options.has_key?(:default)
75
+ valid
76
+ end
77
+ end
78
+
79
+ ::RSpec::Matchers.define :have_index do |*keys|
80
+ description{ "have an index on #{keys.join(", ")}" }
81
+ match do |actual|
82
+ indexes = actual.collection.index_information.values.collect{|h| h["key"].keys}
83
+ indexes.include? keys.collect(&:to_s)
84
+ end
85
+ end
86
+
87
+ ::RSpec::Matchers.define :have_many do |name, options = {}|
88
+ description do
89
+ s = "have many "
90
+ s << "embedded " if options[:embedded]
91
+ s << "#{name}"
92
+ s << " of class #{options[:class].name}" if options[:class]
93
+ s << " ordered by '#{options[:order]}'" if options[:order]
94
+ s
95
+ end
96
+ match do |actual, matcher|
97
+ actual.associations.one? do |association_name, assocation|
98
+ valid = association_name==name.to_s && assocation.type==:many
99
+ valid &&= assocation.klass==options[:class] if options[:class]
100
+ valid &&= !!options[:embedded]==actual.embedded_associations.include?(assocation)
101
+ valid &&= !!options[:order]==assocation.query_options[:order] if options[:order]
102
+ valid
103
+ end
104
+ end
105
+ end
106
+
107
+ ::RSpec::Matchers.define :have_one do |name, options = {}|
108
+ description do
109
+ s = "have one "
110
+ s << "embedded " if options[:embedded]
111
+ s << "#{name}"
112
+ s << " of class #{options[:class].name}" if options[:class]
113
+ s
114
+ end
115
+ match do |actual, matcher|
116
+ actual.associations.one? do |association_name, assocation|
117
+ valid = association_name==name.to_s && assocation.type==:one
118
+ valid &&= assocation.klass==options[:class] if options[:class]
119
+ valid &&= !!options[:embedded]==actual.embedded_associations.include?(assocation)
120
+ valid
121
+ end
122
+ end
123
+ end
124
+
125
+ ::RSpec::Matchers.define :belong_to do |name, options = {}|
126
+ description do
127
+ s = "belong to #{name}"
128
+ s << " of class #{options[:class].name}" if options[:class]
129
+ s
130
+ end
131
+ match do |actual, matcher|
132
+ actual.associations.one? do |association_name, assocation|
133
+ valid = association_name==name.to_s && assocation.type==:belongs_to
134
+ valid &&= assocation.klass==options[:class] if options[:class]
135
+ valid
136
+ end
137
+ end
138
+ end
139
+
140
+ ::RSpec::Matchers.define :validate_presence_of do |field, options = {}|
141
+ description do
142
+ s = "validate presence of #{field}"
143
+ s << " while still allowing nil" if options[:allow_nil]
144
+ s << " for #{options[:groups]} validations" if options[:groups]
145
+ s
146
+ end
147
+ match do |actual, matcher|
148
+ validation_method = options[:groups] ? :"valid_for_#{options[:groups]}?" : :valid?
149
+ actual.send "#{field}=", ""
150
+ actual.send validation_method
151
+ valid = !actual.errors[field].empty?
152
+ if actual.keys[field] && actual.keys[field].default_value.nil?
153
+ actual.send "#{field}=", nil
154
+ actual.send validation_method
155
+ valid && !!options[:allow_nil]==actual.errors[field].empty?
156
+ end
157
+ valid
158
+ end
159
+ end
160
+
161
+ ::RSpec::Matchers.define :validate_length_of do |field, options = {}|
162
+ min, max = options[:within].first, options[:within].last
163
+ description do
164
+ "validate length of #{field} within #{min}..#{max}"
165
+ end
166
+ match do |actual, matcher|
167
+ actual.send "#{field}=", min.times.collect{"a"}.join
168
+ actual.valid?
169
+ valid = !actual.errors[field].include?("is invalid")
170
+ actual.send "#{field}=", (min-1).times.collect{"a"}.join
171
+ actual.valid?
172
+ valid &&= actual.errors[field].include?("is invalid")
173
+ actual.send "#{field}=", (max+1).times.collect{"a"}.join
174
+ actual.valid?
175
+ valid &&= actual.errors[field].include?("is invalid")
176
+ valid
177
+ end
178
+ end
179
+
180
+ ::RSpec::Matchers.define :validate_inclusion_of do |field, options = {}|
181
+ min, max = options[:within].first, options[:within].last
182
+ description do
183
+ "validate inclusion of #{field} within #{min}..#{max}"
184
+ end
185
+ match do |actual, matcher|
186
+ actual.send "#{field}=", min
187
+ actual.valid?
188
+ valid = !actual.errors[field].include?("is not in the list")
189
+ actual.send "#{field}=", min.pred
190
+ actual.valid?
191
+ valid &&= actual.errors[field].include?("is not in the list")
192
+ actual.send "#{field}=", max.succ
193
+ actual.valid?
194
+ valid && actual.errors[field].include?("is not in the list")
195
+ end
196
+ end
197
+
198
+ ::RSpec::Matchers.define :validate_confirmation_of do |field|
199
+ description { "validate confirmation of #{field}" }
200
+ match do |actual, matcher|
201
+ field_confirmation = "#{field}_confirmation"
202
+ actual.send "#{field}=", "aaaa"
203
+ actual.send "#{field_confirmation}=", "aaaa"
204
+ actual.valid?
205
+ valid = !actual.errors[field].include?("doesn't match confirmation")
206
+ actual.send "#{field}=", "aaaa"
207
+ actual.send "#{field}=", "bbbb"
208
+ actual.valid?
209
+ valid && actual.errors[field].include?("doesn't match confirmation")
210
+ end
211
+ end
212
+
213
+ ::RSpec::Matchers.define :validate_uniqueness_of do |field, options = {}|
214
+ description do
215
+ s = "validate uniqueness of #{field}"
216
+ s << " case sensitive" if options[:case_sensitive]
217
+ s << " case insensitive" unless options[:case_sensitive]
218
+ s << " allowing blank value" if options[:allow_blank]
219
+ s << " allowing nil value" if options[:allow_nil]
220
+ s
221
+ end
222
+ match do |actual, matcher|
223
+ existing = actual.class.first
224
+ raise "you need to create at least one record before we can check for uniqueness" unless existing
225
+ actual.attributes = existing.attributes.reject{|k,v| k == "_id" }
226
+ actual.valid?
227
+ valid = actual.errors[field].include?("has already been taken")
228
+ actual.send "#{field}=", actual.send(field).swapcase
229
+ actual.valid?
230
+ if options[:case_sensitive]
231
+ valid &&= !actual.errors[field].include?("has already been taken")
232
+ else
233
+ valid &&= actual.errors[field].include?("has already been taken")
234
+ end
235
+ if options[:allow_blank]
236
+ actual.send "#{field}=", ""
237
+ actual.valid?
238
+ valid &&= !actual.errors[field].include?("has already been taken")
239
+ end
240
+ if options[:allow_nil]
241
+ actual.send "#{field}=", nil
242
+ actual.valid?
243
+ valid &&= !actual.errors[field].include?("has already been taken")
244
+ end
245
+ valid
246
+ end
247
+ end
248
+
249
+ ::RSpec::Matchers.define :validate_config_with do |key, klass|
250
+ description{ "validate presence of #{key} of class #{klass} in config" }
251
+ match do |actual, matcher|
252
+ actual.config.delete key
253
+ actual.valid?
254
+ valid = actual.errors[:config].include? "#{key} is required"
255
+ actual.config[key] = Class.new
256
+ actual.valid?
257
+ valid &&= actual.errors[:config].include? "#{key} should be #{klass}"
258
+ value = mock
259
+ value.stub(:kind_of?).with(klass).and_return true
260
+ actual.config[key] = value
261
+ actual.valid?
262
+ valid &&= actual.errors[:config].none?{|error| error.starts_with?("#{key} ")}
263
+ valid
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
metadata CHANGED
@@ -1,75 +1,128 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: openhood-simple_state_machine
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 1
7
- - 0
8
- - 1
9
- version: 1.0.1
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
12
- - Joseph Halter
7
+ authors:
8
+ - Joseph HALTER
9
+ - Jonathan TRON
13
10
  autorequire:
14
11
  bindir: bin
15
12
  cert_chain: []
16
-
17
- date: 2010-11-24 00:00:00 +01:00
13
+ date: 2011-06-22 00:00:00.000000000 +02:00
18
14
  default_executable:
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rake
18
+ requirement: &2161201240 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 0.8.7
24
+ type: :development
25
+ prerelease: false
26
+ version_requirements: *2161201240
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: &2161198760 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: 2.6.0
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: *2161198760
38
+ - !ruby/object:Gem::Dependency
39
+ name: sqlite3
40
+ requirement: &2161197360 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ type: :development
47
+ prerelease: false
48
+ version_requirements: *2161197360
49
+ - !ruby/object:Gem::Dependency
21
50
  name: activerecord
51
+ requirement: &2161195840 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: 3.0.0
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: *2161195840
60
+ - !ruby/object:Gem::Dependency
61
+ name: mongo_mapper
62
+ requirement: &2161188280 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ~>
66
+ - !ruby/object:Gem::Version
67
+ version: 0.9.0
68
+ type: :development
22
69
  prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
24
- requirements:
25
- - - ">="
26
- - !ruby/object:Gem::Version
27
- segments:
28
- - 2
29
- - 2
30
- - 2
31
- version: 2.2.2
32
- type: :runtime
33
- version_requirements: *id001
34
- description: Same as acts_as_state_machine but on multiple columns and with more strict validation, allow creation of complex events with parameters, used successfully on critical financial applications for quite a long time
70
+ version_requirements: *2161188280
71
+ description: Same as acts_as_state_machine but on multiple columns and with more strict
72
+ validation, allow creation of complex events with parameters, used successfully
73
+ on critical financial applications for quite a long time
35
74
  email: team@openhood.com
36
75
  executables: []
37
-
38
76
  extensions: []
39
-
40
77
  extra_rdoc_files: []
41
-
42
- files:
78
+ files:
79
+ - .gitignore
80
+ - Gemfile
81
+ - Gemfile.lock
82
+ - MIT-LICENSE
83
+ - README.textile
84
+ - Rakefile
85
+ - init.rb
43
86
  - lib/simple_state_machine.rb
87
+ - lib/simple_state_machine/active_record.rb
88
+ - lib/simple_state_machine/mongo_mapper.rb
89
+ - lib/simple_state_machine/version.rb
90
+ - simple_state_machine.gemspec
91
+ - spec/db/schema.rb
92
+ - spec/simple_state_machine/active_record_spec.rb
93
+ - spec/simple_state_machine/mongo_mapper_spec.rb
94
+ - spec/spec_helper.rb
95
+ - spec/support/mongo_mapper_helper.rb
44
96
  has_rdoc: true
45
97
  homepage: http://github.com/openhood/simple_state_machine
46
98
  licenses: []
47
-
48
99
  post_install_message:
49
100
  rdoc_options: []
50
-
51
- require_paths:
101
+ require_paths:
52
102
  - lib
53
- required_ruby_version: !ruby/object:Gem::Requirement
54
- requirements:
55
- - - ">="
56
- - !ruby/object:Gem::Version
57
- segments:
58
- - 0
59
- version: "0"
60
- required_rubygems_version: !ruby/object:Gem::Requirement
61
- requirements:
62
- - - ">="
63
- - !ruby/object:Gem::Version
64
- segments:
65
- - 0
66
- version: "0"
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ! '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
67
115
  requirements: []
68
-
69
116
  rubyforge_project:
70
- rubygems_version: 1.3.6
117
+ rubygems_version: 1.6.2
71
118
  signing_key:
72
119
  specification_version: 3
73
- summary: Same as acts_as_state_machine but on multiple columns and with more strict validation, allow creation of complex events with parameters, used successfully on critical financial applications for quite a long time
74
- test_files: []
75
-
120
+ summary: Same as acts_as_state_machine but on multiple columns and with more strict
121
+ validation, allow creation of complex events with parameters, used successfully
122
+ on critical financial applications for quite a long time
123
+ test_files:
124
+ - spec/db/schema.rb
125
+ - spec/simple_state_machine/active_record_spec.rb
126
+ - spec/simple_state_machine/mongo_mapper_spec.rb
127
+ - spec/spec_helper.rb
128
+ - spec/support/mongo_mapper_helper.rb