state_machine 0.10.1 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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