state-fu 0.12.3 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/persistence.rb CHANGED
@@ -20,7 +20,7 @@ module StateFu
20
20
  # end
21
21
  #
22
22
  # def write_attribute( string_value )
23
- # Logger.debug "magnetising ( #{field_name} => #{string_value} on #{object.inspect}"
23
+ # Logging.debug "magnetising ( #{field_name} => #{string_value} on #{object.inspect}"
24
24
  # object.send "magnetised_#{field_name}=", string_value
25
25
  # end
26
26
  # end
@@ -56,7 +56,7 @@ module StateFu
56
56
  persister_class = class_for klass, field_name
57
57
  prepare_field( klass, field_name, persister_class)
58
58
  returning persister_class.new( binding, field_name ) do |persister|
59
- Logger.debug( "#{persister_class}: method #{binding.method_name} as field #{persister.field_name}" )
59
+ Logging.debug( "#{persister_class}: method #{binding.method_name} as field #{persister.field_name}" )
60
60
  end
61
61
  end
62
62
 
@@ -4,7 +4,7 @@ module StateFu
4
4
 
5
5
  def self.prepare_field( klass, field_name )
6
6
  _field_name = field_name
7
- Logger.debug("Preparing ActiveRecord field #{klass}.#{field_name}")
7
+ Logging.debug("Preparing ActiveRecord field #{klass}.#{field_name}")
8
8
 
9
9
  # this adds a before_save hook to ensure that the field is initialized
10
10
  # (and the initial state set) before create.
@@ -20,12 +20,12 @@ module StateFu
20
20
  # Attribute version, so just do the simplest thing we can.
21
21
 
22
22
  def read_attribute
23
- Logger.debug "Read attribute #{field_name}, got #{object.send(:read_attribute,field_name)} for #{object.inspect}"
23
+ Logging.debug "Read attribute #{field_name}, got #{object.send(:read_attribute,field_name)} for #{object.inspect}"
24
24
  object.send( :read_attribute, field_name )
25
25
  end
26
26
 
27
27
  def write_attribute( string_value )
28
- Logger.debug "Write attribute #{field_name} to #{string_value} for #{object.inspect}"
28
+ Logging.debug "Write attribute #{field_name} to #{string_value} for #{object.inspect}"
29
29
  object.send( :write_attribute, field_name, string_value )
30
30
  end
31
31
 
@@ -5,7 +5,7 @@ module StateFu
5
5
  def self.prepare_field( klass, field_name )
6
6
  # ensure getter exists
7
7
  unless klass.instance_methods.map(&:to_sym).include?( field_name.to_sym )
8
- Logger.debug "Adding attr_reader :#{field_name} for #{klass}"
8
+ Logging.debug "Adding attr_reader :#{field_name} for #{klass}"
9
9
  _field_name = field_name
10
10
  klass.class_eval do
11
11
  private
@@ -15,7 +15,7 @@ module StateFu
15
15
 
16
16
  # ensure setter exists
17
17
  unless klass.instance_methods.map(&:to_sym).include?( :"#{field_name}=" )
18
- Logger.debug "Adding attr_writer :#{field_name}= for #{klass}"
18
+ Logging.debug "Adding attr_writer :#{field_name}= for #{klass}"
19
19
  _field_name = field_name
20
20
  klass.class_eval do
21
21
  private
@@ -32,13 +32,13 @@ module StateFu
32
32
 
33
33
  def read_attribute
34
34
  string = object.send( field_name )
35
- Logger.debug "Read attribute #{field_name}, got #{string.inspect} for #{object.inspect}"
35
+ Logging.debug "Read attribute #{field_name}, got #{string.inspect} for #{object.inspect}"
36
36
  string
37
37
  end
38
38
 
39
39
  def write_attribute( string_value )
40
40
  writer_method = "#{field_name}="
