stator 0.4.0 → 0.9.0.beta

Sign up to get free protection for your applications and to get access to all the features.
data/lib/stator/model.rb CHANGED
@@ -1,113 +1,118 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Stator
2
4
  module Model
5
+ extend ActiveSupport::Concern
3
6
 
4
- def stator(options = {}, &block)
5
-
6
- class_attribute :_stators unless respond_to?(:_stators)
7
+ included do
8
+ class_attribute :_stators
9
+ attr_accessor :_stator_integrations
7
10
 
8
- include InstanceMethods unless self.included_modules.include?(InstanceMethods)
9
- include TrackerMethods if options[:track] == true
11
+ validate :_stator_validate_transition
10
12
 
11
13
  self._stators ||= {}
12
14
 
13
- unless self.abstract_class?
14
- f = options[:field] || :state
15
- # rescue nil since the table may not exist yet.
16
- initial = self.columns_hash[f.to_s].default rescue nil
17
- options = options.merge(initial: initial) if initial
18
- end
19
-
20
- machine = (self._stators[options[:namespace].to_s] ||= ::Stator::Machine.new(self, options))
21
-
22
- if block_given?
23
- machine.instance_eval(&block)
24
- machine.evaluate
25
- end
26
-
27
- machine
15
+ before_save :_stator_maybe_track_transition, prepend: true
28
16
  end
29
17
 
30
- def _stator(namespace)
31
- self._stators[namespace.to_s]
32
- end
18
+ class_methods do
19
+ def stator(namespace: nil, field: :state, initial: nil, track: true, &block)
20
+ unless abstract_class?
21
+ # Discover the default value (usually initial) from the table...
22
+ # but rescue nil since the table may not exist yet.
23
+ initial = _determine_initial_stator_state(field)
24
+ end
33
25
 
34
- module TrackerMethods
26
+ opts = { namespace: _stator_namespace(namespace), field: field.to_sym, initial: initial, track: track }
35
27
 
36
- def self.included(base)
37
- base.class_eval do
38
- before_save :_stator_maybe_track_transition, prepend: true
28
+ Stator::Machine.find_or_create(self, **opts).tap do |machine|
29
+ machine.evaluate_dsl(&block) if block_given?
39
30
  end
40
31
  end
41
32
 
42
- def in_state_at?(state, t, namespace = '')
43
- _integration(namespace).in_state_at?(state, t)
33
+ def _stator(namespace)
34
+ self._stators[_stator_namespace(namespace)]
44
35
  end
45
36
 
46
- def likely_state_at(t, namespace = '')
47
- _integration(namespace).likely_state_at(t)
48
- end
37
+ def _stator_namespace(namespace = nil)
38
+ namespace = nil if namespace.blank?
49
39
 
50
- def state_by?(state, t, namespace = '')
51
- _integration(namespace).state_by?(state, t)
40
+ (namespace || Stator.default_namespace).to_sym
52
41
  end
53
42
 
54
- protected
43
+ def _determine_initial_stator_state(field)
44
+ columns_hash[field.to_s].default.to_sym
45
+ rescue StandardError
46
+ nil
47
+ end
48
+ end
55
49
 
56
- def _stator_maybe_track_transition
57
- self._stators.each do |namespace, machine|
58
- next unless machine.tracking_enabled?
50
+ def initialize_dup(other)
51
+ @_stator_integrations = {}
52
+ super
53
+ end
59
54
 
60
- _integration(namespace).track_transition
61
- end
55
+ def without_state_transition_validations(namespace = '')
56
+ _stator_integration(namespace).without_validation do
57
+ yield self
58
+ end
59
+ end
62
60
 
63
- true
61
+ def without_state_transition_tracking(namespace = '')
62
+ _stator_integration(namespace).without_transition_tracking do
63
+ yield self
64
64
  end
65
+ end
65
66
 
67
+ def current_state
68
+ _stator_integration.state&.to_sym
66
69
  end
67
70
 
68
- module InstanceMethods
71
+ def in_state_at?(state, t, namespace = '')
72
+ _stator_integration(namespace).in_state_at?(state, t)
73
+ end
69
74
 
70
- def self.included(base)
71
- base.class_eval do
72
- validate :_stator_validate_transition
73
- end
74
- end
75
+ def likely_state_at(t, namespace = '')
76
+ _stator_integration(namespace).likely_state_at(t)
77
+ end
75
78
 
76
- def initialize_dup(other)
77
- @_integrations = {}
78
- super
79
- end
79
+ def state_by?(state, t, namespace = '')
80
+ _stator_integration(namespace).state_by?(state, t)
81
+ end
80
82
 
