openhood-simple_state_machine 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.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