state_machine 1.0.0 → 1.0.1

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  == master
2
2
 
3
+ == 1.0.1 / 2011-05-30
4
+
5
+ * Add the ability to ignore method conflicts for helpers
6
+ * Generate warnings for any helper, not just state helpers, that has a conflicting method defined in the class
7
+ * Fix scopes in Sequel not working if the table name contains double underscores or is not a string/symbol
8
+ * Add full support for chaining state scopes within Sequel integrations
9
+ * Fix Rails 3.1 deprecation warnings for configuring engine locales [Stefan Penner]
10
+
3
11
  == 1.0.0 / 2011-05-12
4
12
 
5
13
  * Celebrate
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'rake/gempackagetask'
6
6
 
7
7
  spec = Gem::Specification.new do |s|
8
8
  s.name = 'state_machine'
9
- s.version = '1.0.0'
9
+ s.version = '1.0.1'
10
10
  s.platform = Gem::Platform::RUBY
11
11
  s.summary = 'Adds support for creating state machines for attributes on any Ruby class'
12
12
  s.description = s.summary
@@ -13,7 +13,11 @@ if defined?(Rails)
13
13
  end
14
14
  end
15
15
 
16
- StateMachine::RailsEngine.paths.config.locales = locale_paths
16
+ if Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR == 0
17
+ StateMachine::RailsEngine.paths.config.locales = locale_paths
18
+ else
19
+ StateMachine::RailsEngine.paths['config/locales'] = locale_paths
20
+ end
17
21
  elsif defined?(I18n)
18
22
  # Rails 2.x
19
23
  I18n.load_path.unshift(*locale_paths)
@@ -416,8 +416,8 @@ module StateMachine
416
416
  # ActiveModel's use of method_missing / respond_to for attribute methods
417
417
  # breaks both ancestor lookups and defined?(super). Need to special-case
418
418
  # the existence of query attribute methods.
419
- def owner_class_ancestor_has_method?(method)
420
- method == "#{name}?" || super
419
+ def owner_class_ancestor_has_method?(scope, method)
420
+ scope == :instance && method == "#{name}?" || super
421
421
  end
422
422
  end
423
423
  end
@@ -264,8 +264,8 @@ module StateMachine
264
264
  # ActiveModel's use of method_missing / respond_to for attribute methods
265
265
  # breaks both ancestor lookups and defined?(super). Need to special-case
266
266
  # the existence of query attribute methods.
267
- def owner_class_ancestor_has_method?(method)
268
- method == "#{name}?" || super
267
+ def owner_class_ancestor_has_method?(scope, method)
268
+ scope == :instance && method == "#{name}?" || super
269
269
  end
270
270
  end
271
271
  end
@@ -349,8 +349,8 @@ module StateMachine
349
349
  # ActiveModel's use of method_missing / respond_to for attribute methods
350
350
  # breaks both ancestor lookups and defined?(super). Need to special-case
351
351
  # the existence of query attribute methods.
352
- def owner_class_ancestor_has_method?(method)
353
- method == "#{name}?" || super
352
+ def owner_class_ancestor_has_method?(scope, method)
353
+ scope == :instance && method == "#{name}?" || super
354
354
  end
355
355
  end
356
356
  end
@@ -26,6 +26,10 @@ module StateMachine
26
26
  def action_hook
27
27
  action == :save ? :save : super
28
28
  end
29
+
30
+ def model_from_dataset(dataset)
31
+ dataset.model_classes[nil]
32
+ end
29
33
  end
30
34
  end
31
35
  end
@@ -374,13 +374,39 @@ module StateMachine
374
374
  # Creates a scope for finding records *with* a particular state or
375
375
  # states for the attribute
376
376
  def create_with_scope(name)
377
- lambda {|model, values| model.filter(:"#{owner_class.table_name}__#{attribute}" => values)}
377
+ create_scope(name, lambda {|dataset, values| dataset.filter(attribute_column => values)})
378
378
  end
379
379
 
380
380
  # Creates a scope for finding records *without* a particular state or
381
381
  # states for the attribute
382
382
  def create_without_scope(name)
383
- lambda {|model, values| model.filter(~{:"#{owner_class.table_name}__#{attribute}" => values})}
383
+ create_scope(name, lambda {|dataset, values| dataset.exclude(attribute_column => values)})
384
+ end
385
+
386
+ # Creates a new named scope with the given name
387
+ def create_scope(name, scope)
388
+ machine = self
389
+ owner_class.def_dataset_method(name) do |*states|
390
+ machine.send(:run_scope, scope, self, states)
391
+ end
392
+
393
+ false
394
+ end
395
+
396
+ # Generates the results for the given scope based on one or more states to
397
+ # filter by
398
+ def run_scope(scope, dataset, states)
399
+ super(scope, model_from_dataset(dataset).state_machine(name), dataset, states)
400
+ end
401
+
402
+ # Determines the model associated with the given dataset
403
+ def model_from_dataset(dataset)
404
+ dataset.model
405
+ end
406
+
407
+ # Generates the fully-qualifed column name for this machine's attribute
408
+ def attribute_column
409
+ ::Sequel::SQL::QualifiedIdentifier.new(owner_class.table_name, attribute)
384
410
  end
385
411
 
386
412
  # Runs a new database transaction, rolling back any changes if the