41
- Logger.debug "Writing attribute #{field_name} -> #{string_value.inspect} for #{object.inspect}"
41
+ Logging.debug "Writing attribute #{field_name} -> #{string_value.inspect} for #{object.inspect}"
42
42
  object.send( writer_method, string_value )
43
43
  end
44
44
 
@@ -8,25 +8,9 @@ module StateFu
8
8
 
9
9
  attr_reader :binding, :field_name, :current_state
10
10
 
11
- def self.prepare_class( klass )
12
- unless klass.instance_methods.include?( :method_missing_before_state_fu )
13
- alias_method :method_missing_before_state_fu, :method_missing
14
- klass.class_eval do
15
- def method_missing( method_name, *args, &block )
16
- state_fu!
17
- begin
18
- send( method_name, *args, &block )
19
- rescue NoMethodError => e
20
- method_missing_before_state_fu( method_name, *args, &block )
21
- end
22
- end
23
- end
24
- end
25
- end
26
-
27
11
  # define this method in subclasses to do any preparation
28
12
  def self.prepare_field( klass, field_name )
29
- Logger.warn("Abstract method in #{self}.prepare_field called. Override me!")
13
+ Logging.warn("Abstract method in #{self}.prepare_field called. Override me!")
30
14
  end
31
15
 
32
16
  def initialize( binding, field_name )
@@ -36,11 +20,11 @@ module StateFu
36
20
  @current_state = find_current_state()
37
21
 
38
22
  if current_state.nil?
39
- Logger.warn("undefined state for binding #{binding} on #{object} with field_name #{field_name.inspect}")
40
- Logger.warn("Machine for #{object} has no states: #{machine}") if machine.states.empty?
23
+ Logging.warn("undefined state for binding #{binding} on #{object} with field_name #{field_name.inspect}")
24
+ Logging.warn("Machine for #{object} has no states: #{machine}") if machine.states.empty?
41
25
  else
42
26
  persist!
43
- Logger.debug("#{object} resumes #{binding.method_name} at #{current_state.name}")
27
+ Logging.debug("#{object} resumes #{binding.method_name} at #{current_state.name}")
44
28
  end
45
29
  end
46
30
 
data/lib/state-fu.rb CHANGED
@@ -15,12 +15,14 @@
15
15
  # extend the core features.
16
16
  #
17
17
  # It is also delightfully elegant and easy to use for simple things.
18
+ %w( support support/active_support_lite ).each do |path|
19
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), path)))
20
+ end
18
21
 
