state_attr 0.1.9
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/README.md +40 -0
- data/Rakefile +24 -0
- data/lib/state_attr/state.rb +72 -0
- data/lib/state_attr.rb +36 -0
- data/rails/init.rb +2 -0
- data/state_attr.gemspec +22 -0
- data/test/test_helper.rb +46 -0
- data/test/unit/state_attr_test.rb +79 -0
- metadata +74 -0
data/README.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# About
|
2
|
+
|
3
|
+
StateAttr is an minimalistic state machine approach for rails allowing multiple state attributes at the same time.
|
4
|
+
|
5
|
+
# Installation
|
6
|
+
|
7
|
+
gem install state_attr
|
8
|
+
|
9
|
+
# Examples
|
10
|
+
|
11
|
+
state_attr :state, {
|
12
|
+
nil => :first,
|
13
|
+
:first => [:second, :third],
|
14
|
+
:second => :last,
|
15
|
+
:third => nil,
|
16
|
+
}
|
17
|
+
|
18
|
+
state_attr :invitation_state, {
|
19
|
+
:invited => %w{approved rejected},
|
20
|
+
:approved => :rejected,
|
21
|
+
:rejected => :approved,
|
22
|
+
}, :initial => :invited
|
23
|
+
|
24
|
+
state_attr :progress, {
|
25
|
+
nil => :one,
|
26
|
+
:one => :two,
|
27
|
+
:two => :three
|
28
|
+
} do |old, new|
|
29
|
+
if new == :three
|
30
|
+
send_mail "You just finished."
|
31
|
+
else
|
32
|
+
send_mail "You just made to #{new}."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Options
|
37
|
+
|
38
|
+
- :initial => :value - initial `value` (not yet implemented)
|
39
|
+
- :setter => :exception - raise exception when calling `state=`
|
40
|
+
- :switch_not_allowed => :silent - do not raise exception when switch is not allowed
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the session_management plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Generate documentation for the session_management plugin.'
|
17
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
18
|
+
rdoc.rdoc_dir = 'rdoc'
|
19
|
+
rdoc.title = 'SessionManagement'
|
20
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
21
|
+
rdoc.rdoc_files.include('README.md')
|
22
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module StateAttr
|
2
|
+
module ClassMethods
|
3
|
+
class State
|
4
|
+
|
5
|
+
def initialize(parent, field, machine, logger, options)
|
6
|
+
@parent = parent
|
7
|
+
@field = field
|
8
|
+
#convert different types of input to array of symbols
|
9
|
+
@machine = {}
|
10
|
+
machine.each { |key, value| @machine[key] = value.nil? ? [nil] : Array(value).map(&:to_sym) }
|
11
|
+
@logger = logger
|
12
|
+
@options = options
|
13
|
+
@callback = "on_#{@field}_change".to_sym
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
read_state.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
# current state value
|
21
|
+
def value
|
22
|
+
read_state
|
23
|
+
end
|
24
|
+
alias_method :current, :value
|
25
|
+
|
26
|
+
# validates if state is one of the given states
|
27
|
+
def is?(*symbols)
|
28
|
+
!symbols.flatten.select { |state| read_state == state }.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
# array of allowed stated
|
32
|
+
def allowed
|
33
|
+
@machine[read_state] || []
|
34
|
+
end
|
35
|
+
|
36
|
+
#validate if can switch to given state
|
37
|
+
def allowed?(state)
|
38
|
+
allowed.include?(state) || is?(state)
|
39
|
+
end
|
40
|
+
|
41
|
+
# if allowed switch to given state, if not raise exception
|
42
|
+
def switch(state)
|
43
|
+
if (allowed?(state))
|
44
|
+
write_state(state)
|
45
|
+
elsif @options[:switch_not_allowed] == :silent
|
46
|
+
@logger.error "#{@parent.class.name} changing #{@field} from '#{read_state}' to '#{state}' is not allowed, allowed states are '#{allowed*'\', \''}'"
|
47
|
+
false
|
48
|
+
else
|
49
|
+
raise "#{@parent.class.name} error, changing #{@field} from '#{read_state}' to '#{state}' is not allowed, allowed states are '#{allowed*'\', \''}'"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def read_state
|
56
|
+
@parent.read_attribute(@field).try(:to_sym)
|
57
|
+
end
|
58
|
+
|
59
|
+
def write_state(state)
|
60
|
+
if @parent.methods.include? @callback.to_s
|
61
|
+
unless @parent.send @callback, read_state, state
|
62
|
+
@logger.warn "#{@parent.class.name}: change #{@field} from '#{read_state}' to '#{state}' was rejected by callback"
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
@logger.debug "#{@parent.class.name}: changing #{@field} from '#{read_state}' to '#{state}'"
|
67
|
+
@parent.write_attribute(@field, state.try(:to_s))
|
68
|
+
true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/state_attr.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'state_attr/state'
|
2
|
+
module StateAttr
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
def state_attr(attr, machine, options={}, &block)
|
6
|
+
#attr, initial = attr
|
7
|
+
self.send :define_method, attr do
|
8
|
+
@state_handlers ||= {}
|
9
|
+
@state_handlers[attr] ||= State.new(self, attr, machine, logger, options)
|
10
|
+
@state_handlers[attr]
|
11
|
+
end
|
12
|
+
|
13
|
+
if options[:setter] == :exception
|
14
|
+
self.send :define_method, "#{attr}=".to_sym do |state|
|
15
|
+
raise "#{self.class.name} error, manual setting of state is not allowed (new value '#{state}')"
|
16
|
+
end
|
17
|
+
else
|
18
|
+
self.send :define_method, "#{attr}=".to_sym do |state|
|
19
|
+
@state_handlers ||= {}
|
20
|
+
@state_handlers[attr] ||= State.new(self, attr, machine, logger, options)
|
21
|
+
@state_handlers[attr].switch(state)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
if block_given?
|
26
|
+
self.send :define_method, "on_#{attr}_change".to_sym do |*args|
|
27
|
+
block.call *args
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.included(base)
|
34
|
+
base.extend ClassMethods
|
35
|
+
end
|
36
|
+
end
|
data/rails/init.rb
ADDED
data/state_attr.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "state_attr"
|
3
|
+
s.version = "0.1.9"
|
4
|
+
s.date = "2011-05-16"
|
5
|
+
s.summary = "Minimalistic state machine approach allowing multiple state attributes at the same time."
|
6
|
+
s.email = "mpapis@gmail.com"
|
7
|
+
s.homepage = "http://github.com/mpapis/state_attr/tree/master"
|
8
|
+
s.description = "Minimalistic state machine."
|
9
|
+
s.has_rdoc = false
|
10
|
+
s.authors = ["Michal Papis"]
|
11
|
+
s.files = [
|
12
|
+
"lib/state_attr.rb",
|
13
|
+
"lib/state_attr/state.rb",
|
14
|
+
"rails/init.rb",
|
15
|
+
"Rakefile",
|
16
|
+
"README.md",
|
17
|
+
"state_attr.gemspec",
|
18
|
+
"test/test_helper.rb",
|
19
|
+
"test/unit/state_attr_test.rb",
|
20
|
+
]
|
21
|
+
#s.add_dependency("rails",["~>2","~>3"])
|
22
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'state_attr'
|
3
|
+
|
4
|
+
# fake rails
|
5
|
+
class << nil
|
6
|
+
def try *args
|
7
|
+
nil
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Object
|
12
|
+
def try *args
|
13
|
+
send *args
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Logger
|
18
|
+
def info msg
|
19
|
+
end
|
20
|
+
def warn msg
|
21
|
+
end
|
22
|
+
def error msg
|
23
|
+
end
|
24
|
+
def debug msg
|
25
|
+
end
|
26
|
+
end
|
27
|
+
RAILS_DEFAULT_LOGGER = Logger.new
|
28
|
+
|
29
|
+
module ActiveRecord
|
30
|
+
class Base
|
31
|
+
def write_attribute(name, value)
|
32
|
+
@attributes ||= {}
|
33
|
+
@attributes[name] = value
|
34
|
+
end
|
35
|
+
def read_attribute(name)
|
36
|
+
attributes[name]
|
37
|
+
end
|
38
|
+
def attributes
|
39
|
+
@attributes || {}
|
40
|
+
end
|
41
|
+
def logger
|
42
|
+
RAILS_DEFAULT_LOGGER
|
43
|
+
end
|
44
|
+
include StateAttr
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class FakeModel < ActiveRecord::Base
|
4
|
+
state_attr :state1, {
|
5
|
+
nil => :a,
|
6
|
+
:a => :b,
|
7
|
+
}, :setter => :exception
|
8
|
+
|
9
|
+
state_attr :state2, {
|
10
|
+
nil => [:d,:f],
|
11
|
+
:d => [:e,:f],
|
12
|
+
:f => [:d,:e],
|
13
|
+
:e => nil,
|
14
|
+
}, :setter => :exception
|
15
|
+
|
16
|
+
state_attr :state3, {
|
17
|
+
nil => :g,
|
18
|
+
:g => :h,
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
class StateAttrTest < Test::Unit::TestCase
|
23
|
+
def test_single_set_read
|
24
|
+
model = FakeModel.new
|
25
|
+
assert_equal nil, model.state1.value
|
26
|
+
assert_equal "", model.state1.to_s
|
27
|
+
model.state1.switch(:a)
|
28
|
+
assert_equal :a, model.state1.value
|
29
|
+
assert_equal "a", "#{model.state1}"
|
30
|
+
end
|
31
|
+
def test_single_set_fail
|
32
|
+
model = FakeModel.new
|
33
|
+
assert_raises RuntimeError do
|
34
|
+
model.state1.switch(:c)
|
35
|
+
end
|
36
|
+
assert_equal nil, model.state1.value
|
37
|
+
end
|
38
|
+
def test_single_assignment_fail
|
39
|
+
model = FakeModel.new
|
40
|
+
assert_raises RuntimeError do
|
41
|
+
model.state1=:a
|
42
|
+
end
|
43
|
+
assert_equal nil, model.state1.value
|
44
|
+
end
|
45
|
+
def test_single_assignment_success
|
46
|
+
model = FakeModel.new
|
47
|
+
model.state3=:g
|
48
|
+
assert_equal :g, model.state3.value
|
49
|
+
end
|
50
|
+
def test_single_set_nil
|
51
|
+
model = FakeModel.new
|
52
|
+
model.state2.switch(:d)
|
53
|
+
model.state2.switch(:e)
|
54
|
+
assert_equal :e, model.state2.value
|
55
|
+
model.state2.switch(nil)
|
56
|
+
assert_equal nil, model.state2.value
|
57
|
+
end
|
58
|
+
def test_double_set
|
59
|
+
model = FakeModel.new
|
60
|
+
model.state1.switch(:a)
|
61
|
+
model.state2.switch(:d)
|
62
|
+
assert_equal :a, model.state1.value
|
63
|
+
assert_equal :d, model.state2.value
|
64
|
+
end
|
65
|
+
def test_single_is?
|
66
|
+
model = FakeModel.new
|
67
|
+
model.state2.switch(:d)
|
68
|
+
assert model.state2.is?(:d)
|
69
|
+
assert !model.state2.is?(:e)
|
70
|
+
assert model.state2.is?(:d,:e)
|
71
|
+
end
|
72
|
+
def test_single_allowed?
|
73
|
+
model = FakeModel.new
|
74
|
+
model.state2.switch(:d)
|
75
|
+
assert !model.state2.allowed?(nil)
|
76
|
+
assert model.state2.allowed?(:d)
|
77
|
+
assert model.state2.allowed?(:e)
|
78
|
+
end
|
79
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: state_attr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 9
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 9
|
10
|
+
version: 0.1.9
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Michal Papis
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-05-16 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Minimalistic state machine.
|
23
|
+
email: mpapis@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- lib/state_attr.rb
|
32
|
+
- lib/state_attr/state.rb
|
33
|
+
- rails/init.rb
|
34
|
+
- Rakefile
|
35
|
+
- README.md
|
36
|
+
- state_attr.gemspec
|
37
|
+
- test/test_helper.rb
|
38
|
+
- test/unit/state_attr_test.rb
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://github.com/mpapis/state_attr/tree/master
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
hash: 3
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 3
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.5.2
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: Minimalistic state machine approach allowing multiple state attributes at the same time.
|
73
|
+
test_files: []
|
74
|
+
|