maintain 0.2.21 → 0.2.22
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +10 -0
- data/README.markdown +145 -116
- data/Rakefile +0 -20
- data/VERSION +1 -1
- data/autotest/discover.rb +0 -1
- data/lib/maintain/backend.rb +13 -9
- data/lib/maintain/maintainer.rb +12 -1
- data/lib/maintain/value.rb +23 -12
- data/spec/active_record_spec.rb +3 -0
- data/spec/setting_state_spec.rb +9 -0
- metadata +3 -3
data/CHANGES
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
0.2.22
|
2
|
+
* Added `bang!` method support, so now you can call `@object.awesome!`
|
3
|
+
and its state will be set to "awesome." Like all Maintain methods,
|
4
|
+
I'm saying f*ck you to convention and letting you go nuts; you can
|
5
|
+
achieve the same effect one of three ways:
|
6
|
+
|
7
|
+
@object.awesome!
|
8
|
+
@object.state.awesome!
|
9
|
+
@object.state_awesome!
|
10
|
+
|
1
11
|
0.2.21
|
2
12
|
* Added Enumerable support to bitmask values, so now you can parse
|
3
13
|
through flags with each, select, map, find, and more! This also
|
data/README.markdown
CHANGED
@@ -19,88 +19,109 @@ Basic Usage
|
|
19
19
|
**Maintain** is pretty straightforward to use. First, you have to tell a Ruby object to maintain
|
20
20
|
state on an attribute:
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
22
|
+
```ruby
|
23
|
+
class Foo
|
24
|
+
extend Maintain
|
25
|
+
maintains :state do
|
26
|
+
state :new, :default => true
|
27
|
+
state :old
|
28
|
+
end
|
29
|
+
end
|
30
|
+
```
|
29
31
|
|
30
32
|
That's it for basic state maintenance! Check it out:
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
```ruby
|
35
|
+
foo = Foo.new
|
36
|
+
foo.state #=> :new
|
37
|
+
foo.new? #=> true
|
38
|
+
foo.state = :old
|
39
|
+
foo.old? #=> true
|
40
|
+
```
|
37
41
|
|
38
42
|
But wait! What if you've already defined "new?" on the Foo class? Not to worry, Maintain won't step on your toes. Just use:
|
39
43
|
|
40
44
|
foo.state.new?
|
41
45
|
|
42
|
-
|
46
|
+
And when you *want* Maintain to step on your toes? You can add an optionally add:
|
43
47
|
|
44
48
|
state :new, :force => true
|
45
49
|
|
46
50
|
...and Maintain will make sure your methods get added, even if it overwrites a previous method.
|
47
51
|
|
52
|
+
**UPDATE: Maintain** now supports `bang!` style methods for declaring a state imperatively. It's as simple as calling
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
foo = Foo.new
|
56
|
+
foo.old!
|
57
|
+
foo.state #=> :old
|
58
|
+
```
|
59
|
+
|
48
60
|
Comparisons
|
49
61
|
-
|
50
62
|
|
51
|
-
**Maintain** provides quick and easy comparisons between states.
|
52
|
-
|
63
|
+
**Maintain** provides quick and easy comparisons between states. By default, it uses the order in which you add states to
|
64
|
+
rank them. From our example above:
|
53
65
|
|
54
|
-
|
55
|
-
|
56
|
-
|
66
|
+
```ruby
|
67
|
+
foo.state = :new
|
68
|
+
foo.state > :old #=> false
|
69
|
+
foo.state < :old #=> true
|
70
|
+
```
|
57
71
|
|
58
|
-
|
72
|
+
As an optional second argument to `state`, you can specify a comparison value. This will allow you to define states in any
|
73
|
+
order you want:
|
59
74
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
75
|
+
```ruby
|
76
|
+
class Foo
|
77
|
+
extend Maintain
|
78
|
+
maintains :state do
|
79
|
+
state :new, 12, :default => true
|
80
|
+
state :old, 5
|
81
|
+
end
|
82
|
+
end
|
67
83
|
|
68
|
-
|
84
|
+
Foo.new.state > old #=> true
|
85
|
+
```
|
69
86
|
|
70
87
|
Hooks
|
71
88
|
-
|
72
89
|
|
73
90
|
**Maintain** can hook into state entry and exit, and provides a number of mechanisms for doing so:
|
74
91
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
92
|
+
```ruby
|
93
|
+
class Foo < ActiveRecord::Base
|
94
|
+
maintains :state do
|
95
|
+
state :active, :enter => :activated
|
96
|
+
state :inactive, :exit => lambda { self.bar.baz! }
|
97
|
+
end
|
98
|
+
|
99
|
+
def activated
|
100
|
+
puts "I'm alive!"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
```
|
85
104
|
|
86
105
|
Of course, maybe that's not your style. Why not try this?
|
87
106
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
107
|
+
```ruby
|
108
|
+
class Foo
|
109
|
+
extend Maintain
|
110
|
+
maintains :state do
|
111
|
+
state :active
|
112
|
+
state :inactive
|
93
113
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
114
|
+
on :enter, :active, :activated
|
115
|
+
on :exit, :inactive do
|
116
|
+
bar.baz!
|
117
|
+
end
|
118
|
+
end
|
99
119
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
120
|
+
def activated
|
121
|
+
puts "I'm alive!"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
```
|
104
125
|
|
105
126
|
|
106
127
|
Aggregates
|
@@ -109,21 +130,23 @@ Aggregates
|
|
109
130
|
What about when a group of states is needed? Yeah, you could write `foo.bar? || foo.baz?`. You could even make that a method!
|
110
131
|
But why not just add the following?
|
111
132
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
133
|
+
```ruby
|
134
|
+
class Foo
|
135
|
+
extend Maintain
|
136
|
+
maintains :state do
|
137
|
+
state :new
|
138
|
+
state :old
|
139
|
+
state :borrowed
|
140
|
+
state :blue
|
141
|
+
|
142
|
+
aggregate :starts_with_b, [:borrowed, :blue]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
foo = Foo.new
|
147
|
+
foo.status = :borrowed
|
148
|
+
foo.starts_with_b? #=> true
|
149
|
+
```
|
127
150
|
|
128
151
|
Bitmasking
|
129
152
|
-
|
@@ -131,48 +154,52 @@ Bitmasking
|
|
131
154
|
Sometimes you need to store a simple combination of values. Sure, you could add individual columns for each value to your
|
132
155
|
relational database - or you could implement a single bitmask column:
|
133
156
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
end
|
146
|
-
|
147
|
-
foo = Foo.new
|
148
|
-
foo.state #=> nil
|
149
|
-
foo.state = [:new, :borrowed]
|
150
|
-
foo.state #=> [:new, :borrowed]
|
151
|
-
foo.new? #=> true
|
152
|
-
foo.borrowed? #=> true
|
153
|
-
foo.blue? #=> false
|
154
|
-
foo.blue!
|
155
|
-
foo.blue? #=> true
|
156
|
-
|
157
|
-
# foo.state will boil happily down to an integer when you store it.
|
158
|
-
|
159
|
-
You can also set multiple defaults on bitmasks, just in case you're defaults involve some complicated mix of options:
|
160
|
-
|
161
|
-
class Foo
|
162
|
-
extend Maintain
|
163
|
-
maintains :state, :bitmask => true do
|
164
|
-
state :new, 1, :default => true
|
165
|
-
state :old, 2
|
166
|
-
state :borrowed, 3, :default => true
|
167
|
-
state :blue, 4
|
168
|
-
end
|
157
|
+
```ruby
|
158
|
+
class Foo
|
159
|
+
extend Maintain
|
160
|
+
maintains :state, :bitmask => true do
|
161
|
+
# NOTE: Maintain will try to infer a bitmask value if you do not provide an integer here,
|
162
|
+
# but if you don't -- and you re-order your state calls later -- all stored bitmasks will
|
163
|
+
# be invalidated. You have been warned.
|
164
|
+
state :new, 1
|
165
|
+
state :old, 2
|
166
|
+
state :borrowed, 3
|
167
|
+
state :blue, 4
|
169
168
|
end
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
169
|
+
end
|
170
|
+
|
171
|
+
foo = Foo.new
|
172
|
+
foo.state #=> nil
|
173
|
+
foo.state = [:new, :borrowed]
|
174
|
+
foo.state #=> [:new, :borrowed]
|
175
|
+
foo.new? #=> true
|
176
|
+
foo.borrowed? #=> true
|
177
|
+
foo.blue? #=> false
|
178
|
+
foo.blue!
|
179
|
+
foo.blue? #=> true
|
180
|
+
|
181
|
+
# foo.state will boil happily down to an integer when you store it.
|
182
|
+
```
|
183
|
+
|
184
|
+
You can also set multiple defaults on bitmasks, just in case your defaults involve some complicated mix of options:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
class Foo
|
188
|
+
extend Maintain
|
189
|
+
maintains :state, :bitmask => true do
|
190
|
+
state :new, 1, :default => true
|
191
|
+
state :old, 2
|
192
|
+
state :borrowed, 3, :default => true
|
193
|
+
state :blue, 4
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
foo = Foo.new
|
198
|
+
foo.new? #=> true
|
199
|
+
foo.old? #=> false
|
200
|
+
foo.borrowed? #=> true
|
201
|
+
foo.blue? #=> false
|
202
|
+
```
|
176
203
|
|
177
204
|
Named Scopes
|
178
205
|
-
|
@@ -180,12 +207,14 @@ Named Scopes
|
|
180
207
|
**Maintain** knows all about ActiveRecord - it even extends ActiveRecord::Base by default. So it stands to reason that adding states
|
181
208
|
and aggregates will automatically create named scopes on ActiveRecord::Base subclasses for those states! Check it:
|
182
209
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
210
|
+
```ruby
|
211
|
+
class Foo < ActiveRecord::Base
|
212
|
+
maintains :state do
|
213
|
+
state :active
|
214
|
+
state :inactive
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
Foo.active #=> []
|
219
|
+
Foo.inactive #=> []
|
220
|
+
```
|
data/Rakefile
CHANGED
@@ -1,25 +1,5 @@
|
|
1
1
|
require 'rake'
|
2
2
|
|
3
|
-
task :default => :spec
|
4
|
-
|
5
|
-
begin
|
6
|
-
require 'spec/rake/spectask'
|
7
|
-
|
8
|
-
desc "Run all examples"
|
9
|
-
Spec::Rake::SpecTask.new('spec') do |t|
|
10
|
-
t.spec_files = FileList['spec/**/*.rb']
|
11
|
-
end
|
12
|
-
|
13
|
-
desc "Run all examples with RCov"
|
14
|
-
Spec::Rake::SpecTask.new('spec:rcov') do |t|
|
15
|
-
t.spec_files = FileList['spec/**/*.rb']
|
16
|
-
t.rcov = true
|
17
|
-
t.rcov_opts = ['--exclude', 'spec,gem']
|
18
|
-
end
|
19
|
-
rescue LoadError
|
20
|
-
puts "Could not load Rspec. To run tests, use `gem install rspec`"
|
21
|
-
end
|
22
|
-
|
23
3
|
begin
|
24
4
|
require 'jeweler'
|
25
5
|
Jeweler::Tasks.new do |gemspec|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.22
|
data/autotest/discover.rb
CHANGED
data/lib/maintain/backend.rb
CHANGED
@@ -5,20 +5,24 @@ module Maintain
|
|
5
5
|
class << self
|
6
6
|
def add(name, owner)
|
7
7
|
classes[name.to_sym] = owner
|
8
|
+
# Dig through the constant name to find if it exists
|
8
9
|
modules = owner.split('::')
|
9
10
|
if Object.const_defined?(modules.first) && owner = Object.const_get(modules.shift)
|
10
11
|
while modules.length > 0
|
11
12
|
owner = owner.const_get(modules.shift)
|
12
13
|
end
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
14
|
+
# If it exists, extend it with Maintain methods automatically
|
15
|
+
owner.extend Maintain
|
16
|
+
# TODO: Try and remember why I did this
|
17
|
+
# if owner.is_a? Module
|
18
|
+
# owner.class_eval do
|
19
|
+
# class << self
|
20
|
+
# include Maintain
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
# else
|
24
|
+
# owner.extend Maintain
|
25
|
+
# end
|
22
26
|
end
|
23
27
|
end
|
24
28
|
|
data/lib/maintain/maintainer.rb
CHANGED
@@ -24,7 +24,7 @@ module Maintain
|
|
24
24
|
end
|
25
25
|
# Now define the state
|
26
26
|
if back_end
|
27
|
-
back_end.aggregate(maintainee, name, @attribute, conditions.map{|value| states[value][:value].is_a?(Symbol) ? states[value][:value].to_s : states[value][:value] }, {:force => options[:force]})
|
27
|
+
back_end.aggregate(maintainee, name, @attribute, conditions.select{|value| states.has_key?(value) }.map{|value| states[value][:value].is_a?(Symbol) ? states[value][:value].to_s : states[value][:value] }.compact, {:force => options[:force]})
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
@@ -157,6 +157,17 @@ module Maintain
|
|
157
157
|
end
|
158
158
|
#{"alias :#{boolean_method} :#{@attribute}_#{boolean_method}" if method_free?(boolean_method) || options[:force]}
|
159
159
|
EOC
|
160
|
+
|
161
|
+
# Last but not least, add bang methods to automatically convert to state. Like boolean
|
162
|
+
# methods above, these only get added if they're not already things that are things.
|
163
|
+
bang_method = "#{name}!"
|
164
|
+
# Override any attribute_state! methods
|
165
|
+
maintainee.class_eval <<-EOC
|
166
|
+
def #{@attribute}_#{bang_method}
|
167
|
+
#{@attribute}.#{bang_method}
|
168
|
+
end
|
169
|
+
#{"alias :#{bang_method} :#{@attribute}_#{bang_method}" if method_free?(bang_method) || options[:force]}
|
170
|
+
EOC
|
160
171
|
end
|
161
172
|
|
162
173
|
def states
|
data/lib/maintain/value.rb
CHANGED
@@ -78,20 +78,31 @@ module Maintain
|
|
78
78
|
|
79
79
|
# TODO: Sweet god, this is hideous and needs to be cleaned up!
|
80
80
|
def method_missing(method, *args)
|
81
|
-
if (method.to_s =~ /^(.+)
|
82
|
-
|
83
|
-
if @state.states.has_key?(
|
81
|
+
if (method.to_s =~ /^(.+)(\?|\!)$/)
|
82
|
+
value_name = $1.to_sym
|
83
|
+
if @state.states.has_key?(value_name)
|
84
|
+
case $2
|
85
|
+
when '?'
|
86
|
+
self.class.class_eval <<-EOC
|
87
|
+
def #{method}
|
88
|
+
self == #{value_name.inspect}
|
89
|
+
end
|
90
|
+
EOC
|
91
|
+
# Calling `method` on ourselves fails. Something to do w/subclasses. Meh.
|
92
|
+
return self == value_name
|
93
|
+
when '!'
|
94
|
+
self.class.class_eval <<-EOC
|
95
|
+
def #{method}
|
96
|
+
self.set_value(#{value_name.inspect})
|
97
|
+
end
|
98
|
+
EOC
|
99
|
+
# Calling `method` on ourselves fails. Something to do w/subclasses. Meh.
|
100
|
+
return self.set_value(value_name)
|
101
|
+
end
|
102
|
+
elsif $2 == '?' && aggregates = @state.aggregates[value_name]
|
84
103
|
self.class.class_eval <<-EOC
|
85
104
|
def #{method}
|
86
|
-
|
87
|
-
end
|
88
|
-
EOC
|
89
|
-
# Calling `method` on ourselves fails. Something to do w/subclasses. Meh.
|
90
|
-
return self == check.to_sym
|
91
|
-
elsif aggregates = @state.aggregates[check]
|
92
|
-
self.class.class_eval <<-EOC
|
93
|
-
def #{method}
|
94
|
-
@state.aggregates[#{check.inspect}].include?(@value)
|
105
|
+
@state.aggregates[#{value_name.inspect}].include?(@value)
|
95
106
|
end
|
96
107
|
EOC
|
97
108
|
return aggregates.include?(@value)
|
data/spec/active_record_spec.rb
CHANGED
@@ -6,7 +6,10 @@ begin
|
|
6
6
|
require 'rubygems'
|
7
7
|
gem 'activerecord', '>= 2.3.5'
|
8
8
|
require 'active_record'
|
9
|
+
require 'logger'
|
9
10
|
proceed = true
|
11
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
12
|
+
ActiveRecord::Base.logger.level = Logger::Severity::UNKNOWN
|
10
13
|
rescue Gem::LoadError, LoadError
|
11
14
|
puts 'Not testing ActiveRecord (unavailable)'
|
12
15
|
end
|
data/spec/setting_state_spec.rb
CHANGED
@@ -33,6 +33,15 @@ describe Maintain do
|
|
33
33
|
@maintainer.state = 'nada'
|
34
34
|
@maintainer.state.should be_nil
|
35
35
|
end
|
36
|
+
|
37
|
+
it "should support a `state!` bang method, too!" do
|
38
|
+
@maintainer.new!
|
39
|
+
@maintainer.state.should == :new
|
40
|
+
@maintainer.overdue!
|
41
|
+
@maintainer.state.should == :overdue
|
42
|
+
@maintainer.closed!
|
43
|
+
@maintainer.state.should == :closed
|
44
|
+
end
|
36
45
|
end
|
37
46
|
|
38
47
|
describe "integer states" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: maintain
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.22
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-08-10 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: ! "\n Maintain is a simple state machine mixin for Ruby objects.
|
15
15
|
It supports comparisons, bitmasks,\n and hooks that really work. It can be
|
@@ -73,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
73
73
|
version: '0'
|
74
74
|
requirements: []
|
75
75
|
rubyforge_project:
|
76
|
-
rubygems_version: 1.8.
|
76
|
+
rubygems_version: 1.8.24
|
77
77
|
signing_key:
|
78
78
|
specification_version: 3
|
79
79
|
summary: A Ruby state machine that lets your code do the driving
|