@@ -376,6 +376,11 @@ module StateMachine
376
376
  :invalid_transition => 'cannot transition via "%s"'
377
377
  }
378
378
 
379
+ # Whether to ignore any conflicts that are detected for helper methods that
380
+ # get generated for a machine's owner class. Default is false.
381
+ class << self; attr_accessor :ignore_method_conflicts; end
382
+ @ignore_method_conflicts = false
383
+
379
384
  # The class that the machine is defined in
380
385
  attr_accessor :owner_class
381
386
 
@@ -583,6 +588,11 @@ module StateMachine
583
588
  # name. If the method is already defined in the scope, then this will not
584
589
  # override it.
585
590
  #
591
+ # If passing in a block, there are two side effects to be aware of
592
+ # 1. The method cannot be chained, meaning that the block cannot call +super+
593
+ # 2. If the method is already defined in an ancestor, then it will not get
594
+ # overridden and a warning will be output.
595
+ #
586
596
  # Example:
587
597
  #
588
598
  # # Instance helper
@@ -611,15 +621,22 @@ module StateMachine
611
621
  # end
612
622
  # end_eval
613
623
  def define_helper(scope, method, *args, &block)
624
+ helper_module = @helper_modules.fetch(scope)
625
+
614
626
  if block_given?
615
- name = self.name
616
- @helper_modules.fetch(scope).class_eval do
617
- define_method(method) do |*args|
618
- block.call((scope == :instance ? self.class : self).state_machine(name), self, *args)
627
+ if !self.class.ignore_method_conflicts && conflicting_ancestor = owner_class_ancestor_has_method?(scope, method)
628
+ ancestor_name = conflicting_ancestor.name && !conflicting_ancestor.name.empty? ? conflicting_ancestor.name : conflicting_ancestor.to_s
629
+ warn "#{scope == :class ? 'Class' : 'Instance'} method \"#{method}\" is already defined in #{ancestor_name}, use generic helper instead."
630
+ else
631
+ name = self.name
632
+ helper_module.class_eval do
633
+ define_method(method) do |*args|
634
+ block.call((scope == :instance ? self.class : self).state_machine(name), self, *args)
635
+ end
619
636
  end
620
637
  end
621
638
  else
622
- @helper_modules.fetch(scope).class_eval(method, *args)
639
+ helper_module.class_eval(method, *args)
623
640
  end
624
641
  end
625
642
 
@@ -1585,13 +1602,14 @@ module StateMachine
1585
1602
  def define_state_accessor
1586
1603
  attribute = self.attribute
1587
1604
 
1588
- @helper_modules[:instance].class_eval { attr_accessor attribute }
1605
+ @helper_modules[:instance].class_eval { attr_reader attribute } unless owner_class_ancestor_has_method?(:instance, attribute)
1606
+ @helper_modules[:instance].class_eval { attr_writer attribute } unless owner_class_ancestor_has_method?(:instance, "#{attribute}=")
1589
1607
  end
1590
1608
 
1591
1609
  # Adds predicate method to the owner class for determining the name of the
1592
1610
  # current state
1593
1611
  def define_state_predicate
1594
- call_super = owner_class_ancestor_has_method?("#{name}?")
1612
+ call_super = !!owner_class_ancestor_has_method?(:instance, "#{name}?")
1595
1613
  define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
1596
1614
  def #{name}?(*args)
1597
1615
  args.empty? && #{call_super} ? super : self.class.state_machine(#{name.inspect}).states.matches?(self, *args)
@@ -1686,14 +1704,35 @@ module StateMachine
1686
1704
  # action must be defined in an ancestor of the owner classs in order for
1687
1705
  # it to be the action hook.
1688
1706
  def action_hook
1689
- action && owner_class_ancestor_has_method?(action) ? action : nil
1707
+ action && owner_class_ancestor_has_method?(:instance, action) ? action : nil
1690
1708
  end
1691
1709
 
1692
- # Determines whether any of the ancestors for this machine's owner class
1693
- # has the given method defined, even if it's private.
1694
- def owner_class_ancestor_has_method?(method)
1695
- owner_class.ancestors.any? do |ancestor|
1696
- ancestor != owner_class && (ancestor.method_defined?(method) || ancestor.private_method_defined?(method))
1710
+ # Determines whether there's already a helper method defined within the
1711
+ # given scope. This is true only if one of the owner's ancestors defines
1712
+ # the method and is further along in the ancestor chain than this
1713
+ # machine's helper module.
1714
+ def owner_class_ancestor_has_method?(scope, method)
1715
+ superclasses = owner_class.ancestors[1..-1].select {|ancestor| ancestor.is_a?(Class)}
1716
+
1717
+ if scope == :class
1718
+ # Use singleton classes
1719
+ current = (class << owner_class; self; end)
1720
+ superclass = superclasses.first
1721
+ else
1722
+ current = owner_class
1723
+ superclass = owner_class.superclass
1724
+ end
1725
+
1726
+ # Generate the list of modules that *only* occur in the owner class, but
1727
+ # were included *prior* to the helper modules, in addition to the
1728
+ # superclasses
1729
+ ancestors = current.ancestors - superclass.ancestors + superclasses
1730
+ ancestors = ancestors[ancestors.index(@helper_modules[scope]) + 1..-1].reverse
1731
+
1732
+ # Search for for the first ancestor that defined this method
1733
+ ancestors.detect do |ancestor|
1734
+ ancestor = (class << ancestor; self; end) if scope == :class && ancestor.is_a?(Class)
1735
+ ancestor.method_defined?(method) || ancestor.private_method_defined?(method)
1697
1736
  end
