state_machine 0.10.1 → 0.10.2

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.
@@ -266,31 +266,28 @@ module StateMachine
266
266
  require 'sequel/extensions/inflector'
267
267
  end
268
268
 
269
- # Only allows state initialization on new records that aren't being
270
- # created with a set of attributes that includes this machine's
271
- # attribute.
272
- def initialize_state?(object, options)
273
- if object.new?
274
- attributes = options[:attributes] || {}
275
- ignore = object.send(:setter_methods, nil, nil).map {|setter| setter.chop.to_sym} & attributes.keys.map {|key| key.to_sym}
276
- !ignore.map {|attribute| attribute.to_sym}.include?(attribute)
277
- end
278
- end
279
-
280
269
  # Defines an initialization hook into the owner class for setting the
281
270
  # initial state of the machine *before* any attributes are set on the
282
271
  # object
283
272
  def define_state_initializer
284
273
  # Hooks in to attribute initialization to set the states *prior* to
285
274
  # the attributes being set
286
- define_helper(:instance, :set) do |machine, object, _super, *args|
287
- if !object.instance_variable_defined?('@initialized_state_machines')
288
- object.class.state_machines.initialize_states(object, :attributes => args.first) { _super.call }
289
- object.instance_variable_set('@initialized_state_machines', true)
290
- else
291
- _super.call
275
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
276
+ # Initializes dynamic states
277
+ def initialize(*)
278
+ super do |*args|
279
+ self.class.state_machines.initialize_states(self, :static => false)
280
+ changed_columns.clear
281
+ yield(*args) if block_given?
282
+ end
292
283
  end
293
- end
284
+
285
+ # Initializes static states
286
+ def set(*)
287
+ self.class.state_machines.initialize_states(self, :dynamic => false) if values.empty?
288
+ super
289
+ end
290
+ end_eval
294
291
  end
295
292
 
296
293
  # Skips defining reader/writer methods since this is done automatically
@@ -309,16 +306,21 @@ module StateMachine
309
306
  super
310
307
 
311
308
  if action == :save
312
- handle_validation_failure = self.handle_validation_failure
313
- define_helper(:instance, :valid?) do |machine, object, _super, *args|
314
- yielded = false
315
- result = object.class.state_machines.transitions(object, :save, :after => false).perform do
316
- yielded = true
317
- _super.call
309
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
310
+ def valid?(*args)
311
+ yielded = false
312
+ result = self.class.state_machines.transitions(self, :save, :after => false).perform do
313
+ yielded = true
314
+ super
315
+ end
316
+
317
+ if yielded || result
318
+ result
319
+ else
320
+ #{handle_validation_failure}
321
+ end
318
322
  end
319
-
320
- !yielded && !result ? handle_validation_failure.call(object, args, yielded, result) : result
321
- end
323
+ end_eval
322
324
  end
323
325
  end
324
326
 
@@ -328,22 +330,21 @@ module StateMachine
328
330
  # save calls.
329
331
  def define_action_hook
330
332
  if action == :save
331
- action_hook = self.action_hook
332
- handle_save_failure = self.handle_save_failure
333
-
334
- define_helper(:instance, action_hook) do |machine, object, _super, *|
335
- yielded = false
336
- result = object.class.state_machines.transitions(object, :save).perform do
337
- yielded = true
338
- _super.call
339
- end
340
-
341
- if yielded || result
342
- result
343
- else
344
- handle_save_failure.call(object)
333
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
334
+ def #{action_hook}(*)
335
+ yielded = false
336
+ result = self.class.state_machines.transitions(self, :save).perform do
337
+ yielded = true
338
+ super
339
+ end
340
+
341
+ if yielded || result
342
+ result
343
+ else
344
+ #{handle_save_failure}
345
+ end
345
346
  end
346
- end
347
+ end_eval
347
348
  else
348
349
  super
349
350
  end
