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 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
@@ -0,0 +1,2 @@
1
+ require 'state_attr'
2
+ ActiveRecord::Base.send( :include, StateAttr )
@@ -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
@@ -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
+