81
- def without_state_transition_validations(namespace = '')
82
- _integration(namespace).without_validation do
83
- yield self
84
- end
85
- end
83
+ private
86
84
 
87
- def without_state_transition_tracking(namespace = '')
88
- _integration(namespace).without_transition_tracking do
89
- yield self
90
- end
91
- end
85
+ # core methods
86
+ def _stator(namespace = nil)
87
+ self.class._stator(namespace)
88
+ end
92
89
 
93
- protected
90
+ def _stator_namespace(namespace = nil)
91
+ self.class._stator_namespace(namespace)
92
+ end
94
93
 
95
- def _stator_validate_transition
96
- self._stators.each_key do |namespace|
97
- _integration(namespace).validate_transition
98
- end
99
- end
94
+ def _stator_integration(namespace = nil)
95
+ ns = _stator_namespace(namespace)
100
96
 
101
- def _stator(namespace = '')
102
- self.class._stator(namespace)
97
+ self._stator_integrations ||= {}
98
+ self._stator_integrations[ns] ||= self.class._stator(ns).integration(self)
99
+ end
100
+
101
+ # validation/transitional
102
+ def _stator_validate_transition
103
+ self._stators.each_key do |namespace|
104
+ _stator_integration(namespace).validate_transition
103
105
  end
106
+ end
107
+
108
+ def _stator_maybe_track_transition
109
+ self._stators.each do |namespace, machine|
110
+ next unless machine.tracking_enabled?
104
111
 
105
- def _integration(namespace = '')
106
- @_integrations ||= {}
107
- @_integrations[namespace] ||= _stator(namespace).integration(self)
108
- @_integrations[namespace]
112
+ _stator_integration(namespace).track_transition
109
113
  end
110
114
 
115
+ true
111
116
  end
112
117
  end
113
118
  end
@@ -1,44 +1,39 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Stator
2
4
  class Transition
3
-
4
- ANY = '__any__'
5
-
6
- attr_reader :name
7
- attr_reader :full_name
5
+ attr_reader :namespace, :name, :attr_name, :from_states, :to_state, :class_name, :callbacks
8
6
 
9
7
  def initialize(class_name, name, namespace = nil)
10
- @class_name = class_name
11
- @name = name
12
- @namespace = namespace
13
- @full_name = [@namespace, @name].compact.join('_') if @name
14
- @froms = []
15
- @to = nil
16
- @callbacks = {}
8
+ @class_name = class_name
9
+ @name = name&.to_sym
10
+ @namespace = namespace&.to_sym
11
+ @from_states = []
12
+ @to_state = nil
13
+ @callbacks = {}
17
14
  end
18
15
 
19
- def from(*froms)
20
- @froms |= froms.map{|f| f.try(:to_s) } # nils are ok
16
+ def attr_name
17
+ @attr_name ||= generate_attr_name
21
18
  end
22
19
 
23
- def to(to)
24
- @to = to.to_s
20
+ def from_states(*new_froms)
21
+ @from_states |= new_froms
25
22
  end
23
+ alias from from_states
26
24
 
27
- def to_state
28
- @to
29
- end
30
-
31
- def from_states
32
- @froms
25
+ def to(new_to)
26
+ @to_state = new_to
33
27
  end
34
28
 
35
29
  def can?(current_state)
36
- @froms.include?(current_state) || @froms.include?(ANY) || current_state == ANY
30
+ from_states.include?(current_state) || from_states.include?(Stator::ANY) || current_state == Stator::ANY
37
31
  end
38
32
 
39
- def valid?(from, to)
40
- can?(from) &&
41
- (@to == to || @to == ANY || to == ANY)
33
+ def valid?(from_check, to_check)
34
+ from_check = from_check&.to_sym # coming from the database, i suspect
35
+
36
+ can?(from_check) && (to_check == to_state || to_check == ANY || to_state == ANY)
42
37
  end
43
38
 
44
39
  def conditional(options = {}, &block)
@@ -46,79 +41,85 @@ module Stator
46
41
  end
47
42
 
48
43
  def any
49
- ANY
44
+ Stator::ANY
50
45
  end
51
46
 
52
47
  def evaluate
53
- generate_methods unless @full_name.blank?
48
+ generate_methods if attr_name.present?
54
49
  end
55
50
 
56
- protected
51
+ private
57
52
 
58
53
  def klass
59
- @class_name.constantize
54
+ class_name.constantize
55
+ end
56
+
57
+ def generate_attr_name
58
+ if namespace == Stator.default_namespace
59
+ name
60
+ else
61
+ [namespace, name].compact.join('_').to_sym
62
+ end
60
63
  end