1698
1737
  end
1699
1738
 
@@ -1737,14 +1776,20 @@ module StateMachine
1737
1776
  # Converts state names to their corresponding values so that they
1738
1777
  # can be looked up properly
1739
1778
  define_helper(:class, method) do |machine, klass, *states|
1740
- values = states.flatten.map {|state| machine.states.fetch(state).value}
1741
- scope.call(klass, values)
1779
+ run_scope(scope, machine, klass, states)
1742
1780
  end
1743
1781
  end
1744
1782
  end
1745
1783
  end
1746
1784
  end
1747
1785
 
1786
+ # Generates the results for the given scope based on one or more states to
1787
+ # filter by
1788
+ def run_scope(scope, machine, klass, states)
1789
+ values = states.flatten.map {|state| machine.states.fetch(state).value}
1790
+ scope.call(klass, values)
1791
+ end
1792
+
1748
1793
  # Pluralizes the given word using #pluralize (if available) or simply
1749
1794
  # adding an "s" to the end of the word
1750
1795
  def pluralize(word)
@@ -260,19 +260,11 @@ module StateMachine
260
260
  end
261
261
 
262
262
  # Adds a predicate method to the owner class so long as a name has
263
- # actually been configured for the state and the method isn't already
264
- # defined in the owner class.
263
+ # actually been configured for the state
265
264
  def add_predicate
266
- owner_class = machine.owner_class
267
- predicate = "#{qualified_name}?"
268
- if !owner_class.method_defined?(predicate) && !owner_class.private_method_defined?(predicate)
269
- # Checks whether the current value matches this state
270
- machine.define_helper(:instance, predicate) do |machine, object|
271
- machine.states.matches?(object, name)
272
- end
273
- else
274
- # Only output a warning since we can't defined the predicate
275
- warn "#{owner_class.name}##{predicate} is already defined, use #{owner_class.name}##{machine.name}?(:#{name}) instead."
265
+ # Checks whether the current value matches this state
266
+ machine.define_helper(:instance, "#{qualified_name}?") do |machine, object|
267
+ machine.states.matches?(object, name)
276
268
  end
277
269
  end
278
270
  end
@@ -127,8 +127,68 @@ class EventWithDynamicHumanNameTest < Test::Unit::TestCase
127
127
  end
128
128
  end
129
129
 
130
- class EventWithConflictingHelpersTest < Test::Unit::TestCase
130
+ class EventWithConflictingHelpersBeforeDefinitionTest < Test::Unit::TestCase
131
131
  def setup
132
+ require 'stringio'
133
+ @original_stderr, $stderr = $stderr, StringIO.new
134
+
135
+ @superclass = Class.new do
136
+ def can_ignite?
137
+ 0
138
+ end
139
+
140
+ def ignite_transition
141
+ 0
142
+ end
143
+
144
+ def ignite
145
+ 0
146
+ end
147
+
148
+ def ignite!
149
+ 0
150
+ end
151
+ end
152
+ @klass = Class.new(@superclass)
153
+ @machine = StateMachine::Machine.new(@klass)
154
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite)
155
+ @object = @klass.new
156
+ end
157
+
158
+ def test_should_not_redefine_predicate
159
+ assert_equal 0, @object.can_ignite?
160
+ end
161
+
162
+ def test_should_not_redefine_transition_accessor
163
+ assert_equal 0, @object.ignite_transition
164
+ end
165
+
166
+ def test_should_not_redefine_action
167
+ assert_equal 0, @object.ignite
168
+ end
169
+
170
+ def test_should_not_redefine_bang_action
171
+ assert_equal 0, @object.ignite!
172
+ end
173
+
174
+ def test_should_output_warning
175
+ expected = %w(can_ignite? ignite_transition ignite ignite!).map do |method|
176
+ "Instance method \"#{method}\" is already defined in #{@superclass.to_s}, use generic helper instead.\n"
177
+ end.join
178
+
179
+ assert_equal expected, $stderr.string
180
+ end
181
+
182
+ def teardown
183
+ $stderr = @original_stderr
184
+ end
185
+ end
186
+
187
+ class EventWithConflictingHelpersAfterDefinitionTest < Test::Unit::TestCase
188
+ def setup
189
+ require 'stringio'
190
+ @original_stderr, $stderr = $stderr, StringIO.new
191
+
132
192
  @klass = Class.new do
133
193
  def can_ignite?
134
194
  0
@@ -170,31 +230,34 @@ class EventWithConflictingHelpersTest < Test::Unit::TestCase
170
230
  def test_should_allow_super_chaining
171
231
  @klass.class_eval do
172
232
  def can_ignite?
173
- super ? 1 : 0
233
+ super
174
234
  end
175
235
 
176
236
  def ignite_transition
177
- super ? 1 : 0
237
+ super
178
238
  end
179
239
 
180
240
  def ignite
181
- super ? 1 : 0
241
+ super
182
242
  end
183
243
 
184
244
  def ignite!