@@ -356,20 +357,12 @@ module StateMachine
356
357
 
357
358
  # Handles whether validation errors should be raised
358
359
  def handle_validation_failure
359
- lambda do |object, args, yielded, result|
360
- object.instance_eval do
361
- raise_on_failure?(args.first || {}) ? raise_hook_failure(:validation) : result
362
- end
363
- end
360
+ 'raise_on_failure?(args.first || {}) ? raise_hook_failure(:validation) : result'
364
361
  end
365
362
 
366
363
  # Handles how save failures are raised
367
364
  def handle_save_failure
368
- lambda do |object|
369
- object.instance_eval do
370
- raise_hook_failure(:save)
371
- end
372
- end
365
+ 'raise_hook_failure(:save)'
373
366
  end
374
367
 
375
368
  # Creates a scope for finding records *with* a particular state or
@@ -7,19 +7,11 @@ module StateMachine
7
7
  end
8
8
 
9
9
  def handle_validation_failure
10
- lambda do |object, args, yielded, result|
11
- object.instance_eval do
12
- raise_on_save_failure ? save_failure(:validation) : result
13
- end
14
- end
10
+ 'raise_on_save_failure ? save_failure(:validation) : result'
15
11
  end
16
12
 
17
13
  def handle_save_failure
18
- lambda do |object|
19
- object.instance_eval do
20
- save_failure(:save)
21
- end
22
- end
14
+ 'save_failure(:save)'
23
15
  end
24
16
  end
25
17
 
@@ -445,9 +445,8 @@ module StateMachine
445
445
  @action = options[:action]
446
446
  @use_transactions = options[:use_transactions]
447
447
  @initialize_state = options[:initialize]
448
- @helpers = {:instance => {}, :class => {}}
449
448
  self.owner_class = owner_class
450
- self.initial_state = options[:initial]
449
+ self.initial_state = options[:initial] unless owner_class.state_machines.any? {|name, machine| machine.attribute == attribute && machine != self}
451
450
 
452
451
  # Define class integration
453
452
  define_helpers
@@ -469,7 +468,6 @@ module StateMachine
469
468
  @states = @states.dup
470
469
  @states.machine = self
471
470
  @callbacks = {:before => @callbacks[:before].dup, :after => @callbacks[:after].dup, :failure => @callbacks[:failure].dup}
472
- @helpers = {:instance => @helpers[:instance].dup, :class => @helpers[:class].dup}
473
471
  end
474
472
 
475
473
  # Sets the class which is the owner of this state machine. Any methods
@@ -511,56 +509,6 @@ module StateMachine
511
509
  states.each {|state| state.initial = (state.name == @initial_state)}
512
510
  end
513
511
 
514
- # Initializes the state on the given object. Initial values are only set if
515
- # the machine's attribute hasn't been previously initialized.
516
- def initialize_state(object, options = {})
517
- write(object, :state, initial_state(object).value) if initialize_state?(object, options)
518
- end
519
-
520
- # Gets the actual name of the attribute on the machine's owner class that
521
- # stores data with the given name.
522
- def attribute(name = :state)
523
- name == :state ? @attribute : :"#{self.name}_#{name}"
524
- end
525
-
526
- # Defines a new helper method in an instance or class scope with the given
527
- # name. If the method is already defined in the scope, then this will not
528
- # override it.
529
- #
530
- # Example:
531
- #
532
- # # Instance helper
533
- # machine.define_helper(:instance, :state_name) do |machine, object, _super|
534
- # machine.states.match(object)
535
- # end
536
- #
537
- # # Class helper
538
- # machine.define_helper(:class, :state_machine_name) do |machine, klass, _super|
539
- # "State"
540
- # end
541
- def define_helper(scope, method, &block)
542
- @helpers.fetch(scope)[method] = block
543
- @helper_modules.fetch(scope).class_eval <<-end_eval, __FILE__, __LINE__
544
- def #{method}(*args)
545
- _super = lambda {|*new_args| new_args.empty? ? super(*args) : super(*new_args)}
546
- #{scope == :class ? 'self' : 'self.class'}.state_machine(#{name.inspect}).call_helper(#{scope.inspect}, #{method.inspect}, self, _super, *args)
547
- end
548
- end_eval
549
- end
550
-
551
- # Invokes the helper method defined in the given scope.
552
- #
553
- # Example:
554
- #
555
- # # Instance helper
556
- # machine.call_helper(:instance, :state_name, self, lambda {super})
557
- #
558
- # # Class helper
559
- # machine.call_helper(:class, :state_machine_name, self, lambda {super})
560
- def call_helper(scope, method, object, _super, *args)
561
- @helpers.fetch(scope).fetch(method).call(self, object, _super, *args)
562
- end
563
-
564
512
  # Gets the initial state of the machine for the given object. If a dynamic
