state_machines 0.6.0 → 0.30.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.
- checksums.yaml +4 -4
- data/README.md +205 -14
- data/lib/state_machines/branch.rb +20 -17
- data/lib/state_machines/callback.rb +13 -12
- data/lib/state_machines/core.rb +3 -3
- data/lib/state_machines/core_ext/class/state_machine.rb +2 -0
- data/lib/state_machines/core_ext.rb +2 -0
- data/lib/state_machines/error.rb +7 -4
- data/lib/state_machines/eval_helpers.rb +93 -26
- data/lib/state_machines/event.rb +41 -29
- data/lib/state_machines/event_collection.rb +6 -5
- data/lib/state_machines/extensions.rb +7 -5
- data/lib/state_machines/helper_module.rb +3 -1
- data/lib/state_machines/integrations/base.rb +3 -1
- data/lib/state_machines/integrations.rb +13 -14
- data/lib/state_machines/machine/action_hooks.rb +53 -0
- data/lib/state_machines/machine/callbacks.rb +59 -0
- data/lib/state_machines/machine/class_methods.rb +93 -0
- data/lib/state_machines/machine/configuration.rb +124 -0
- data/lib/state_machines/machine/event_methods.rb +59 -0
- data/lib/state_machines/machine/helper_generators.rb +125 -0
- data/lib/state_machines/machine/integration.rb +70 -0
- data/lib/state_machines/machine/parsing.rb +77 -0
- data/lib/state_machines/machine/rendering.rb +17 -0
- data/lib/state_machines/machine/scoping.rb +44 -0
- data/lib/state_machines/machine/state_methods.rb +101 -0
- data/lib/state_machines/machine/utilities.rb +85 -0
- data/lib/state_machines/machine/validation.rb +39 -0
- data/lib/state_machines/machine.rb +83 -673
- data/lib/state_machines/machine_collection.rb +23 -15
- data/lib/state_machines/macro_methods.rb +4 -2
- data/lib/state_machines/matcher.rb +8 -5
- data/lib/state_machines/matcher_helpers.rb +3 -1
- data/lib/state_machines/node_collection.rb +23 -18
- data/lib/state_machines/options_validator.rb +72 -0
- data/lib/state_machines/path.rb +7 -5
- data/lib/state_machines/path_collection.rb +7 -4
- data/lib/state_machines/state.rb +76 -47
- data/lib/state_machines/state_collection.rb +5 -3
- data/lib/state_machines/state_context.rb +11 -8
- data/lib/state_machines/stdio_renderer.rb +74 -0
- data/lib/state_machines/syntax_validator.rb +57 -0
- data/lib/state_machines/test_helper.rb +568 -0
- data/lib/state_machines/transition.rb +45 -41
- data/lib/state_machines/transition_collection.rb +27 -26
- data/lib/state_machines/version.rb +3 -1
- data/lib/state_machines.rb +4 -1
- metadata +32 -16
- data/lib/state_machines/assertions.rb +0 -40
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module StateMachines
|
2
4
|
# A transition represents a state change for a specific attribute.
|
3
5
|
#
|
@@ -31,11 +33,11 @@ module StateMachines
|
|
31
33
|
# Determines whether the current ruby implementation supports pausing and
|
32
34
|
# resuming transitions
|
33
35
|
def self.pause_supported?
|
34
|
-
%w
|
36
|
+
%w[ruby maglev].include?(RUBY_ENGINE)
|
35
37
|
end
|
36
38
|
|
37
39
|
# Creates a new, specific transition
|
38
|
-
def initialize(object, machine, event, from_name, to_name, read_state = true)
|
40
|
+
def initialize(object, machine, event, from_name, to_name, read_state = true) # :nodoc:
|
39
41
|
@object = object
|
40
42
|
@machine = machine
|
41
43
|
@args = []
|
@@ -134,7 +136,7 @@ module StateMachines
|
|
134
136
|
# transition = StateMachines::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
|
135
137
|
# transition.attributes # => {:object => #<Vehicle:0xb7d60ea4>, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'}
|
136
138
|
def attributes
|
137
|
-
@attributes ||= {object: object, attribute: attribute, event: event, from: from, to: to}
|
139
|
+
@attributes ||= { object: object, attribute: attribute, event: event, from: from, to: to }
|
138
140
|
end
|
139
141
|
|
140
142
|
# Runs the actual transition and any before/after callbacks associated
|
@@ -160,16 +162,14 @@ module StateMachines
|
|
160
162
|
self.args = args
|
161
163
|
|
162
164
|
# Run the transition
|
163
|
-
!!TransitionCollection.new([self], {use_transactions: machine.use_transactions, actions: run_action}).perform
|
165
|
+
!!TransitionCollection.new([self], { use_transactions: machine.use_transactions, actions: run_action }).perform
|
164
166
|
end
|
165
167
|
|
166
168
|
# Runs a block within a transaction for the object being transitioned.
|
167
169
|
# By default, transactions are a no-op unless otherwise defined by the
|
168
170
|
# machine's integration.
|
169
|
-
def within_transaction
|
170
|
-
machine.within_transaction(object)
|
171
|
-
yield
|
172
|
-
end
|
171
|
+
def within_transaction(&)
|
172
|
+
machine.within_transaction(object, &)
|
173
173
|
end
|
174
174
|
|
175
175
|
# Runs the before / after callbacks for this transition. If a block is
|
@@ -184,7 +184,7 @@ module StateMachines
|
|
184
184
|
# This will return true if all before callbacks gets executed. After
|
185
185
|
# callbacks will not have an effect on the result.
|
186
186
|
def run_callbacks(options = {}, &block)
|
187
|
-
options = {before: true, after: true}.merge(options)
|
187
|
+
options = { before: true, after: true }.merge(options)
|
188
188
|
@success = false
|
189
189
|
|
190
190
|
halted = pausable { before(options[:after], &block) } if options[:before]
|
@@ -217,10 +217,10 @@ module StateMachines
|
|
217
217
|
#
|
218
218
|
# vehicle.state # => 'idling'
|
219
219
|
def persist
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
220
|
+
return if @persisted
|
221
|
+
|
222
|
+
machine.write(object, :state, to)
|
223
|
+
@persisted = true
|
224
224
|
end
|
225
225
|
|
226
226
|
# Rolls back changes made to the object's state via this transition. This
|
@@ -263,11 +263,11 @@ module StateMachines
|
|
263
263
|
# and event involved in the transition are equal
|
264
264
|
def ==(other)
|
265
265
|
other.instance_of?(self.class) &&
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
266
|
+
other.object == object &&
|
267
|
+
other.machine == machine &&
|
268
|
+
other.from_name == from_name &&
|
269
|
+
other.to_name == to_name &&
|
270
|
+
other.event == event
|
271
271
|
end
|
272
272
|
|
273
273
|
# Generates a nicely formatted description of this transitions's contents.
|
@@ -277,10 +277,10 @@ module StateMachines
|
|
277
277
|
# transition = StateMachines::Transition.new(object, machine, :ignite, :parked, :idling)
|
278
278
|
# transition # => #<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
|
279
279
|
def inspect
|
280
|
-
"#<#{self.class} #{%w
|
280
|
+
"#<#{self.class} #{%w[attribute event from from_name to to_name].map { |attr| "#{attr}=#{send(attr).inspect}" } * ' '}>"
|
281
281
|
end
|
282
282
|
|
283
|
-
|
283
|
+
private
|
284
284
|
|
285
285
|
# Runs a block that may get paused. If the block doesn't pause, then
|
286
286
|
# execution will continue as normal. If the block gets paused, then it
|
@@ -290,13 +290,16 @@ module StateMachines
|
|
290
290
|
# getting paused.
|
291
291
|
def pausable
|
292
292
|
begin
|
293
|
-
halted = !catch(:halt)
|
294
|
-
|
293
|
+
halted = !catch(:halt) do
|
294
|
+
yield
|
295
|
+
true
|
296
|
+
end
|
297
|
+
rescue StandardError => e
|
295
298
|
raise unless @resume_block
|
296
299
|
end
|
297
300
|
|
298
301
|
if @resume_block
|
299
|
-
@resume_block.call(halted,
|
302
|
+
@resume_block.call(halted, e)
|
300
303
|
else
|
301
304
|
halted
|
302
305
|
end
|
@@ -308,12 +311,12 @@ module StateMachines
|
|
308
311
|
def pause
|
309
312
|
raise ArgumentError, 'around_transition callbacks cannot be called in multiple execution contexts in java implementations of Ruby. Use before/after_transitions instead.' unless self.class.pause_supported?
|
310
313
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
314
|
+
return if @resume_block
|
315
|
+
|
316
|
+
require 'continuation' unless defined?(callcc)
|
317
|
+
callcc do |block|
|
318
|
+
@paused_block = block
|
319
|
+
throw :halt, true
|
317
320
|
end
|
318
321
|
end
|
319
322
|
|
@@ -370,8 +373,9 @@ module StateMachines
|
|
370
373
|
@before_run = true
|
371
374
|
end
|
372
375
|
|
373
|
-
action = {success: true}.merge(block_given? ? yield : {})
|
374
|
-
@result
|
376
|
+
action = { success: true }.merge(block_given? ? yield : {})
|
377
|
+
@result = action[:result]
|
378
|
+
@success = action[:success]
|
375
379
|
end
|
376
380
|
|
377
381
|
# Runs the machine's +after+ callbacks for this transition. Only
|
@@ -388,17 +392,17 @@ module StateMachines
|
|
388
392
|
# exception will not bubble up to the caller since +after+ callbacks
|
389
393
|
# should never halt the execution of a +perform+.
|
390
394
|
def after
|
391
|
-
|
392
|
-
# First resume previously paused callbacks
|
393
|
-
if resume
|
394
|
-
catch(:halt) do
|
395
|
-
type = @success ? :after : :failure
|
396
|
-
machine.callbacks[type].each { |callback| callback.call(object, context, self) }
|
397
|
-
end
|
398
|
-
end
|
395
|
+
return if @after_run
|
399
396
|
|
400
|
-
|
397
|
+
# First resume previously paused callbacks
|
398
|
+
if resume
|
399
|
+
catch(:halt) do
|
400
|
+
type = @success ? :after : :failure
|
401
|
+
machine.callbacks[type].each { |callback| callback.call(object, context, self) }
|
402
|
+
end
|
401
403
|
end
|
404
|
+
|
405
|
+
@after_run = true
|
402
406
|
end
|
403
407
|
|
404
408
|
# Gets a hash of the context defining this unique transition (including
|
@@ -410,7 +414,7 @@ module StateMachines
|
|
410
414
|
# transition = StateMachines::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
|
411
415
|
# transition.context # => {:on => :ignite, :from => :parked, :to => :idling}
|
412
416
|
def context
|
413
|
-
@context ||= {on: event, from: from_name, to: to_name}
|
417
|
+
@context ||= { on: event, from: from_name, to: to_name }
|
414
418
|
end
|
415
419
|
end
|
416
420
|
end
|
@@ -1,7 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'options_validator'
|
4
|
+
|
1
5
|
module StateMachines
|
2
6
|
# Represents a collection of transitions in a state machine
|
3
7
|
class TransitionCollection < Array
|
4
|
-
|
5
8
|
# Whether to skip running the action for each transition's machine
|
6
9
|
attr_reader :skip_actions
|
7
10
|
|
@@ -26,10 +29,10 @@ module StateMachines
|
|
26
29
|
reject! { |transition| !transition }
|
27
30
|
|
28
31
|
attributes = map { |transition| transition.attribute }.uniq
|
29
|
-
|
32
|
+
raise ArgumentError, 'Cannot perform multiple transitions in parallel for the same state machine attribute' if attributes.length != length
|
30
33
|
|
31
|
-
|
32
|
-
options = {actions: true, after: true, use_transactions: true}.merge(options)
|
34
|
+
StateMachines::OptionsValidator.assert_valid_keys!(options, :actions, :after, :use_transactions)
|
35
|
+
options = { actions: true, after: true, use_transactions: true }.merge(options)
|
33
36
|
@skip_actions = !options[:actions]
|
34
37
|
@skip_after = !options[:after]
|
35
38
|
@use_transactions = options[:use_transactions]
|
@@ -72,11 +75,11 @@ module StateMachines
|
|
72
75
|
end
|
73
76
|
end
|
74
77
|
|
75
|
-
|
78
|
+
protected
|
76
79
|
|
77
|
-
attr_reader :results
|
80
|
+
attr_reader :results # :nodoc:
|
78
81
|
|
79
|
-
|
82
|
+
private
|
80
83
|
|
81
84
|
# Is this a valid set of transitions? If the collection was creating with
|
82
85
|
# any +false+ values for transitions, then the the collection will be
|
@@ -127,7 +130,7 @@ module StateMachines
|
|
127
130
|
if transition = self[index]
|
128
131
|
throw :halt unless transition.run_callbacks(after: !skip_after) do
|
129
132
|
run_callbacks(index + 1, &block)
|
130
|
-
{result: results[transition.action], success: success?}
|
133
|
+
{ result: results[transition.action], success: success? }
|
131
134
|
end
|
132
135
|
else
|
133
136
|
persist
|
@@ -148,13 +151,13 @@ module StateMachines
|
|
148
151
|
def run_actions
|
149
152
|
catch_exceptions do
|
150
153
|
@success = if block_given?
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
154
|
+
result = yield
|
155
|
+
actions.each { |action| results[action] = result }
|
156
|
+
!!result
|
157
|
+
else
|
158
|
+
actions.compact.each { |action| !skip_actions && (results[action] = object.send(action)) }
|
159
|
+
results.values.all?
|
160
|
+
end
|
158
161
|
end
|
159
162
|
end
|
160
163
|
|
@@ -167,12 +170,10 @@ module StateMachines
|
|
167
170
|
# occur will automatically result in the transition rolling back any changes
|
168
171
|
# that were made to the object involved.
|
169
172
|
def catch_exceptions
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
raise
|
175
|
-
end
|
173
|
+
yield
|
174
|
+
rescue StandardError
|
175
|
+
rollback
|
176
|
+
raise
|
176
177
|
end
|
177
178
|
|
178
179
|
# Runs a block within a transaction for the object being transitioned. If
|
@@ -192,11 +193,11 @@ module StateMachines
|
|
192
193
|
# Represents a collection of transitions that were generated from attribute-
|
193
194
|
# based events
|
194
195
|
class AttributeTransitionCollection < TransitionCollection
|
195
|
-
def initialize(transitions = [], options = {})
|
196
|
-
super(transitions, {use_transactions: false, actions: false}.merge(options))
|
196
|
+
def initialize(transitions = [], options = {}) # :nodoc:
|
197
|
+
super(transitions, { use_transactions: false, actions: false }.merge(options))
|
197
198
|
end
|
198
199
|
|
199
|
-
|
200
|
+
private
|
200
201
|
|
201
202
|
# Hooks into running transition callbacks so that event / event transition
|
202
203
|
# attributes can be properly updated
|
@@ -212,9 +213,9 @@ module StateMachines
|
|
212
213
|
# Rollback only if exceptions occur during before callbacks
|
213
214
|
begin
|
214
215
|
super
|
215
|
-
rescue
|
216
|
+
rescue StandardError
|
216
217
|
rollback unless @before_run
|
217
|
-
@success = nil
|
218
|
+
@success = nil # mimics ActiveRecord.save behavior on rollback
|
218
219
|
raise
|
219
220
|
end
|
220
221
|
|
data/lib/state_machines.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: state_machines
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.30.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
8
8
|
- Aaron Pfeifer
|
9
|
-
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: bundler
|
@@ -26,37 +25,36 @@ dependencies:
|
|
26
25
|
- !ruby/object:Gem::Version
|
27
26
|
version: 1.7.6
|
28
27
|
- !ruby/object:Gem::Dependency
|
29
|
-
name:
|
28
|
+
name: minitest
|
30
29
|
requirement: !ruby/object:Gem::Requirement
|
31
30
|
requirements:
|
32
31
|
- - ">="
|
33
32
|
- !ruby/object:Gem::Version
|
34
|
-
version: '
|
33
|
+
version: '5.4'
|
35
34
|
type: :development
|
36
35
|
prerelease: false
|
37
36
|
version_requirements: !ruby/object:Gem::Requirement
|
38
37
|
requirements:
|
39
38
|
- - ">="
|
40
39
|
- !ruby/object:Gem::Version
|
41
|
-
version: '
|
40
|
+
version: '5.4'
|
42
41
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
42
|
+
name: rake
|
44
43
|
requirement: !ruby/object:Gem::Requirement
|
45
44
|
requirements:
|
46
45
|
- - ">="
|
47
46
|
- !ruby/object:Gem::Version
|
48
|
-
version: '
|
47
|
+
version: '0'
|
49
48
|
type: :development
|
50
49
|
prerelease: false
|
51
50
|
version_requirements: !ruby/object:Gem::Requirement
|
52
51
|
requirements:
|
53
52
|
- - ">="
|
54
53
|
- !ruby/object:Gem::Version
|
55
|
-
version: '
|
54
|
+
version: '0'
|
56
55
|
description: Adds support for creating state machines for attributes on any Ruby class
|
57
56
|
email:
|
58
57
|
- terminale@gmail.com
|
59
|
-
- aaron@pluginaweek.org
|
60
58
|
executables: []
|
61
59
|
extensions: []
|
62
60
|
extra_rdoc_files: []
|
@@ -64,7 +62,6 @@ files:
|
|
64
62
|
- LICENSE.txt
|
65
63
|
- README.md
|
66
64
|
- lib/state_machines.rb
|
67
|
-
- lib/state_machines/assertions.rb
|
68
65
|
- lib/state_machines/branch.rb
|
69
66
|
- lib/state_machines/callback.rb
|
70
67
|
- lib/state_machines/core.rb
|
@@ -79,24 +76,44 @@ files:
|
|
79
76
|
- lib/state_machines/integrations.rb
|
80
77
|
- lib/state_machines/integrations/base.rb
|
81
78
|
- lib/state_machines/machine.rb
|
79
|
+
- lib/state_machines/machine/action_hooks.rb
|
80
|
+
- lib/state_machines/machine/callbacks.rb
|
81
|
+
- lib/state_machines/machine/class_methods.rb
|
82
|
+
- lib/state_machines/machine/configuration.rb
|
83
|
+
- lib/state_machines/machine/event_methods.rb
|
84
|
+
- lib/state_machines/machine/helper_generators.rb
|
85
|
+
- lib/state_machines/machine/integration.rb
|
86
|
+
- lib/state_machines/machine/parsing.rb
|
87
|
+
- lib/state_machines/machine/rendering.rb
|
88
|
+
- lib/state_machines/machine/scoping.rb
|
89
|
+
- lib/state_machines/machine/state_methods.rb
|
90
|
+
- lib/state_machines/machine/utilities.rb
|
91
|
+
- lib/state_machines/machine/validation.rb
|
82
92
|
- lib/state_machines/machine_collection.rb
|
83
93
|
- lib/state_machines/macro_methods.rb
|
84
94
|
- lib/state_machines/matcher.rb
|
85
95
|
- lib/state_machines/matcher_helpers.rb
|
86
96
|
- lib/state_machines/node_collection.rb
|
97
|
+
- lib/state_machines/options_validator.rb
|
87
98
|
- lib/state_machines/path.rb
|
88
99
|
- lib/state_machines/path_collection.rb
|
89
100
|
- lib/state_machines/state.rb
|
90
101
|
- lib/state_machines/state_collection.rb
|
91
102
|
- lib/state_machines/state_context.rb
|
103
|
+
- lib/state_machines/stdio_renderer.rb
|
104
|
+
- lib/state_machines/syntax_validator.rb
|
105
|
+
- lib/state_machines/test_helper.rb
|
92
106
|
- lib/state_machines/transition.rb
|
93
107
|
- lib/state_machines/transition_collection.rb
|
94
108
|
- lib/state_machines/version.rb
|
95
109
|
homepage: https://github.com/state-machines/state_machines
|
96
110
|
licenses:
|
97
111
|
- MIT
|
98
|
-
metadata:
|
99
|
-
|
112
|
+
metadata:
|
113
|
+
changelog_uri: https://github.com/state-machines/state_machines/blob/master/CHANGELOG.md
|
114
|
+
homepage_uri: https://github.com/state-machines/state_machines
|
115
|
+
source_code_uri: https://github.com/state-machines/state_machines
|
116
|
+
rubygems_mfa_required: 'true'
|
100
117
|
rdoc_options: []
|
101
118
|
require_paths:
|
102
119
|
- lib
|
@@ -104,15 +121,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
121
|
requirements:
|
105
122
|
- - ">="
|
106
123
|
- !ruby/object:Gem::Version
|
107
|
-
version: 3.
|
124
|
+
version: 3.2.0
|
108
125
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
126
|
requirements:
|
110
127
|
- - ">="
|
111
128
|
- !ruby/object:Gem::Version
|
112
129
|
version: '0'
|
113
130
|
requirements: []
|
114
|
-
rubygems_version: 3.
|
115
|
-
signing_key:
|
131
|
+
rubygems_version: 3.6.9
|
116
132
|
specification_version: 4
|
117
133
|
summary: State machines for attributes
|
118
134
|
test_files: []
|
@@ -1,40 +0,0 @@
|
|
1
|
-
class Hash
|
2
|
-
# Provides a set of helper methods for making assertions about the content
|
3
|
-
# of various objects
|
4
|
-
|
5
|
-
unless respond_to?(:assert_valid_keys)
|
6
|
-
# Validate all keys in a hash match <tt>*valid_keys</tt>, raising ArgumentError
|
7
|
-
# on a mismatch. Note that keys are NOT treated indifferently, meaning if you
|
8
|
-
# use strings for keys but assert symbols as keys, this will fail.
|
9
|
-
#
|
10
|
-
# { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: :years. Valid keys are: :name, :age"
|
11
|
-
# { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: :name. Valid keys are: 'name', 'age'"
|
12
|
-
# { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing
|
13
|
-
# Code from ActiveSupport
|
14
|
-
def assert_valid_keys(*valid_keys)
|
15
|
-
valid_keys.flatten!
|
16
|
-
each_key do |k|
|
17
|
-
unless valid_keys.include?(k)
|
18
|
-
raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}")
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
# Validates that the given hash only includes at *most* one of a set of
|
25
|
-
# exclusive keys. If more than one key is found, an ArgumentError will be
|
26
|
-
# raised.
|
27
|
-
#
|
28
|
-
# == Examples
|
29
|
-
#
|
30
|
-
# options = {:only => :on, :except => :off}
|
31
|
-
# options.assert_exclusive_keys(:only) # => nil
|
32
|
-
# options.assert_exclusive_keys(:except) # => nil
|
33
|
-
# options.assert_exclusive_keys(:only, :except) # => ArgumentError: Conflicting keys: only, except
|
34
|
-
# options.assert_exclusive_keys(:only, :except, :with) # => ArgumentError: Conflicting keys: only, except
|
35
|
-
def assert_exclusive_keys(*exclusive_keys)
|
36
|
-
conflicting_keys = exclusive_keys & keys
|
37
|
-
raise ArgumentError, "Conflicting keys: #{conflicting_keys.join(', ')}" unless conflicting_keys.length <= 1
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|