state_objects 0.0.2
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 +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +136 -0
- data/Rakefile +1 -0
- data/lib/state_objects/base.rb +28 -0
- data/lib/state_objects/model_additions.rb +93 -0
- data/lib/state_objects/version.rb +3 -0
- data/lib/state_objects.rb +3 -0
- data/state_objects.gemspec +25 -0
- metadata +103 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Mark Windholtz
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
# StateObjects
|
2
|
+
|
3
|
+
This is a State Machine implementation based on the http://en.wikipedia.org/wiki/State_pattern
|
4
|
+
From the classic book http://en.wikipedia.org/wiki/Design_Patterns
|
5
|
+
Each state is a separate class.
|
6
|
+
You can add methods to the state classes, but each state class must have implement the same list of methods.
|
7
|
+
State transitions are the responsibility of the state classes.
|
8
|
+
|
9
|
+
Since this was extracted from an application, the specs are currently still in the main application. I will move the specs to this gem as soon as possible.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'state_objects'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install state_objects
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
class WalkLights < ActiveRecord::Migration
|
28
|
+
def self.change
|
29
|
+
create_table :walk_lights do |t|
|
30
|
+
t.string :color_state, :default => LightRedState.db_value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class LightRedState < StateObjects::Base
|
35
|
+
state_object_values :red, 'R', "Dont Walk"
|
36
|
+
def change
|
37
|
+
model.color_state_green!
|
38
|
+
model.save!
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class LightGreenState < StateObjects::Base
|
43
|
+
state_object_values :green, 'G', 'Walk'
|
44
|
+
def change
|
45
|
+
model.color_state_red!
|
46
|
+
model.save!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class WalkLight < ActiveRecord::Base
|
51
|
+
extend StateObjects::ModelAdditions
|
52
|
+
state_objects :color_state,
|
53
|
+
LightGreenState,
|
54
|
+
LightRedState
|
55
|
+
state_object_events :change
|
56
|
+
|
57
|
+
scope :red, where("color_state = #{LightRedState.db_value}" )
|
58
|
+
scope :green, where("color_state = #{LightGreenState.db_value}" )
|
59
|
+
end
|
60
|
+
|
61
|
+
# now lets use it
|
62
|
+
north_south_elm_300_block = WalkLight.create(color_state: LightRedState.db_value)
|
63
|
+
while (true)
|
64
|
+
pause rand(200) # keep the pedestrians guessing :-)
|
65
|
+
north_south_elm_300_block.change
|
66
|
+
end
|
67
|
+
|
68
|
+
### adds the following CLASS METHODS to WalkLight
|
69
|
+
|
70
|
+
TODO: there may be typos in the following. It should be cleared up when I move the specs into the gem.
|
71
|
+
|
72
|
+
* color_states
|
73
|
+
returns a array of 2-value arrays suitable to fill a select tag
|
74
|
+
The second example shows how to start the selection on a blank
|
75
|
+
|
76
|
+
<%= select :walk_light, :color_state, WalkLight.color_states %>
|
77
|
+
<%= select :walk_light, :color_state, [['','']] + WalkLight.color_states %>
|
78
|
+
|
79
|
+
assert_equal ['Walk', 'Dont Walk'], WalkLight.color_state_hash.values
|
80
|
+
assert_equal "['Walk', 'Dont Walk']", WalkLight.color_state_js_list
|
81
|
+
|
82
|
+
color_state_symbols # returns hash of symbols
|
83
|
+
|
84
|
+
adds the following INSTANCE METHODS to WalkLight
|
85
|
+
|
86
|
+
color_state_hash
|
87
|
+
color_state # returns the single character value as in the db
|
88
|
+
color_state_label # returns the current values label
|
89
|
+
color_state_symbol # returns the current values symbol
|
90
|
+
|
91
|
+
* methods ending in '?' return boolean if the value is set
|
92
|
+
* methods ending in '!' set the value ( this does NOT save the model)
|
93
|
+
|
94
|
+
color_state_red?
|
95
|
+
color_state_red!
|
96
|
+
|
97
|
+
color_state_green?
|
98
|
+
color_state_green!
|
99
|
+
|
100
|
+
### example #1: Selection list
|
101
|
+
|
102
|
+
walk_light = WalkLight.build(color_state: LightGreenState.db_value)
|
103
|
+
walk_light.color_state_red!
|
104
|
+
assert_equal 'R', walk_light.color_state
|
105
|
+
assert_equal :red, walk_light.color_state_symbol
|
106
|
+
assert_equal true, walk_light.color_state_red?
|
107
|
+
assert_equal "Dont Walk", walk_light.color_state_label
|
108
|
+
|
109
|
+
assert_equal [["Walk", "G"], ["Dont Walk", "R"]], WalkLight.color_states
|
110
|
+
assert_equal({"G"=>"Walk", "R"=>"Dont Walk",}, WalkLight.color_state_hash)
|
111
|
+
assert_equals({'G'=>:green, 'R'=>:red }, WalkLight.color_state_symbols)
|
112
|
+
|
113
|
+
### Example #2: Selection list
|
114
|
+
|
115
|
+
<%= select :walk_light, :color_state, WalkLight.color_states %>
|
116
|
+
|
117
|
+
|
118
|
+
### Example #3: Radio button labels
|
119
|
+
|
120
|
+
<% WalkLight.color_state_hash.each do | key, value | %>
|
121
|
+
<%= radio_button :walk_light, :color_state, key %> <%= value %><br />
|
122
|
+
<% end %>
|
123
|
+
|
124
|
+
|
125
|
+
### Example #4 in a java_script list
|
126
|
+
|
127
|
+
color_state_js_list
|
128
|
+
|
129
|
+
|
130
|
+
## Contributing
|
131
|
+
|
132
|
+
1. Fork it
|
133
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
134
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
135
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
136
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module StateObjects
|
2
|
+
class Base
|
3
|
+
|
4
|
+
def initialize(model)
|
5
|
+
@model = model
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.state_object_values(*opts) # :nodoc:
|
9
|
+
class_eval <<-EOF
|
10
|
+
def self.symbol
|
11
|
+
'#{opts[0]}'.to_sym
|
12
|
+
end
|
13
|
+
def self.db_value
|
14
|
+
'#{opts[1]}'
|
15
|
+
end
|
16
|
+
def self.label
|
17
|
+
'#{opts[2]}'
|
18
|
+
end
|
19
|
+
EOF
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
def model
|
24
|
+
@model
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module StateObjects
|
2
|
+
module ModelAdditions
|
3
|
+
|
4
|
+
def state_object_events(id,*methods) # :nodoc:
|
5
|
+
unless self.respond_to?("#{id}_klasses")
|
6
|
+
raise "Invalid call sequence. #state_objects must be defined before #state_object_events"
|
7
|
+
end
|
8
|
+
|
9
|
+
# check methods on State classes
|
10
|
+
self.send("#{id}_states").each do |klass|
|
11
|
+
methods.each do |method|
|
12
|
+
unless klass.new(nil).respond_to?(method)
|
13
|
+
raise "Invalid state class #{klass} must implement ##{method}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
id = id.to_s
|
19
|
+
methods.each do |method|
|
20
|
+
module_eval <<-EOF1
|
21
|
+
def #{method}(*args)
|
22
|
+
#{id}_state.#{method}(*args)
|
23
|
+
end
|
24
|
+
EOF1
|
25
|
+
end
|
26
|
+
end # state_object_events
|
27
|
+
|
28
|
+
def state_objects(id, *opts) # :nodoc:
|
29
|
+
id = id.to_s
|
30
|
+
id_klasses = id + '_klasses'
|
31
|
+
class_eval <<-EOF
|
32
|
+
class_attribute "#{id_klasses}".to_sym
|
33
|
+
send("#{id_klasses}=".to_sym, {})
|
34
|
+
|
35
|
+
def self.#{id}_states
|
36
|
+
#{id_klasses}.values
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.#{id}s
|
40
|
+
#{id_klasses}.map { |key, klass| [klass.label, key] }
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.#{id}_js_list
|
44
|
+
result = '['
|
45
|
+
self.#{id}_states.each{ |klass| result = result + "'"+ klass.label + "', "}
|
46
|
+
result.chop!.chop! if result.size > 2
|
47
|
+
result + ']'
|
48
|
+
end
|
49
|
+
|
50
|
+
def #{id}_state_klass
|
51
|
+
self.#{id_klasses}[self.#{id}]
|
52
|
+
end
|
53
|
+
|
54
|
+
def #{id}_state
|
55
|
+
#{id}_state_klass.new(self)
|
56
|
+
end
|
57
|
+
|
58
|
+
def #{id}_label
|
59
|
+
#{id}_state_klass.label
|
60
|
+
end
|
61
|
+
|
62
|
+
def #{id}_symbol
|
63
|
+
#{id}_state_klass.symbol
|
64
|
+
end
|
65
|
+
EOF
|
66
|
+
opts.each do |option_klass|
|
67
|
+
[:symbol, :label, :db_value].each do |required_method|
|
68
|
+
unless option_klass.respond_to?(required_method)
|
69
|
+
raise "Invalid State class ["+ option_klass.to_s + "]. Must implement a class method named: ##{required_method}. I suggest using #state_object_values"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
letter = option_klass.db_value
|
74
|
+
display_text = option_klass.label
|
75
|
+
if(send(id_klasses).has_key?(letter))
|
76
|
+
raise "Duplicate key during state_objects :" + id + ". key: " +
|
77
|
+
letter + ' for text: ' + display_text
|
78
|
+
end
|
79
|
+
send(id_klasses)[letter] = option_klass
|
80
|
+
module_eval <<-EOF2
|
81
|
+
def #{id}_#{option_klass.symbol}?
|
82
|
+
self.#{id} == '#{letter}'
|
83
|
+
end
|
84
|
+
def #{id}_#{option_klass.symbol}!
|
85
|
+
self.#{id} = '#{letter}'
|
86
|
+
end
|
87
|
+
EOF2
|
88
|
+
end # opts.each
|
89
|
+
end # state_objects
|
90
|
+
end # ModelAdditions
|
91
|
+
end # StateObjects
|
92
|
+
|
93
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'state_objects/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "state_objects"
|
8
|
+
gem.version = StateObjects::VERSION
|
9
|
+
gem.authors = ["Mark Windholtz"]
|
10
|
+
gem.email = ["windholtz@gmail.com"]
|
11
|
+
gem.homepage = "https://github.com/mwindholtz/state_objects"
|
12
|
+
gem.summary = %q{ 'State' Design Pattern from the Gang of Four book }
|
13
|
+
gem.description = %q{ 'State' Design Pattern from the Gang of Four book }
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
# gem.add_development_dependency "supermodel" # TODO
|
21
|
+
gem.add_development_dependency "activerecord"
|
22
|
+
gem.add_development_dependency "rspec-given"
|
23
|
+
gem.add_runtime_dependency "activerecord"
|
24
|
+
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: state_objects
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mark Windholtz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-25 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec-given
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: activerecord
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: ! ' ''State'' Design Pattern from the Gang of Four book '
|
63
|
+
email:
|
64
|
+
- windholtz@gmail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- LICENSE.txt
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- lib/state_objects.rb
|
75
|
+
- lib/state_objects/base.rb
|
76
|
+
- lib/state_objects/model_additions.rb
|
77
|
+
- lib/state_objects/version.rb
|
78
|
+
- state_objects.gemspec
|
79
|
+
homepage: https://github.com/mwindholtz/state_objects
|
80
|
+
licenses: []
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 1.8.24
|
100
|
+
signing_key:
|
101
|
+
specification_version: 3
|
102
|
+
summary: ! '''State'' Design Pattern from the Gang of Four book'
|
103
|
+
test_files: []
|