19
22
  [ 'support/core_ext',
20
- 'support/logger',
23
+ 'support/logging',
21
24
  'support/applicable',
22
25
  'support/arrays',
23
- 'support/methodical',
24
26
  'support/has_options',
25
27
  'support/vizier',
26
28
  'support/plotter',
@@ -38,53 +38,31 @@ class Array
38
38
  end
39
39
 
40
40
  class Object
41
-
42
- def self.__define_method( method_name, &block )
43
- self.class.class_eval do
44
- define_method method_name, &block
45
- end
46
- end
47
-
48
- def __define_singleton_method( method_name, &block )
49
- (class << self; self; end).class_eval do
50
- define_method method_name, &block
51
- end
52
- end
53
-
54
-
55
- def with_methods_on(other)
56
- (class << self; self; end).class_eval do
57
- # we need some accounting to ensure that everything behaves itself when
58
- # .with_methods_on is called more than once.
59
- @_with_methods_on ||= []
60
- if !@_with_methods_on.include?("method_missing_before_#{other.__id__}")
61
- alias_method "method_missing_before_#{other.__id__}", :method_missing
62
- end
63
- @_with_methods_on << "method_missing_before_#{other.__id__}"
64
-
65
- define_method :method_missing do |method_name, *args|
66
- if _other.respond_to?(method_name, true)
67
- _other.__send__( method_name, *args )
68
- else
69
- send "method_missing_before_#{other.__id__}", method_name, *args
70
- end
71
- end
41
+ unless defined? instance_exec # 1.9
42
+ module InstanceExecMethods #:nodoc:
72
43
  end
44
+ include InstanceExecMethods
73
45
 
74
- result = yield
46
+ # Evaluate the block with the given arguments within the context of
47
+ # this object, so self is set to the method receiver.
48
+ #
49
+ # From Mauricio's http://eigenclass.org/hiki/bounded+space+instance_exec
50
+ def instance_exec(*args, &block)
51
+ begin
52
+ old_critical, Thread.critical = Thread.critical, true
53
+ n = 0
54
+ n += 1 while respond_to?(method_name = "__instance_exec#{n}")
55
+ InstanceExecMethods.module_eval { define_method(method_name, &block) }
56
+ ensure
57
+ Thread.critical = old_critical
58
+ end
75
59
 
76
- (class << self; self; end).class_eval do
77
- # heal the damage
78
- if @_with_methods_on.pop != "method_missing_before_#{other.__id__}"
79
- raise "there is no god"
80
- end
81
- if !@_with_methods_on.include?("method_missing_before_#{other.__id__}")
82
- alias_method :method_missing, "method_missing_before_#{other.__id__}"
83
- undef_method "method_missing_before_#{other.__id__}"
84
- end
60
+ begin
61
+ send(method_name, *args)
62
+ ensure
63
+ InstanceExecMethods.module_eval { remove_method(method_name) } rescue nil
64
+ end
85
65
  end
86
-
87
- result
88
- end # with_methods_on
66
+ end
89
67
  end
90
68
 
@@ -7,7 +7,7 @@ module StateFu
7
7
  # Use Rails' log if running as a rails plugin; allow independent control of
8
8
  # StateFu log level.
9
9
 
10
- class Logger
10
+ class Logging
11
11
  cattr_accessor :prefix # prefix for log messages
12
12
  cattr_accessor :suppress # set true to send messages to /dev/null
13
13
  cattr_accessor :shared
@@ -101,10 +101,10 @@ module StateFu
101
101
  case logger
102
102
  when String
103
103
  file = File.open(logger, File::WRONLY | File::APPEND)
104
- @@logger = Logger.activesupport_logger_available? ? ActiveSupport::BufferedLogger.new(file) : ::Logger.new(file)
105
- when ::Logger
104
+ @@logger = activesupport_logger_available? ? ActiveSupport::BufferedLogger.new(file) : Logger.new(file)
105
+ when Logger
106
106
  @@logger = logger
107
- when Logger.activesupport_logger_available? && ActiveSupport::BufferedLogger
107
+ when activesupport_logger_available? && ActiveSupport::BufferedLogger
108
108
  @@logger = logger
109
109
  else
110
110
  default_logger
@@ -134,7 +134,7 @@ module StateFu
134
134
  ActiveSupport::BufferedLogger.new(target)
135
135
  end
136
136
  else
137
- ::Logger.new(target)
137
+ Logger.new(target)
138
138
  end
139
139
  end
140
140
 
data/lib/transition.rb CHANGED
@@ -165,7 +165,7 @@ module StateFu
165
165
  # halt a transition with a message
166
166
  # can be used to back out of a transition inside eg a state entry hook
167
167
  def halt! message
168
- raise TransitionHalted.new( self, message )
168
+ raise StateFu::TransitionHalted.new( self, message )
169
169
  end
170
170
 
171
171
  #
@@ -184,10 +184,10 @@ module StateFu
184
184
  StateFu::Hooks::ALL_HOOKS.map do |owner, slot|
185
185
  [ [owner, slot], send(owner).hooks[slot] ]
186
186
  end.each do |address, hooks|
187
- Logger.info("running #{address.inspect} hooks for #{object.class} #{object}")
187
+ Logging.info("running #{address.inspect} hooks for #{object.class} #{object}")
188
188
  owner,slot = *address
189
189
  hooks.each do |hook|
190
- Logger.info("running hook #{hooks} for #{object.class} #{object}")
190
+ Logging.info("running hook #{hooks} for #{object.class} #{object}")
191
191
  @current_hook_slot = address
192
192
  @current_hook = hook
193
193
  run_hook hook
@@ -195,14 +195,14 @@ module StateFu
195
195
  if slot == :entry
196
196
  @accepted = true
197
197
  @binding.persister.current_state = @target
198
- Logger.info("State is now :#{@target.name} for #{object.class} #{object}")
198
+ Logging.info("State is now :#{@target.name} for #{object.class} #{object}")
199
199
  end
200
200
  end
201
201
  # transition complete
202
202
  @current_hook_slot = nil
203
203
  @current_hook = nil
204
204
  rescue TransitionHalted => e
205
- Logger.info("Transition halted for #{object.class} #{object}: #{e.inspect}")
205
+ Logging.info("Transition halted for #{object.class} #{object}: #{e.inspect}")
206
206
  @errors << e
207
207
  end
208
208
  self
@@ -292,17 +292,15 @@ module StateFu
292
292
  s
293
293
  end
294
294
 
295
+ def evaluate(method_name_or_proc)
296
+ executioner.evaluate(method_name_or_proc)
297
+ end
298
+ alias_method :call, :evaluate
299
+
295
300
  private
296
301
 
297
302
  def executioner
298
- @executioner ||= Executioner.new( self ) do |ex|
299
- machine.inject_helpers_into( ex )
300
- machine.inject_methods_into( ex )
301
- end
302
- end
303
-
304
- def evaluate(method_name_or_proc)
305
- executioner.evaluate(method_name_or_proc)
303
+ @executioner ||= Executioner.new( self )
306
304
  end
307
305
 
308
306
  def evaluate_requirement_message( name, revalidate=false)
@@ -0,0 +1,22 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ describe "machine(:autosave => true)" do
4
+ before(:all) do
5
+ reset!
6
+ prepare_active_record() do
7
+ def self.up
8
+ create_table :example_records do |t|
9
+ t.string :name, :null => false
10
+ t.string :state_fu_field, :null => false
11
+ t.string :description
12
+ t.string :status
13
+ t.timestamps
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ it "should automatically save the record when a transition is complete" do
20
+ pending
21
+ end
22
+ end
@@ -1,57 +1,32 @@
1
1
  require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
2
 
3
3
  module RequirementFeatureHelper
4
- def account_expired?
5
- !! account_expired
6
- end
7
4
 
8
5
  def valid_password?
9
6
  !! valid_password
10
7
  end
11
8
  end
12
9
 
13
- # it_should_behave_like "!" do
14
- shared_examples_for "not requirements" do
15
- describe "requirements with names beginning with no[t]_" do
16
-
17
- it "should return the opposite of the requirement name without not_" do
18
- # @obj.current_state.should == :guest
19
- @obj.stfu.teleport! :anonymous
20
- @obj.valid_password = false
21
- @binding.can_has_valid_password?.should == false
22
- @binding.can_has_not_valid_password?.should == true
23
- @binding.can_has_no_valid_password?.should == true
24
- @obj.valid_password = true
25
- @binding.can_has_valid_password?.should == true
26
- @binding.can_has_not_valid_password?.should == false
27
- @binding.can_has_no_valid_password?.should == false
28
- end
29
-
30
- it "should call the method directly if one exists" do
31
- @obj.valid_password = true
32
- (class << @obj; self; end).class_eval do
33
- define_method( :no_valid_password? ) { true }
34
- end
35
- @binding.can_has_valid_password?.should == true
36
- @binding.can_has_not_valid_password?.should == false
37
- @binding.can_has_no_valid_password?.should == true
38
- end
39
-
40
- end
41
-
42
- end
43
-
44
10
  describe "requirements" do
11
+
45
12
  before(:all) do
46
13
  reset!
47
14
  make_pristine_class('Klass')
15
+
48
16
  Klass.class_eval do
49
17
  attr_accessor :valid_password
50
18
  attr_accessor :account_expired
19
+
20
+ def account_expired?
21
+ !! account_expired
22
+ end
51
23
  end
24
+
52
25
  @machine = StateFu::Machine.new do
53
26
  initial_state :guest
54
27
 
28
+ define(:valid_password?) { !! valid_password }
29
+
55
30
  event :has_valid_password, :from => :anonymous, :to => :logged_in do
56
31
  requires :valid_password?
57
32
  end
@@ -62,9 +37,12 @@ describe "requirements" do
62
37
 
63
38
  event :has_no_valid_password, :from => :anonymous, :to => :suspect do
64
39
  requires :no_valid_password?
65
- end
66
-
40
+ end
67
41
  end
42
+
43
+ @machine.bind!(Klass, :default)
44
+ @obj = Klass.new
45
+ @binding = @obj.state_fu
68
46
  end
69
47
 
70
48
  before :each do
@@ -72,47 +50,31 @@ describe "requirements" do
72
50
  @obj.account_expired = false
73
51
  end
74
52
 
75
- describe "requirements defined with a machine helper" do
76
- before :all do
77
- @machine.lathe { helper RequirementFeatureHelper }
78
- @machine.bind!(Klass, :default)
79
- @obj = Klass.new
80
- @binding = @obj.state_fu
81
- end
82
-
83
- it_should_behave_like "not requirements"
84
-
85
- it "should not have methods on the object" do
86
- @obj.respond_to?(:valid_password?).should == false
87
- @obj.respond_to?(:account_expired?).should == false
88
- end
89
-
90
- it "should have methods on the binding" do
91
- # this is a little misleading because theyre not evaluated on the binding ..
92
- @binding.respond_to?(:valid_password?).should == true
93
- @binding.respond_to?(:account_expired?).should == true
94
- @binding.respond_to?(:not_valid_password?).should == false
95
- @binding.respond_to?(:not_account_expired?).should == false
96
- end
53
+ it "should have methods ...." do
54
+ @obj.should respond_to(:account_expired?)
55
+ @machine.named_procs.keys.should include(:valid_password?)
56
+ end
57
+
58
+ it "should return the opposite of the requirement name without not_" do
59
+ @obj.stfu.teleport! :anonymous
60
+ @obj.valid_password = false
61
+ @binding.can_has_valid_password?.should == false
62
+ @binding.can_has_not_valid_password?.should == true
63
+ @binding.can_has_no_valid_password?.should == true
64
+ @obj.valid_password = true
65
+ @binding.can_has_valid_password?.should == true
66
+ @binding.can_has_not_valid_password?.should == false
67
+ @binding.can_has_no_valid_password?.should == false
97
68
  end
98
69
 
99
- describe "requirements defined on the object" do
100
- before :all do
101
- @machine.bind!(Klass, :default)
102
- @obj = Klass.new
103
- @binding = @obj.state_fu
104
- Klass.class_eval do
105
- include RequirementFeatureHelper
106
- end
70
+ it "should call the method directly if one exists" do
71
+ @obj.valid_password = true
72
+ (class << @obj; self; end).class_eval do
73
+ define_method( :no_valid_password? ) { true }
107
74
  end
75
+ @binding.can_has_valid_password?.should == true
76
+ @binding.can_has_not_valid_password?.should == false
77
+ @binding.can_has_no_valid_password?.should == true
78
+ end
108
79
 
109
- it_should_behave_like "not requirements"
110
-
111
- it "should have methods on the object" do
112
- @obj.respond_to?(:valid_password?).should == true
113
- @obj.respond_to?(:not_valid_password?).should == false
114
- @obj.respond_to?(:account_expired?).should == true
115
- @obj.respond_to?(:not_account_expired?).should == false
116
- end
117
- end
118
80
  end