565
513
  # initial state was configured for this machine, then the object will be
566
514
  # passed into the lambda block to help determine the actual state.
@@ -596,7 +544,7 @@ module StateMachine
596
544
  # vehicle.force_idle = false
597
545
  # Vehicle.state_machine.initial_state(vehicle) # => #<StateMachine::State name=:parked value="parked" initial=false>
598
546
  def initial_state(object)
599
- states.fetch(dynamic_initial_state? ? evaluate_method(object, @initial_state) : @initial_state)
547
+ states.fetch(dynamic_initial_state? ? evaluate_method(object, @initial_state) : @initial_state) if instance_variable_defined?('@initial_state')
600
548
  end
601
549
 
602
550
  # Whether a dynamic initial state is being used in the machine
@@ -604,6 +552,77 @@ module StateMachine
604
552
  @initial_state.is_a?(Proc)
605
553
  end
606
554
 
555
+ # Initializes the state on the given object. Initial values are only set if
556
+ # the machine's attribute hasn't been previously initialized.
557
+ #
558
+ # Configuration options:
559
+ # * <tt>:force</tt> - Whether to initialize the state regardless of its
560
+ # current value
561
+ # * <tt>:to</tt> - A hash to set the initial value in instead of writing
562
+ # directly to the object
563
+ def initialize_state(object, options = {})
564
+ state = initial_state(object)
565
+ if state && (options[:force] || initialize_state?(object))
566
+ value = state.value
567
+
568
+ if hash = options[:to]
569
+ hash[attribute.to_s] = value
570
+ else
571
+ write(object, :state, value)
572
+ end
573
+ end
574
+ end
575
+
576
+ # Gets the actual name of the attribute on the machine's owner class that
577
+ # stores data with the given name.
578
+ def attribute(name = :state)
579
+ name == :state ? @attribute : :"#{self.name}_#{name}"
580
+ end
581
+
582
+ # Defines a new helper method in an instance or class scope with the given
583
+ # name. If the method is already defined in the scope, then this will not
584
+ # override it.
585
+ #
586
+ # Example:
587
+ #
588
+ # # Instance helper
589
+ # machine.define_helper(:instance, :state_name) do |machine, object|
590
+ # machine.states.match(object).name
591
+ # end
592
+ #
593
+ # # Class helper
594
+ # machine.define_helper(:class, :state_machine_name) do |machine, klass|
595
+ # "State"
596
+ # end
597
+ #
598
+ # You can also define helpers using string evaluation like so:
599
+ #
600
+ # # Instance helper
601
+ # machine.define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
602
+ # def state_name
603
+ # self.class.state_machine(:state).states.match(self).name
604
+ # end
605
+ # end_eval
606
+ #
607
+ # # Class helper
608
+ # machine.define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1
609
+ # def state_machine_name
610
+ # "State"
611
+ # end
612
+ # end_eval
613
+ def define_helper(scope, method, *args, &block)
614
+ 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)
619
+ end
620
+ end
621
+ else
622
+ @helper_modules.fetch(scope).class_eval(method, *args)
623
+ end
624
+ end
625
+
607
626
  # Customizes the definition of one or more states in the machine.