185
- begin
186
- super
187
- 1
188
- rescue Exception => ex
189
- 0
190
- end
245
+ super
191
246
  end
192
247
  end
193
248
 
194
- assert_equal 0, @object.can_ignite?
195
- assert_equal 0, @object.ignite_transition
196
- assert_equal 0, @object.ignite
197
- assert_equal 1, @object.ignite!
249
+ assert_equal false, @object.can_ignite?
250
+ assert_equal nil, @object.ignite_transition
251
+ assert_equal false, @object.ignite
252
+ assert_raise(StateMachine::InvalidTransition) { @object.ignite! }
253
+ end
254
+
255
+ def test_should_not_output_warning
256
+ assert_equal '', $stderr.string
257
+ end
258
+
259
+ def teardown
260
+ $stderr = @original_stderr
198
261
  end
199
262
  end
200
263
 
@@ -20,11 +20,11 @@ module SequelTest
20
20
  def new_model(create_table = :foo, &block)
21
21
  table_name = create_table || :foo
22
22
 
23
- DB.create_table!(table_name) do
23
+ DB.create_table!(::Sequel::SQL::Identifier.new(table_name)) do
24
24
  primary_key :id
25
25
  column :state, :string
26
26
  end if create_table
27
- model = Class.new(Sequel::Model(table_name)) do
27
+ model = Class.new(Sequel::Model(DB[::Sequel::SQL::Identifier.new(table_name)])) do
28
28
  self.raise_on_save_failure = false
29
29
  (class << self; self; end).class_eval do
30
30
  define_method(:name) { "SequelTest::#{table_name.to_s.capitalize}" }
@@ -1426,7 +1426,19 @@ module SequelTest
1426
1426
  parked = @model.create :state => 'parked'
1427
1427
  idling = @model.create :state => 'idling'
1428
1428
 
1429
- assert_equal [idling], @model.without_state(:parked).filter(:state => 'idling').all
1429
+ assert_equal [idling], @model.without_state(:parked).with_state(:idling).all
1430
+ end
1431
+
1432
+ def test_should_run_on_tables_with_double_underscores
1433
+ @model = new_model(:foo__bar)
1434
+ @machine = StateMachine::Machine.new(@model)
1435
+ @machine.state :parked, :first_gear
1436
+ @machine.state :idling, :value => lambda {'idling'}
1437
+
1438
+ parked = @model.create :state => 'parked'
1439
+ idling = @model.create :state => 'idling'
1440
+
1441
+ assert_equal [parked], @model.with_state(:parked).all
1430
1442
  end
1431
1443
  end
1432
1444
 
@@ -1114,6 +1114,104 @@ class MachineWithInstanceHelpersTest < Test::Unit::TestCase
1114
1114
  assert_equal 'parked', @object.send(:state)
1115
1115
  end
1116
1116
 
1117
+ def test_should_warn_if_defined_in_superclass
1118
+ require 'stringio'
1119
+ @original_stderr, $stderr = $stderr, StringIO.new
1120
+
1121
+ superclass = Class.new do
1122
+ def park
1123
+ end
1124
+ end
1125
+ klass = Class.new(superclass)
1126
+ machine = StateMachine::Machine.new(klass)
1127
+
1128
+ machine.define_helper(:instance, :park) {}
1129
+ assert_equal "Instance method \"park\" is already defined in #{superclass.to_s}, use generic helper instead.\n", $stderr.string
1130
+ ensure
1131
+ $stderr = @original_stderr
1132
+ end
1133
+
1134
+ def test_should_warn_if_defined_in_multiple_superclasses
1135
+ require 'stringio'
1136
+ @original_stderr, $stderr = $stderr, StringIO.new
1137
+
1138
+ superclass1 = Class.new do
1139
+ def park
1140
+ end
1141
+ end
1142
+ superclass2 = Class.new(superclass1) do
1143
+ def park
1144
+ end
1145
+ end
1146
+ klass = Class.new(superclass2)
1147
+ machine = StateMachine::Machine.new(klass)
1148
+
1149
+ machine.define_helper(:instance, :park) {}
1150
+ assert_equal "Instance method \"park\" is already defined in #{superclass1.to_s}, use generic helper instead.\n", $stderr.string
1151
+ ensure
1152
+ $stderr = @original_stderr
1153
+ end
1154
+
1155
+ def test_should_warn_if_defined_in_module_prior_to_helper_module
1156
+ require 'stringio'
1157
+ @original_stderr, $stderr = $stderr, StringIO.new
1158
+
1159
+ mod = Module.new do
1160
+ def park
1161
+ end
1162
+ end
1163
+ klass = Class.new do
1164
+ include mod
1165
+ end
1166
+ machine = StateMachine::Machine.new(klass)
1167
+
1168
+ machine.define_helper(:instance, :park) {}
1169
+ assert_equal "Instance method \"park\" is already defined in #{mod.to_s}, use generic helper instead.\n", $stderr.string
1170
+ ensure
1171
+ $stderr = @original_stderr
1172
+ end
1173
+
1174
+ def test_should_not_warn_if_defined_in_module_after_helper_module
1175
+ require 'stringio'
1176
+ @original_stderr, $stderr = $stderr, StringIO.new
1177
+
1178
+ klass = Class.new
1179
+ machine = StateMachine::Machine.new(klass)
1180
+
1181
+ mod = Module.new do
1182
+ def park
1183
+ end
1184
+ end
1185
+ klass.class_eval do
1186
+ include mod
1187
+ end
1188
+
1189
+ machine.define_helper(:instance, :park) {}
1190
+ assert_equal '', $stderr.string
1191
+ ensure
1192
+ $stderr = @original_stderr
1193
+ end
1194
+
1195
+ def test_should_define_if_ignoring_method_conflicts_and_defined_in_superclass
1196
+ require 'stringio'
1197
+ @original_stderr, $stderr = $stderr, StringIO.new
1198
+ StateMachine::Machine.ignore_method_conflicts = true
1199
+
1200
+ superclass = Class.new do
1201
+ def park
1202
+ end
1203
+ end
1204
+ klass = Class.new(superclass)
1205
+ machine = StateMachine::Machine.new(klass)
1206
+
1207
+ machine.define_helper(:instance, :park) {true}
1208
+ assert_equal '', $stderr.string
1209
+ assert_equal true, klass.new.park
1210
+ ensure
1211
+ StateMachine::Machine.ignore_method_conflicts = false
1212
+ $stderr = @original_stderr
1213
+ end
1214
+
1117
1215
  def test_should_define_nonexistent_methods
