maintain 0.2.21 → 0.2.22
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/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
|