state_machine 0.3.1 → 0.4.0
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/CHANGELOG.rdoc +26 -0
- data/README.rdoc +254 -46
- data/Rakefile +29 -3
- data/examples/AutoShop_state.png +0 -0
- data/examples/Car_state.jpg +0 -0
- data/examples/Vehicle_state.png +0 -0
- data/lib/state_machine.rb +161 -116
- data/lib/state_machine/assertions.rb +21 -0
- data/lib/state_machine/callback.rb +168 -0
- data/lib/state_machine/eval_helpers.rb +67 -0
- data/lib/state_machine/event.rb +135 -101
- data/lib/state_machine/extensions.rb +83 -0
- data/lib/state_machine/guard.rb +115 -0
- data/lib/state_machine/integrations/active_record.rb +242 -0
- data/lib/state_machine/integrations/data_mapper.rb +198 -0
- data/lib/state_machine/integrations/data_mapper/observer.rb +153 -0
- data/lib/state_machine/integrations/sequel.rb +169 -0
- data/lib/state_machine/machine.rb +746 -352
- data/lib/state_machine/transition.rb +104 -212
- data/test/active_record.log +34865 -0
- data/test/classes/switch.rb +11 -0
- data/test/data_mapper.log +14015 -0
- data/test/functional/state_machine_test.rb +249 -15
- data/test/sequel.log +3835 -0
- data/test/test_helper.rb +3 -12
- data/test/unit/assertions_test.rb +13 -0
- data/test/unit/callback_test.rb +189 -0
- data/test/unit/eval_helpers_test.rb +92 -0
- data/test/unit/event_test.rb +247 -113
- data/test/unit/guard_test.rb +420 -0
- data/test/unit/integrations/active_record_test.rb +515 -0
- data/test/unit/integrations/data_mapper_test.rb +407 -0
- data/test/unit/integrations/sequel_test.rb +244 -0
- data/test/unit/invalid_transition_test.rb +1 -1
- data/test/unit/machine_test.rb +1056 -98
- data/test/unit/state_machine_test.rb +14 -113
- data/test/unit/transition_test.rb +269 -495
- metadata +44 -30
- data/test/app_root/app/models/auto_shop.rb +0 -34
- data/test/app_root/app/models/car.rb +0 -19
- data/test/app_root/app/models/highway.rb +0 -3
- data/test/app_root/app/models/motorcycle.rb +0 -3
- data/test/app_root/app/models/switch.rb +0 -23
- data/test/app_root/app/models/switch_observer.rb +0 -20
- data/test/app_root/app/models/toggle_switch.rb +0 -2
- data/test/app_root/app/models/vehicle.rb +0 -78
- data/test/app_root/config/environment.rb +0 -7
- data/test/app_root/db/migrate/001_create_switches.rb +0 -12
- data/test/app_root/db/migrate/002_create_auto_shops.rb +0 -13
- data/test/app_root/db/migrate/003_create_highways.rb +0 -11
- data/test/app_root/db/migrate/004_create_vehicles.rb +0 -16
- data/test/factory.rb +0 -77
@@ -0,0 +1,67 @@
|
|
1
|
+
module StateMachine
|
2
|
+
# Provides a set of helper methods for evaluating methods within the context
|
3
|
+
# of an object.
|
4
|
+
module EvalHelpers
|
5
|
+
# Evaluates one of several different types of methods within the context
|
6
|
+
# of the given object. Methods can be one of the following types:
|
7
|
+
# * Symbol
|
8
|
+
# * Method / Proc
|
9
|
+
# * String
|
10
|
+
#
|
11
|
+
# == Examples
|
12
|
+
#
|
13
|
+
# Below are examples of the various ways that a method can be evaluated
|
14
|
+
# on an object:
|
15
|
+
#
|
16
|
+
# class Person
|
17
|
+
# def initialize(name)
|
18
|
+
# @name = name
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# def name
|
22
|
+
# @name
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# class PersonCallback
|
27
|
+
# def self.run(person)
|
28
|
+
# person.name
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# person = Person.new('John Smith')
|
33
|
+
#
|
34
|
+
# evaluate_method(person, :name) # => "John Smith"
|
35
|
+
# evaluate_method(person, PersonCallback.method(:run)) # => "John Smith"
|
36
|
+
# evaluate_method(person, Proc.new {|person| person.name}) # => "John Smith"
|
37
|
+
# evaluate_method(person, lambda {|person| person.name}) # => "John Smith"
|
38
|
+
# evaluate_method(person, '@name') # => "John Smith"
|
39
|
+
#
|
40
|
+
# == Additional arguments
|
41
|
+
#
|
42
|
+
# Additional arguments can be passed to the methods being evaluated. If
|
43
|
+
# the method defines additional arguments other than the object context,
|
44
|
+
# then all arguments are required.
|
45
|
+
#
|
46
|
+
# For example,
|
47
|
+
#
|
48
|
+
# person = Person.new('John Smith')
|
49
|
+
#
|
50
|
+
# evaluate_method(person, lambda {|person| person.name}, 21) # => "John Smith"
|
51
|
+
# evaluate_method(person, lambda {|person, age| "#{person.name} is #{age}"}, 21) # => "John Smith is 21"
|
52
|
+
# evaluate_method(person, lambda {|person, age| "#{person.name} is #{age}"}, 21, 'male') # => ArgumentError: wrong number of arguments (3 for 2)
|
53
|
+
def evaluate_method(object, method, *args)
|
54
|
+
case method
|
55
|
+
when Symbol
|
56
|
+
method = object.method(method)
|
57
|
+
method.arity == 0 ? method.call : method.call(*args)
|
58
|
+
when String
|
59
|
+
eval(method, object.instance_eval {binding})
|
60
|
+
when Proc, Method
|
61
|
+
method.arity == 1 ? method.call(object) : method.call(object, *args)
|
62
|
+
else
|
63
|
+
raise ArgumentError, 'Methods must be a symbol denoting the method to call, a string to be evaluated, or a block to be invoked'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/state_machine/event.rb
CHANGED
@@ -1,116 +1,150 @@
|
|
1
1
|
require 'state_machine/transition'
|
2
|
+
require 'state_machine/guard'
|
3
|
+
require 'state_machine/assertions'
|
2
4
|
|
3
|
-
module
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
module StateMachine
|
6
|
+
# An event defines an action that transitions an attribute from one state to
|
7
|
+
# another. The state that an attribute is transitioned to depends on the
|
8
|
+
# guards configured for the event.
|
9
|
+
class Event
|
10
|
+
include Assertions
|
11
|
+
|
12
|
+
# The state machine for which this event is defined
|
13
|
+
attr_accessor :machine
|
14
|
+
|
15
|
+
# The name of the action that fires the event
|
16
|
+
attr_reader :name
|
17
|
+
|
18
|
+
# The list of guards that determine what state this event transitions
|
19
|
+
# objects to when fired
|
20
|
+
attr_reader :guards
|
21
|
+
|
22
|
+
# A list of all of the states known to this event using the configured
|
23
|
+
# guards/transitions as the source.
|
24
|
+
attr_reader :known_states
|
25
|
+
|
26
|
+
# Creates a new event within the context of the given machine
|
27
|
+
def initialize(machine, name) #:nodoc:
|
28
|
+
@machine = machine
|
29
|
+
@name = name
|
30
|
+
@guards = []
|
31
|
+
@known_states = []
|
10
32
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
33
|
+
add_actions
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates a copy of this event in addition to the list of associated
|
37
|
+
# guards to prevent conflicts across different events.
|
38
|
+
def initialize_copy(orig) #:nodoc:
|
39
|
+
super
|
40
|
+
@guards = @guards.dup
|
41
|
+
@known_states = @known_states.dup
|
42
|
+
end
|
43
|
+
|
44
|
+
# Creates a new transition that will be evaluated when the event is fired.
|
45
|
+
#
|
46
|
+
# Configuration options:
|
47
|
+
# * +to+ - The state that being transitioned to. If not specified, then the transition will not change the state.
|
48
|
+
# * +from+ - A state or array of states that can be transitioned from. If not specified, then the transition can occur for *any* from state
|
49
|
+
# * +except_from+ - A state or array of states that *cannot* be transitioned from.
|
50
|
+
# * +if+ - Specifies a method, proc or string to call to determine if the transition should occur (e.g. :if => :moving?, or :if => Proc.new {|car| car.speed > 60}). The method, proc or string should return or evaluate to a true or false value.
|
51
|
+
# * +unless+ - Specifies a method, proc or string to call to determine if the transition should not occur (e.g. :unless => :stopped?, or :unless => Proc.new {|car| car.speed <= 60}). The method, proc or string should return or evaluate to a true or false value.
|
52
|
+
#
|
53
|
+
# == Order of operations
|
54
|
+
#
|
55
|
+
# Transitions are evaluated in the order in which they're defined. As a
|
56
|
+
# result, if more than one transition applies to a given object, then the
|
57
|
+
# first transition that matches will be performed.
|
58
|
+
#
|
59
|
+
# == Dynamic states
|
60
|
+
#
|
61
|
+
# There is limited support for using dynamically generated values for the
|
62
|
+
# +to+ state in transitions. This is especially useful for times where
|
63
|
+
# the machine attribute represents a Time object. In order to have a
|
64
|
+
# a transition be made to the current time, a lambda block can be passed
|
65
|
+
# in representing the state, such as:
|
66
|
+
#
|
67
|
+
# transition :to => lambda {Time.now}
|
68
|
+
#
|
69
|
+
# == Examples
|
70
|
+
#
|
71
|
+
# transition :from => %w(first_gear reverse)
|
72
|
+
# transition :except_from => 'parked'
|
73
|
+
# transition :to => 'parked'
|
74
|
+
# transition :to => lambda {Time.now}
|
75
|
+
# transition :to => 'parked', :from => 'first_gear'
|
76
|
+
# transition :to => 'parked', :from => %w(first_gear reverse)
|
77
|
+
# transition :to => 'parked', :from => 'first_gear', :if => :moving?
|
78
|
+
# transition :to => 'parked', :from => 'first_gear', :unless => :stopped?
|
79
|
+
# transition :to => 'parked', :except_from => 'parked'
|
80
|
+
def transition(options)
|
81
|
+
assert_valid_keys(options, :to, :from, :except_from, :if, :unless)
|
57
82
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
83
|
+
guards << guard = Guard.new(options)
|
84
|
+
@known_states |= guard.known_states
|
85
|
+
guard
|
86
|
+
end
|
87
|
+
|
88
|
+
# Determines whether any transitions can be performed for this event based
|
89
|
+
# on the current state of the given object.
|
90
|
+
#
|
91
|
+
# If the event can't be fired, then this will return false, otherwise true.
|
92
|
+
def can_fire?(object)
|
93
|
+
!next_transition(object).nil?
|
94
|
+
end
|
95
|
+
|
96
|
+
# Finds and builds the next transition that can be performed on the given
|
97
|
+
# object. If no transitions can be made, then this will return nil.
|
98
|
+
def next_transition(object)
|
99
|
+
from = object.send(machine.attribute)
|
65
100
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
101
|
+
if guard = guards.find {|guard| guard.matches?(object, :from => from)}
|
102
|
+
# Guard allows for the transition to occur
|
103
|
+
to = guard.requirements[:to] || from
|
104
|
+
to = to.call if to.is_a?(Proc)
|
105
|
+
Transition.new(object, machine, name, from, to)
|
70
106
|
end
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
107
|
+
end
|
108
|
+
|
109
|
+
# Attempts to perform the next available transition on the given object.
|
110
|
+
# If no transitions can be made, then this will return false, otherwise
|
111
|
+
# true.
|
112
|
+
def fire(object, *args)
|
113
|
+
if transition = next_transition(object)
|
114
|
+
transition.perform(*args)
|
115
|
+
else
|
116
|
+
false
|
77
117
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
118
|
+
end
|
119
|
+
|
120
|
+
protected
|
121
|
+
# Add the various instance methods that can transition the object using
|
122
|
+
# the current event
|
123
|
+
def add_actions
|
124
|
+
attribute = machine.attribute
|
125
|
+
name = self.name
|
126
|
+
|
127
|
+
machine.owner_class.class_eval do
|
128
|
+
# Checks whether the event can be fired on the current object
|
129
|
+
define_method("can_#{name}?") do
|
130
|
+
self.class.state_machines[attribute].events[name].can_fire?(self)
|
131
|
+
end
|
85
132
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
define_method("can_#{name}?") {self.class.state_machines[attribute].events[name].can_fire?(self)}
|
133
|
+
# Gets the next transition that would be performed if the event were to be fired now
|
134
|
+
define_method("next_#{name}_transition") do
|
135
|
+
self.class.state_machines[attribute].events[name].next_transition(self)
|
90
136
|
end
|
91
|
-
end
|
92
|
-
|
93
|
-
# Attempts to find a transition that can be performed for this event.
|
94
|
-
#
|
95
|
-
# +bang+ indicates whether +perform+ or <tt>perform!</tt> will be
|
96
|
-
# invoked on transitions.
|
97
|
-
def run(record, bang)
|
98
|
-
result = false
|
99
137
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
result = bang ? transition.perform!(record) : transition.perform(record)
|
104
|
-
break
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
# Rollback any changes if the transition failed
|
109
|
-
raise ActiveRecord::Rollback unless result
|
138
|
+
# Fires the event
|
139
|
+
define_method(name) do |*args|
|
140
|
+
self.class.state_machines[attribute].events[name].fire(self, *args)
|
110
141
|
end
|
111
142
|
|
112
|
-
|
143
|
+
# Fires the event, raising an exception if it fails to transition
|
144
|
+
define_method("#{name}!") do |*args|
|
145
|
+
send(name, *args) || raise(StateMachine::InvalidTransition, "Cannot transition via :#{name} from #{send(attribute).inspect}")
|
146
|
+
end
|
113
147
|
end
|
114
|
-
|
148
|
+
end
|
115
149
|
end
|
116
150
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module StateMachine
|
2
|
+
module ClassMethods
|
3
|
+
def self.extended(base) #:nodoc:
|
4
|
+
base.class_eval do
|
5
|
+
@state_machines = {}
|
6
|
+
|
7
|
+
# method_added may get defined by the class, so instead it's chained
|
8
|
+
class << self
|
9
|
+
alias_method :method_added_without_state_machine, :method_added
|
10
|
+
alias_method :method_added, :method_added_with_state_machine
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Ensures that the +initialize+ hook defined in StateMachine::InstanceMethods
|
16
|
+
# remains there even if the class defines its own +initialize+ method
|
17
|
+
# *after* the state machine has been defined. For example,
|
18
|
+
#
|
19
|
+
# class Switch
|
20
|
+
# state_machine do
|
21
|
+
# ...
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# def initialize(attributes = {})
|
25
|
+
# ...
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
def method_added_with_state_machine(method) #:nodoc:
|
29
|
+
method_added_without_state_machine(method)
|
30
|
+
|
31
|
+
# Aliasing the +initialize+ method also invokes +method_added+, so
|
32
|
+
# alias processing is tracked to prevent an infinite loop
|
33
|
+
if !@skip_initialize_hook && [:initialize, :initialize_with_state_machine].include?(method)
|
34
|
+
@skip_initialize_hook = true
|
35
|
+
|
36
|
+
# +define_method+ is used to prevent it from showing up in #instance_methods
|
37
|
+
alias_method :initialize_without_state_machine, :initialize
|
38
|
+
class_eval <<-end_eval, __FILE__, __LINE__
|
39
|
+
def initialize(*args, &block)
|
40
|
+
initialize_with_state_machine(*args, &block)
|
41
|
+
end
|
42
|
+
end_eval
|
43
|
+
|
44
|
+
@skip_initialize_hook = false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Gets the current list of state machines defined for this class. This
|
49
|
+
# class-level attribute acts like an inheritable attribute. The attribute
|
50
|
+
# is available to each subclass, each subclass having a copy of its
|
51
|
+
# superclass's attribute.
|
52
|
+
#
|
53
|
+
# The hash of state machines maps +name+ => +machine+, e.g.
|
54
|
+
#
|
55
|
+
# Vehicle.state_machines # => {"state" => #<StateMachine::Machine:0xb6f6e4a4 ...>
|
56
|
+
def state_machines
|
57
|
+
@state_machines ||= superclass.state_machines.dup
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module InstanceMethods
|
62
|
+
def self.included(base) #:nodoc:
|
63
|
+
# Methods added from an included module don't invoke +method_added+,
|
64
|
+
# triggering the initialize alias, so it's done explicitly
|
65
|
+
base.method_added(:initialize_with_state_machine)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Defines the initial values for state machine attributes. The values
|
69
|
+
# will be set *after* the original initialize method is invoked. This is
|
70
|
+
# necessary in order to ensure that the object is initialized before
|
71
|
+
# dynamic initial attributes are evaluated.
|
72
|
+
def initialize_with_state_machine(*args, &block)
|
73
|
+
initialize_without_state_machine(*args, &block)
|
74
|
+
|
75
|
+
self.class.state_machines.each do |attribute, machine|
|
76
|
+
# Set the initial value of the machine's attribute unless it already
|
77
|
+
# exists (which must mean the defaults are being skipped)
|
78
|
+
value = send(attribute)
|
79
|
+
send("#{attribute}=", machine.initial_state(self)) if value.nil? || value.respond_to?(:empty?) && value.empty?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'state_machine/eval_helpers'
|
2
|
+
require 'state_machine/assertions'
|
3
|
+
|
4
|
+
module StateMachine
|
5
|
+
# Represents a set of requirements that must be met in order for a transition
|
6
|
+
# or callback to occur. Guards verify that the event, from state, and to
|
7
|
+
# state of the transition match, in addition to if/unless conditionals for
|
8
|
+
# an object's state.
|
9
|
+
class Guard
|
10
|
+
include Assertions
|
11
|
+
include EvalHelpers
|
12
|
+
|
13
|
+
# The transition/conditional options that must be met in order for the
|
14
|
+
# guard to match
|
15
|
+
attr_reader :requirements
|
16
|
+
|
17
|
+
# A list of all of the states known to this guard. This will pull state
|
18
|
+
# names from the following requirements:
|
19
|
+
# * +to+
|
20
|
+
# * +from+
|
21
|
+
# * +except_to+
|
22
|
+
# * +except_from+
|
23
|
+
attr_reader :known_states
|
24
|
+
|
25
|
+
# Creates a new guard with the given requirements
|
26
|
+
def initialize(requirements = {}) #:nodoc:
|
27
|
+
assert_valid_keys(requirements, :to, :from, :on, :except_to, :except_from, :except_on, :if, :unless)
|
28
|
+
|
29
|
+
@requirements = requirements
|
30
|
+
@known_states = [:to, :from, :except_to, :except_from].inject([]) {|states, option| states |= Array(requirements[option])}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Determines whether the given object / query matches the requirements
|
34
|
+
# configured for this guard. In addition to matching the event, from state,
|
35
|
+
# and to state, this will also check whether the configured :if/:unless
|
36
|
+
# conditionals pass on the given object.
|
37
|
+
#
|
38
|
+
# Query options:
|
39
|
+
# * +to+ - One or more states being transitioned to. If none are specified, then this will always match.
|
40
|
+
# * +from+ - One or more states being transitioned from. If none are specified, then this will always match.
|
41
|
+
# * +on+ - One or more events that fired the transition. If none are specified, then this will always match.
|
42
|
+
# * +except_to+ - One more states *not* being transitioned to
|
43
|
+
# * +except_from+ - One or more states *not* being transitioned from
|
44
|
+
# * +except_on+ - One or more events that *did not* fire the transition.
|
45
|
+
#
|
46
|
+
# == Examples
|
47
|
+
#
|
48
|
+
# guard = StateMachine::Guard.new(:on => 'ignite', :from => 'parked', :to => 'idling')
|
49
|
+
#
|
50
|
+
# # Successful
|
51
|
+
# guard.matches?(object, :on => 'ignite') # => true
|
52
|
+
# guard.matches?(object, :from => 'parked') # => true
|
53
|
+
# guard.matches?(object, :to => 'idling') # => true
|
54
|
+
# guard.matches?(object, :from => 'parked', :to => 'idling') # => true
|
55
|
+
# guard.matches?(object, :on => 'ignite', :from => 'parked', :to => 'idling') # => true
|
56
|
+
#
|
57
|
+
# # Unsuccessful
|
58
|
+
# guard.matches?(object, :on => 'park') # => false
|
59
|
+
# guard.matches?(object, :from => 'idling') # => false
|
60
|
+
# guard.matches?(object, :to => 'first_gear') # => false
|
61
|
+
# guard.matches?(object, :from => 'parked', :to => 'first_gear') # => false
|
62
|
+
# guard.matches?(object, :on => 'park', :from => 'parked', :to => 'idling') # => false
|
63
|
+
def matches?(object, query = {})
|
64
|
+
matches_query?(object, query) && matches_conditions?(object)
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
# Verify that the from state, to state, and event match the query
|
69
|
+
def matches_query?(object, query)
|
70
|
+
(!query || query.empty?) ||
|
71
|
+
find_match(query[:from], requirements[:from], requirements[:except_from]) &&
|
72
|
+
find_match(query[:to], requirements[:to], requirements[:except_to]) &&
|
73
|
+
find_match(query[:on], requirements[:on], requirements[:except_on])
|
74
|
+
end
|
75
|
+
|
76
|
+
# Verify that the conditionals for this guard evaluate to true for the
|
77
|
+
# given object
|
78
|
+
def matches_conditions?(object)
|
79
|
+
if requirements[:if]
|
80
|
+
evaluate_method(object, requirements[:if])
|
81
|
+
elsif requirements[:unless]
|
82
|
+
!evaluate_method(object, requirements[:unless])
|
83
|
+
else
|
84
|
+
true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Attempts to find the given value in either a whitelist of values or
|
89
|
+
# a blacklist of values. The whitelist will always be used first if it
|
90
|
+
# is specified. If neither lists are specified or the value is blank,
|
91
|
+
# then this will always find a match and return true.
|
92
|
+
#
|
93
|
+
# == Examples
|
94
|
+
#
|
95
|
+
# find_match(nil, %w(parked idling), nil) # => true
|
96
|
+
# find_match('parked', nil, nil) # => true
|
97
|
+
# find_match('parked', %w(parked idling), nil) # => true
|
98
|
+
# find_match('first_gear', %w(parked idling, nil) # => false
|
99
|
+
# find_match('parked', nil, %w(parked idling)) # => false
|
100
|
+
# find_match('first_gear', nil, %w(parked idling)) # => true
|
101
|
+
def find_match(value, whitelist, blacklist)
|
102
|
+
if value
|
103
|
+
if whitelist
|
104
|
+
Array(whitelist).include?(value)
|
105
|
+
elsif blacklist
|
106
|
+
!Array(blacklist).include?(value)
|
107
|
+
else
|
108
|
+
true
|
109
|
+
end
|
110
|
+
else
|
111
|
+
true
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|