608
627
  #
609
628
  # Configuration options:
@@ -1535,9 +1554,9 @@ module StateMachine
1535
1554
 
1536
1555
  # Determines if the machine's attribute needs to be initialized. This
1537
1556
  # will only be true if the machine's attribute is blank.
1538
- def initialize_state?(object, options = {})
1557
+ def initialize_state?(object)
1539
1558
  value = read(object, :state)
1540
- value.nil? || value.respond_to?(:empty?) && value.empty?
1559
+ (value.nil? || value.respond_to?(:empty?) && value.empty?) && !states[value, :value]
1541
1560
  end
1542
1561
 
1543
1562
  # Adds helper methods for interacting with the state machine, including
@@ -1555,9 +1574,11 @@ module StateMachine
1555
1574
  # are set prior to the original initialize method and dynamic values are
1556
1575
  # set *after* the initialize method in case it is dependent on it.
1557
1576
  def define_state_initializer
1558
- define_helper(:instance, :initialize) do |machine, object, _super, *|
1559
- object.class.state_machines.initialize_states(object) { _super.call }
1560
- end
1577
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
1578
+ def initialize(*)
1579
+ self.class.state_machines.initialize_states(self) { super }
1580
+ end
1581
+ end_eval
1561
1582
  end
1562
1583
 
1563
1584
  # Adds reader/writer methods for accessing the state attribute
@@ -1571,22 +1592,24 @@ module StateMachine
1571
1592
  # current state
1572
1593
  def define_state_predicate
1573
1594
  call_super = owner_class_ancestor_has_method?("#{name}?")
1574
- define_helper(:instance, "#{name}?") do |machine, object, _super, *args|
1575
- args.empty? && call_super ? _super.call : machine.states.matches?(object, args.first)
1576
- end
1595
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
1596
+ def #{name}?(*args)
1597
+ args.empty? && #{call_super} ? super : self.class.state_machine(#{name.inspect}).states.matches?(self, *args)
1598
+ end
1599
+ end_eval
1577
1600
  end
1578
1601
 
1579
1602
  # Adds helper methods for getting information about this state machine's
1580
1603
  # events
1581
1604
  def define_event_helpers
1582
1605
  # Gets the events that are allowed to fire on the current object
1583
- define_helper(:instance, attribute(:events)) do |machine, object, _super, *args|
1606
+ define_helper(:instance, attribute(:events)) do |machine, object, *args|
1584
1607
  machine.events.valid_for(object, *args).map {|event| event.name}
1585
1608
  end
1586
1609
 
1587
1610
  # Gets the next possible transitions that can be run on the current
1588
1611
  # object
1589
- define_helper(:instance, attribute(:transitions)) do |machine, object, _super, *args|
1612
+ define_helper(:instance, attribute(:transitions)) do |machine, object, *args|
1590
1613
  machine.events.transitions_for(object, *args)
1591
1614
  end
1592
1615
 
@@ -1594,7 +1617,7 @@ module StateMachine
1594
1617
  # action is called
1595
1618
  if action
1596
1619
  event_attribute = attribute(:event)
1597
- define_helper(:instance, event_attribute) do |machine, object, *|
1620
+ define_helper(:instance, event_attribute) do |machine, object|
1598
1621
  # Interpret non-blank events as present
1599
1622
  event = machine.read(object, :event, true)
1600
1623
  event && !(event.respond_to?(:empty?) && event.empty?) ? event.to_sym : nil
@@ -1602,12 +1625,14 @@ module StateMachine
1602
1625
 
1603
1626
  # A roundabout way of writing the attribute is used here so that
1604
1627
  # integrations can hook into this modification