61
64
 
62
65
  def callbacks(kind)
63
- @callbacks[kind] || []
66
+ callbacks[kind] || []
64
67
  end
65
68
 
66
69
  def conditional_block(options = {})
67
70
  options[:use_previous] ||= false
68
71
 
69
- _namespace = @namespace
70
- _froms = @froms
71
- _to = @to
72
-
73
- Proc.new do
74
- (
75
- self._stator(_namespace).integration(self).state_changed?(options[:use_previous])
76
- ) && (
77
- _froms.include?(self._stator(_namespace).integration(self).state_was(options[:use_previous])) ||
78
- _froms.include?(::Stator::Transition::ANY)
79
- ) && (
80
- self._stator(_namespace).integration(self).state == _to ||
81
- _to == ::Stator::Transition::ANY
82
- )
72
+ _namespace = namespace
73
+ _froms = from_states
74
+ _to = to_state
75
+
76
+ proc do
77
+ integration = self.class._stator(_namespace).integration(self)
78
+
79
+ integration.state_changed?(options[:use_previous]) &&
80
+ _froms.include?(integration.state_was(options[:use_previous])) ||
81
+ _froms.include?(Stator::ANY) &&
82
+ integration.state == _to || _to == Stator::ANY
83
83
  end
84
84
  end
85
85
 
86
86
  def generate_methods
87
87
  klass.class_eval <<-EV, __FILE__, __LINE__ + 1
88
- def #{@full_name}(should_save = true)
89
- integration = _integration(#{@namespace.to_s.inspect})
88
+ def #{attr_name}(should_save = true)
89
+ integration = _stator_integration(:#{namespace})
90
90
 
91
- unless can_#{@full_name}?
92
- integration.invalid_transition!(integration.state, #{@to.inspect}) if should_save
91
+ unless can_#{attr_name}?
92
+ integration.invalid_transition!(integration.state, :#{to_state}) if should_save
93
93
  return false
94
94
  end
95
95
 
96
- integration.state = #{@to.inspect}
96
+ integration.state = :#{to_state}
97
+
97
98
  self.save if should_save
98
99
  end
99
100
 
100
- def #{@full_name}!
101
- integration = _integration(#{@namespace.to_s.inspect})
101
+ def #{attr_name}!
102
+ integration = _stator_integration(:#{namespace})
102
103
 
103
- unless can_#{@full_name}?
104
- integration.invalid_transition!(integration.state, #{@to.inspect})
104
+ unless can_#{attr_name}?
105
+ integration.invalid_transition!(integration.state, :#{to_state})
105
106
  raise ActiveRecord::RecordInvalid.new(self)
106
107
  end
107
108
 
108
- integration.state = #{@to.inspect}
109
+ integration.state = :#{to_state}
109
110
  self.save!
110
111
  end
111
112
 
112
- def can_#{@full_name}?
113
- integration = _integration(#{@namespace.to_s.inspect})
113
+ def can_#{attr_name}?
114
+ integration = _stator_integration(:#{namespace})
114
115
  return true if integration.skip_validations
115
116
 
116
- machine = self._stator(#{@namespace.to_s.inspect})
117
- transition = machine.transitions.detect{|t| t.full_name.to_s == #{@full_name.inspect}.to_s }
117
+ machine = self._stator(:#{namespace})
118
+ transition = machine.transitions.detect { |t| t.attr_name == :#{attr_name} }
119
+
118
120
  transition.can?(integration.state)
119
121
  end
120
122
  EV
121
123
  end
122
-
123
124
  end
124
125
  end
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stator
4
-
5
4
  MAJOR = 0
6
- MINOR = 4
5
+ MINOR = 9
7
6
  PATCH = 0
8
- PRERELEASE = nil
9
-
10
- VERSION = [MAJOR, MINOR, PATCH, PRERELEASE].compact.join(".")
7
+ PRERELEASE = "beta"
11
8
 
9
+ VERSION = [MAJOR, MINOR, PATCH, PRERELEASE].compact.join('.')
12
10
  end
data/lib/stator.rb CHANGED
@@ -1,6 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'stator/version'
2
4
  require 'stator/alias'
3
5
  require 'stator/integration'
4
6
  require 'stator/machine'
5
7
  require 'stator/model'
6
8
  require 'stator/transition'
9
+
10
+ require 'active_support/concern'
11
+ require 'debug'
12
+
13
+ module Stator
14
+ ANY = :__ANY__
15
+
16
+ def self.default_namespace
17
+ ENV.fetch('STATOR_NAMESPACE', :default).to_sym
18
+ end
19
+ end