1118
1216
  @machine.define_helper(:instance, :state) {'parked'}
1119
1217
  assert_equal 'parked', @object.state
@@ -1186,6 +1284,104 @@ class MachineWithClassHelpersTest < Test::Unit::TestCase
1186
1284
  assert_equal [], @klass.send(:states)
1187
1285
  end
1188
1286
 
1287
+ def test_should_warn_if_defined_in_superclass
1288
+ require 'stringio'
1289
+ @original_stderr, $stderr = $stderr, StringIO.new
1290
+
1291
+ superclass = Class.new do
1292
+ def self.park
1293
+ end
1294
+ end
1295
+ klass = Class.new(superclass)
1296
+ machine = StateMachine::Machine.new(klass)
1297
+
1298
+ machine.define_helper(:class, :park) {}
1299
+ assert_equal "Class method \"park\" is already defined in #{superclass.to_s}, use generic helper instead.\n", $stderr.string
1300
+ ensure
1301
+ $stderr = @original_stderr
1302
+ end
1303
+
1304
+ def test_should_warn_if_defined_in_multiple_superclasses
1305
+ require 'stringio'
1306
+ @original_stderr, $stderr = $stderr, StringIO.new
1307
+
1308
+ superclass1 = Class.new do
1309
+ def self.park
1310
+ end
1311
+ end
1312
+ superclass2 = Class.new(superclass1) do
1313
+ def self.park
1314
+ end
1315
+ end
1316
+ klass = Class.new(superclass2)
1317
+ machine = StateMachine::Machine.new(klass)
1318
+
1319
+ machine.define_helper(:class, :park) {}
1320
+ assert_equal "Class method \"park\" is already defined in #{superclass1.to_s}, use generic helper instead.\n", $stderr.string
1321
+ ensure
1322
+ $stderr = @original_stderr
1323
+ end
1324
+
1325
+ def test_should_warn_if_defined_in_module_prior_to_helper_module
1326
+ require 'stringio'
1327
+ @original_stderr, $stderr = $stderr, StringIO.new
1328
+
1329
+ mod = Module.new do
1330
+ def park
1331
+ end
1332
+ end
1333
+ klass = Class.new do
1334
+ extend mod
1335
+ end
1336
+ machine = StateMachine::Machine.new(klass)
1337
+
1338
+ machine.define_helper(:class, :park) {}
1339
+ assert_equal "Class method \"park\" is already defined in #{mod.to_s}, use generic helper instead.\n", $stderr.string
1340
+ ensure
1341
+ $stderr = @original_stderr
1342
+ end
1343
+
1344
+ def test_should_not_warn_if_defined_in_module_after_helper_module
1345
+ require 'stringio'
1346
+ @original_stderr, $stderr = $stderr, StringIO.new
1347
+
1348
+ klass = Class.new
1349
+ machine = StateMachine::Machine.new(klass)
1350
+
1351
+ mod = Module.new do
1352
+ def park
1353
+ end
1354
+ end
1355
+ klass.class_eval do
1356
+ extend mod
1357
+ end
1358
+
1359
+ machine.define_helper(:class, :park) {}
1360
+ assert_equal '', $stderr.string
1361
+ ensure
1362
+ $stderr = @original_stderr
1363
+ end
1364
+
1365
+ def test_should_define_if_ignoring_method_conflicts_and_defined_in_superclass
1366
+ require 'stringio'
1367
+ @original_stderr, $stderr = $stderr, StringIO.new
1368
+ StateMachine::Machine.ignore_method_conflicts = true
1369
+
1370
+ superclass = Class.new do
1371
+ def self.park
1372
+ end
1373
+ end
1374
+ klass = Class.new(superclass)
1375
+ machine = StateMachine::Machine.new(klass)
1376
+
1377
+ machine.define_helper(:class, :park) {true}
1378
+ assert_equal '', $stderr.string
1379
+ assert_equal true, klass.park
1380
+ ensure
1381
+ StateMachine::Machine.ignore_method_conflicts = false
1382
+ $stderr = @original_stderr
1383
+ end
1384
+
1189
1385
  def test_should_define_nonexistent_methods