1605
- define_helper(:instance, "#{event_attribute}=") do |machine, object, _super, value|
1628
+ define_helper(:instance, "#{event_attribute}=") do |machine, object, value|
1606
1629
  machine.write(object, :event, value, true)
1607
1630
  end
1608
1631
 
1609
1632
  event_transition_attribute = attribute(:event_transition)
1610
- @helper_modules[:instance].class_eval { protected; attr_accessor event_transition_attribute }
1633
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
1634
+ protected; attr_accessor #{event_transition_attribute.inspect}
1635
+ end_eval
1611
1636
  end
1612
1637
  end
1613
1638
 
@@ -1615,7 +1640,7 @@ module StateMachine
1615
1640
  # available transition paths
1616
1641
  def define_path_helpers
1617
1642
  # Gets the paths of transitions available to the current object
1618
- define_helper(:instance, attribute(:paths)) do |machine, object, _super, *args|
1643
+ define_helper(:instance, attribute(:paths)) do |machine, object, *args|
1619
1644
  machine.paths_for(object, *args)
1620
1645
  end
1621
1646
  end
@@ -1645,11 +1670,13 @@ module StateMachine
1645
1670
  private_action_hook = owner_class.private_method_defined?(action_hook)
1646
1671
 
1647
1672
  # Only define helper if it hasn't
1648
- define_helper(:instance, action_hook) do |machine, object, _super, *args|
1649
- object.class.state_machines.transitions(object, action).perform { _super.call }
1650
- end
1651
-
1652
- @helper_modules[:instance].class_eval { private action_hook } if private_action_hook
1673
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
1674
+ def #{action_hook}(*)
1675
+ self.class.state_machines.transitions(self, #{action.inspect}).perform { super }
1676
+ end
1677
+
1678
+ private #{action_hook.inspect} if #{private_action_hook}
1679
+ end_eval
1653
1680
  end
1654
1681
 
1655
1682
  # The method to hook into for triggering transitions when invoked. By
@@ -1674,22 +1701,22 @@ module StateMachine
1674
1701
  # events on the owner class
1675
1702
  def define_name_helpers
1676
1703
  # Gets the humanized version of a state
1677
- define_helper(:class, "human_#{attribute(:name)}") do |machine, klass, _super, state|
1704
+ define_helper(:class, "human_#{attribute(:name)}") do |machine, klass, state|
1678
1705
  machine.states.fetch(state).human_name(klass)
1679
1706
  end
1680
1707
 
1681
1708
  # Gets the humanized version of an event
1682
- define_helper(:class, "human_#{attribute(:event_name)}") do |machine, klass, _super, event|
1709
+ define_helper(:class, "human_#{attribute(:event_name)}") do |machine, klass, event|
1683
1710
  machine.events.fetch(event).human_name(klass)
1684
1711
  end
1685
1712
 
1686
1713
  # Gets the state name for the current value
1687
- define_helper(:instance, attribute(:name)) do |machine, object, *|
1714
+ define_helper(:instance, attribute(:name)) do |machine, object|
1688
1715
  machine.states.match!(object).name
1689
1716
  end
1690
1717
 
1691
1718
  # Gets the human state name for the current value
1692
- define_helper(:instance, "human_#{attribute(:name)}") do |machine, object, *|
1719
+ define_helper(:instance, "human_#{attribute(:name)}") do |machine, object|
1693
1720
  machine.states.match!(object).human_name(object.class)
1694
1721
  end
1695
1722
  end
@@ -1709,7 +1736,7 @@ module StateMachine
1709
1736
  if scope = send("create_#{kind}_scope", method)
1710
1737
  # Converts state names to their corresponding values so that they
1711
1738
  # can be looked up properly
1712
- define_helper(:class, method) do |machine, klass, _super, *states|
1739
+ define_helper(:class, method) do |machine, klass, *states|
1713
1740
  values = states.flatten.map {|state| machine.states.fetch(state).value}
1714
1741
  scope.call(klass, values)
1715
1742
  end