1190
1386
  @machine.define_helper(:class, :states) {[]}
1191
1387
  assert_equal [], @klass.states
@@ -1217,8 +1413,176 @@ class MachineWithClassHelpersTest < Test::Unit::TestCase
1217
1413
  end
1218
1414
  end
1219
1415
 
1220
- class MachineWithConflictingHelpersTest < Test::Unit::TestCase
1416
+ class MachineWithConflictingHelpersBeforeDefinitionTest < Test::Unit::TestCase
1417
+ def setup
1418
+ require 'stringio'
1419
+ @original_stderr, $stderr = $stderr, StringIO.new
1420
+
1421
+ @superclass = Class.new do
1422
+ def self.with_state
1423
+ :with_state
1424
+ end
1425
+
1426
+ def self.with_states
1427
+ :with_states
1428
+ end
1429
+
1430
+ def self.without_state
1431
+ :without_state
1432
+ end
1433
+
1434
+ def self.without_states
1435
+ :without_states
1436
+ end
1437
+
1438
+ def self.human_state_name
1439
+ :human_state_name
1440
+ end
1441
+
1442
+ def self.human_state_event_name
1443
+ :human_state_event_name
1444
+ end
1445
+
1446
+ attr_accessor :status
1447
+
1448
+ def state
1449
+ 'parked'
1450
+ end
1451
+
1452
+ def state=(value)
1453
+ self.status = value
1454
+ end
1455
+
1456
+ def state?
1457
+ true
1458
+ end
1459
+
1460
+ def state_name
1461
+ :parked
1462
+ end
1463
+
1464
+ def human_state_name
1465
+ 'parked'
1466
+ end
1467
+
1468
+ def state_events
1469
+ [:ignite]
1470
+ end
1471
+
1472
+ def state_transitions
1473
+ [{:parked => :idling}]
1474
+ end
1475
+
1476
+ def state_paths
1477
+ [[{:parked => :idling}]]
1478
+ end
1479
+ end
1480
+ @klass = Class.new(@superclass)
1481
+
1482
+ StateMachine::Integrations.const_set('Custom', Module.new do
1483
+ include StateMachine::Integrations::Base
1484
+
1485
+ def create_with_scope(name)
1486
+ lambda {|klass, values| []}
1487
+ end
1488
+
1489
+ def create_without_scope(name)
1490
+ lambda {|klass, values| []}
1491
+ end
1492
+ end)
1493
+
1494
+ @machine = StateMachine::Machine.new(@klass, :integration => :custom)
1495
+ @machine.state :parked, :idling
1496
+ @machine.event :ignite
1497
+ @object = @klass.new
1498
+ end
1499
+
1500
+ def test_should_not_redefine_singular_with_scope
1501
+ assert_equal :with_state, @klass.with_state
1502
+ end
1503
+
1504
+ def test_should_not_redefine_plural_with_scope
1505
+ assert_equal :with_states, @klass.with_states
1506
+ end
1507
+
1508
+ def test_should_not_redefine_singular_without_scope
1509
+ assert_equal :without_state, @klass.without_state
1510
+ end
1511
+
1512
+ def test_should_not_redefine_plural_without_scope
1513
+ assert_equal :without_states, @klass.without_states
1514
+ end
1515
+
1516
+ def test_should_not_redefine_human_attribute_name_reader
1517
+ assert_equal :human_state_name, @klass.human_state_name
1518
+ end
1519
+
1520
+ def test_should_not_redefine_human_event_name_reader
1521
+ assert_equal :human_state_event_name, @klass.human_state_event_name
1522
+ end
1523
+
1524
+ def test_should_not_redefine_attribute_writer
1525
+ assert_equal 'parked', @object.state
1526
+ end
1527
+
1528
+ def test_should_not_redefine_attribute_writer
1529
+ @object.state = 'parked'
1530
+ assert_equal 'parked', @object.status
1531
+ end
1532
+
1533
+ def test_should_not_define_attribute_predicate
1534
+ assert @object.state?
1535
+ end
1536
+
1537
+ def test_should_not_redefine_attribute_name_reader
1538
+ assert_equal :parked, @object.state_name
1539
+ end
1540
+
1541
+ def test_should_not_redefine_attribute_human_name_reader
1542
+ assert_equal 'parked', @object.human_state_name
1543
+ end
1544
+
1545
+ def test_should_not_redefine_attribute_events_reader
1546
+ assert_equal [:ignite], @object.state_events
1547
+ end
1548
+
1549
+ def test_should_not_redefine_attribute_transitions_reader
1550
+ assert_equal [{:parked => :idling}], @object.state_transitions
1551
+ end
1552
+
1553
+ def test_should_not_redefine_attribute_paths_reader
1554
+ assert_equal [[{:parked => :idling}]], @object.state_paths
1555
+ end
1556
+
1557
+ def test_should_output_warning
1558
+ expected = [
1559
+ 'Instance method "state_events"',
1560
+ 'Instance method "state_transitions"',
1561
+ 'Instance method "state_paths"',
1562
+ 'Class method "human_state_name"',
1563
+ 'Class method "human_state_event_name"',
1564
+ 'Instance method "state_name"',
1565
+ 'Instance method "human_state_name"',
1566
+ 'Class method "with_state"',
1567
+ 'Class method "without_state"',
1568
+ 'Class method "with_states"',
1569
+ 'Class method "without_states"'
1570
+ ].map {|method| "#{method} is already defined in #{@superclass.to_s}, use generic helper instead.\n"}.join
1571
+
1572
+ assert_equal expected, $stderr.string
1573
+ end
1574
+
1575
+ def teardown
1576
+ $stderr = @original_stderr
1577
+ StateMachine::Integrations.send(:remove_const, 'Custom')
1578
+ end
1579
+ end
1580
+
1581
+ class MachineWithConflictingHelpersAfterDefinitionTest < Test::Unit::TestCase
1221
1582
  def setup
1583
+ require 'stringio'
1584
+ @original_stderr, $stderr = $stderr, StringIO.new
1585
+
1222
1586
  @klass = Class.new do
1223
1587
  def self.with_state
1224
1588
  :with_state
@@ -1357,84 +1721,89 @@ class MachineWithConflictingHelpersTest < Test::Unit::TestCase
1357
1721
  def test_should_allow_super_chaining
1358
1722
  @klass.class_eval do
1359
1723
  def self.with_state(*states)
1360
- super == []
1724
+ super
1361
1725
  end
1362
1726
 
1363
1727
  def self.with_states(*states)
1364
- super == []
1728
+ super
1365
1729
  end
1366
1730
 
1367
1731
  def self.without_state(*states)
1368
- super == []
1732
+ super
1369
1733
  end
1370
1734
 
1371
1735
  def self.without_states(*states)
1372
- super == []
1736
+ super
1373
1737
  end
1374
1738
 
1375
1739
  def self.human_state_name(state)
1376
- super == 'parked'
1740
+ super
1377
1741
  end
1378
1742
 
1379
1743
  def self.human_state_event_name(event)
1380
- super == 'ignite'
1744
+ super
1381
1745
  end
1382
1746
 
1383
1747
  attr_accessor :status
1384
1748
 
1385
1749
  def state
1386
- super || 'parked'
1750
+ super
1387
1751
  end
1388
1752
 
1389
1753
  def state=(value)
1390
1754
  super
1391
- self.status = value
1392
1755
  end
1393
1756
 
1394
1757
  def state?(state)
1395
- super ? 1 : 0
1758
+ super
1396
1759
  end
1397
1760
 
1398
1761
  def state_name
1399
- super == :parked ? 1 : 0
1762
+ super
1400
1763
  end
1401
1764
 
1402
1765
  def human_state_name
1403
- super == 'parked' ? 1 : 0
1766
+ super
1404
1767
  end
1405
1768
 
1406
1769
  def state_events
1407
- super == []
1770
+ super
1408
1771
  end
1409
1772
 
1410
1773
  def state_transitions
1411
- super == []
1774
+ super
1412
1775
  end
1413
1776
 
1414
1777
  def state_paths
1415
- super == []
1778
+ super
1416
1779
  end
1417
1780
  end
1418
1781
 
1419
- assert_equal true, @klass.with_state
1420
- assert_equal true, @klass.with_states
1421
- assert_equal true, @klass.without_state
1422
- assert_equal true, @klass.without_states
1423
- assert_equal true, @klass.human_state_name(:parked)
1424
- assert_equal true, @klass.human_state_event_name(:ignite)
1782
+ assert_equal [], @klass.with_state
1783
+ assert_equal [], @klass.with_states
1784
+ assert_equal [], @klass.without_state
1785
+ assert_equal [], @klass.without_states
1786
+ assert_equal 'parked', @klass.human_state_name(:parked)
1787
+ assert_equal 'ignite', @klass.human_state_event_name(:ignite)
1425
1788
 
1426
- assert_equal 'parked', @object.state
1789
+ assert_equal nil, @object.state
1427
1790
  @object.state = 'idling'
1428
- assert_equal 'idling', @object.status
1429
- assert_equal 0, @object.state?(:parked)
1430
- assert_equal 0, @object.state_name
1431
- assert_equal 0, @object.human_state_name
1432
- assert_equal true, @object.state_events
1433
- assert_equal true, @object.state_transitions
1434
- assert_equal true, @object.state_paths
1791
+ assert_equal 'idling', @object.state
1792
+ assert_equal nil, @object.status
1793
+ assert_equal false, @object.state?(:parked)
1794
+ assert_equal :idling, @object.state_name
1795
+ assert_equal 'idling', @object.human_state_name
1796
+ assert_equal [], @object.state_events
1797
+ assert_equal [], @object.state_transitions
1798
+ assert_equal [], @object.state_paths
1799
+ end
1800
+
1801
+ def test_should_not_output_warning
1802
+ assert_equal '', $stderr.string
1435
1803
  end
1436
1804
 
1437
1805
  def teardown
1806
+ $stderr = @original_stderr
1438
1807
  StateMachine::Integrations.send(:remove_const, 'Custom')
1439
1808
  end
1440
1809
  end
@@ -458,7 +458,36 @@ class StateNotFinalTest < Test::Unit::TestCase
458
458
  end
459
459
  end
460
460
 
461
- class StateWithConflictingHelpersTest < Test::Unit::TestCase
461
+ class StateWithConflictingHelpersBeforeDefinitionTest < Test::Unit::TestCase
462
+ def setup
463
+ require 'stringio'
464
+ @original_stderr, $stderr = $stderr, StringIO.new
465
+
466
+ @superclass = Class.new do
467
+ def parked?
468
+ 0
469
+ end
470
+ end
471
+ @klass = Class.new(@superclass)
472
+ @machine = StateMachine::Machine.new(@klass)
473
+ @machine.state :parked
474
+ @object = @klass.new
475
+ end
476
+
477
+ def test_should_not_override_state_predicate
478
+ assert_equal 0, @object.parked?
479
+ end
480
+
481
+ def test_should_output_warning
482
+ assert_equal "Instance method \"parked?\" is already defined in #{@superclass.to_s}, use generic helper instead.\n", $stderr.string
483
+ end
484
+
485
+ def teardown
486
+ $stderr = @original_stderr
487
+ end
488
+ end
489
+
490
+ class StateWithConflictingHelpersAfterDefinitionTest < Test::Unit::TestCase
462
491
  def setup
463
492
  require 'stringio'
464
493
  @original_stderr, $stderr = $stderr, StringIO.new
@@ -477,18 +506,18 @@ class StateWithConflictingHelpersTest < Test::Unit::TestCase
477
506
  assert_equal 0, @object.parked?
478
507
  end
479
508
 
480
- def test_should_not_allow_super_chaining
509
+ def test_should_still_allow_super_chaining
481
510
  @klass.class_eval do
482
511
  def parked?
483
- super ? 1 : 0
512
+ super
484
513
  end
485
514
  end
486
515
 
487
- assert_raise(NoMethodError) { @object.parked? }
516
+ assert_equal false, @object.parked?
488
517
  end
489
518
 
490
- def test_should_output_warning
491
- assert_equal "#parked? is already defined, use #state?(:parked) instead.\n", $stderr.string
519
+ def test_should_not_output_warning
520
+ assert_equal '', $stderr.string
492
521
  end
493
522
 
494
523
  def teardown
@@ -496,7 +525,7 @@ class StateWithConflictingHelpersTest < Test::Unit::TestCase
496
525
  end
497
526
  end
498
527
 
499
- class EventWithConflictingMachineTest < Test::Unit::TestCase
528
+ class StateWithConflictingMachineTest < Test::Unit::TestCase
500
529
  def setup
501
530
  require 'stringio'
502
531
  @original_stderr, $stderr = $stderr, StringIO.new
@@ -1508,7 +1508,7 @@ class TransitionEqualityTest < Test::Unit::TestCase
1508
1508
  end
1509
1509
 
1510
1510
  def test_should_not_be_equal_with_different_machines
1511
- machine = StateMachine::Machine.new(@klass, :namespace => :other)
1511
+ machine = StateMachine::Machine.new(@klass, :status, :namespace => :other)
1512
1512
  machine.state :parked, :idling
1513
1513
  machine.event :ignite
1514
1514
  transition = StateMachine::Transition.new(@object, machine, :ignite, :parked, :idling)
metadata CHANGED
@@ -1,33 +1,23 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: state_machine
3
- version: !ruby/object:Gem::Version
4
- hash: 23
5
- prerelease: false
6
- segments:
7
- - 1
8
- - 0
9
- - 0
10
- version: 1.0.0
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ prerelease:
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Aaron Pfeifer
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2011-05-12 00:00:00 -04:00
12
+ date: 2011-05-30 00:00:00.000000000 -04:00
19
13
  default_executable:
20
14
  dependencies: []
21
-
22
15
  description: Adds support for creating state machines for attributes on any Ruby class
23
16
  email: aaron@pluginaweek.org
24
17
  executables: []
25
-
26
18
  extensions: []
27
-
28
19
  extra_rdoc_files: []
29
-
30
- files:
20
+ files:
31
21
  - examples/car.rb
32
22
  - examples/TrafficLight_state.png
33
23
  - examples/vehicle.rb
@@ -138,38 +128,29 @@ files:
138
128
  has_rdoc: true
139
129
  homepage: http://www.pluginaweek.org
140
130
  licenses: []
141
-
142
131
  post_install_message:
143
132
  rdoc_options: []
144
-
145
- require_paths:
133
+ require_paths:
146
134
  - lib
147
- required_ruby_version: !ruby/object:Gem::Requirement
135
+ required_ruby_version: !ruby/object:Gem::Requirement
148
136
  none: false
149
- requirements:
150
- - - ">="
151
- - !ruby/object:Gem::Version
152
- hash: 3
153
- segments:
154
- - 0
155
- version: "0"
156
- required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ! '>='
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
142
  none: false
158
- requirements:
159
- - - ">="
160
- - !ruby/object:Gem::Version
161
- hash: 3
162
- segments:
163
- - 0
164
- version: "0"
143
+ requirements:
144
+ - - ! '>='
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
165
147
  requirements: []
166
-
167
148
  rubyforge_project: pluginaweek
168
- rubygems_version: 1.3.7
149
+ rubygems_version: 1.6.2
169
150
  signing_key:
170
151
  specification_version: 3
171
152
  summary: Adds support for creating state machines for attributes on any Ruby class
172
- test_files:
153
+ test_files:
173
154
  - test/unit/path_test.rb
174
155
  - test/unit/condition_proxy_test.rb
175
156
  - test/unit/state_machine_test.rb