state_tracking 1.1.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.
- checksums.yaml +7 -0
- data/lib/state_tracking.rb +196 -0
- data/lib/state_tracking/version.rb +3 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 11aa8ecf4c278e2618469d390459ea75bcc516ba
|
4
|
+
data.tar.gz: c25b31ba007c525863dd5eb800dd62424fdc1d3c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9d819427660d35ec34f5b367312f4abe65a6b5848dace1f7e9c2fcf19f5e03296bfc1f5808b7befac7871311b8fc640d7550808872848f8a439746a2968f099f
|
7
|
+
data.tar.gz: 7a566c7ead46c80421c78bb6f150680df71cb4a55f4443eeac8f18801d60355aa4a53fe24b94ad41c070728dc87191946524cf56fa89a16740509765f9fd703d
|
@@ -0,0 +1,196 @@
|
|
1
|
+
require 'aasm'
|
2
|
+
require 'date'
|
3
|
+
require 'active_support/all'
|
4
|
+
|
5
|
+
require 'state_tracking/version'
|
6
|
+
|
7
|
+
module StateTracking
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
include ::AASM
|
12
|
+
before_save :_state_tracking_save
|
13
|
+
end
|
14
|
+
|
15
|
+
class StateTrackingProc < Proc; end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def state_tracking(options={}, &block)
|
19
|
+
# We're relying on the fact that the user is not able to set the
|
20
|
+
# skip_validation_on_save configuration value to true.
|
21
|
+
#
|
22
|
+
# If that's set, then the state_tracking column won't be updated.
|
23
|
+
aasm(column: :state, &block)
|
24
|
+
|
25
|
+
aasm.states.each do |state|
|
26
|
+
callbacks = state.options[:enter] ||= []
|
27
|
+
|
28
|
+
unless callbacks.is_a?(Array)
|
29
|
+
callbacks = state.options[:enter] = [callbacks]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Need to make sure we don't add multiple Procs. For example, if the
|
33
|
+
# classes are reloaded in a development environment.
|
34
|
+
#
|
35
|
+
# Note: Using is_a? doesn't work. It doesn't recognize previously added
|
36
|
+
# StateTracking::StateTrackingProc as actually being instances of
|
37
|
+
# StateTracking::StateTrackingProc, so I have to compare by name.
|
38
|
+
unless callbacks.any?{|c| c.class.name == 'StateTracking::StateTrackingProc'}
|
39
|
+
callbacks << StateTrackingProc.new { |record|
|
40
|
+
record.send(:_state_tracking_record_state_change, state.name)
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
_override_aasm_events
|
46
|
+
|
47
|
+
# This is required because the instance methods need to override
|
48
|
+
# the instance methods that AASM adds. If the instance methods
|
49
|
+
# are simply defined within the StateTracking module, then they
|
50
|
+
# get defined first, and then the AASM methods override the
|
51
|
+
# StateTracking methods, which is the opposite of what we want.
|
52
|
+
self.send(:include, InstanceMethods)
|
53
|
+
|
54
|
+
if options[:fire_events]
|
55
|
+
@_state_tracking_fire_states = options[:fire_events] if options[:fire_events].is_a?(Array)
|
56
|
+
|
57
|
+
self.class_eval do
|
58
|
+
# Note: We're defining instance methods here.
|
59
|
+
after_commit :_state_tracking_fire_events
|
60
|
+
|
61
|
+
def _state_tracking_fire_states
|
62
|
+
self.class.instance_variable_get(:@_state_tracking_fire_states)
|
63
|
+
end
|
64
|
+
|
65
|
+
def _state_tracking_fire_events
|
66
|
+
(@_state_tracking_events_to_fire || []).each do |state|
|
67
|
+
next if _state_tracking_fire_states &&
|
68
|
+
!_state_tracking_fire_states.include?(state.to_sym)
|
69
|
+
|
70
|
+
primary_key = self.class.primary_key
|
71
|
+
name_underscored = self.class.name.underscore
|
72
|
+
pkey_value = send(primary_key)
|
73
|
+
event_name = "#{name_underscored}_#{state}"
|
74
|
+
|
75
|
+
unless pkey_value
|
76
|
+
raise "Can't publish event (#{event_name.inspect}). " \
|
77
|
+
"Primary key (#{primary_key.inspect}) for " \
|
78
|
+
"#{self.class.name.inspect} is nil."
|
79
|
+
end
|
80
|
+
|
81
|
+
params = {
|
82
|
+
"#{name_underscored}_#{primary_key}" => pkey_value
|
83
|
+
}
|
84
|
+
|
85
|
+
Announcer.publish(event_name, params)
|
86
|
+
end
|
87
|
+
|
88
|
+
@_state_tracking_events_to_fire = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def _state_tracking_record_state_change(state)
|
92
|
+
super
|
93
|
+
(@_state_tracking_events_to_fire ||= []) << state.to_s
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def _override_aasm_events
|
100
|
+
aasm.events.each do |name, *args|
|
101
|
+
["#{name}!", "#{name}"].each { |meth|
|
102
|
+
previous = instance_method(meth)
|
103
|
+
|
104
|
+
define_method(meth) { |*args, &block|
|
105
|
+
_state_tracking_stash_meta(args.last)
|
106
|
+
|
107
|
+
previous.bind(self).(nil, *args, &block)
|
108
|
+
}
|
109
|
+
}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end # Class Methods
|
113
|
+
|
114
|
+
module InstanceMethods
|
115
|
+
def state_history(state_name = nil)
|
116
|
+
history = _state_tracking_data['history'] || []
|
117
|
+
|
118
|
+
if state_name
|
119
|
+
timestamp = nil
|
120
|
+
|
121
|
+
# Find most recent time when the object was in the specified state.
|
122
|
+
history.reverse_each do |record|
|
123
|
+
if record['state'] == state_name.to_s
|
124
|
+
timestamp = record['time']
|
125
|
+
break
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
timestamp
|
130
|
+
else
|
131
|
+
history
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def was?(state_name)
|
136
|
+
!state_history(state_name).nil?
|
137
|
+
end
|
138
|
+
|
139
|
+
def state_at(time)
|
140
|
+
unless time.is_a?(Date) || time.is_a?(Time) || time.is_a?(DateTime)
|
141
|
+
raise ArgumentError, "invalid time: #{time}"
|
142
|
+
end
|
143
|
+
|
144
|
+
# Get the most recent state up to the time param
|
145
|
+
state_obj = state_history.find_all { |state_obj|
|
146
|
+
state_obj['time'] <= time.to_time.to_f
|
147
|
+
}.last
|
148
|
+
|
149
|
+
state_obj && state_obj['state']
|
150
|
+
end
|
151
|
+
|
152
|
+
def update_state_meta(meta={})
|
153
|
+
state_history = _state_tracking_data['history'].last
|
154
|
+
raise "No current state" unless state_history
|
155
|
+
state_history['meta'].merge!(meta)
|
156
|
+
end
|
157
|
+
|
158
|
+
def _state_tracking_stash_meta(meta={})
|
159
|
+
@_state_tracking_pending_meta = meta.is_a?(Hash) ? meta : nil
|
160
|
+
end
|
161
|
+
|
162
|
+
def _state_tracking_data
|
163
|
+
@__state_tracking_data ||= self.state_tracking ? JSON.parse(self.state_tracking) : {}
|
164
|
+
end
|
165
|
+
|
166
|
+
def _state_tracking_save
|
167
|
+
if @_state_tracking_data_changed
|
168
|
+
self.state_tracking = JSON.dump(_state_tracking_data)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def _state_tracking_record_state_change(state)
|
173
|
+
unless self.state || state == self.class.aasm.initial_state
|
174
|
+
# Want to make sure that we record entering the initial state
|
175
|
+
# in the history even if a subsequent state is being entered
|
176
|
+
# before creating the instance.
|
177
|
+
self.aasm.enter_initial_state
|
178
|
+
end
|
179
|
+
|
180
|
+
@_state_tracking_data_changed = true
|
181
|
+
|
182
|
+
(_state_tracking_data['history'] ||= []) << {
|
183
|
+
'state' => state.to_s,
|
184
|
+
'time' => Time.now.to_f,
|
185
|
+
'meta' => {}
|
186
|
+
}
|
187
|
+
|
188
|
+
# We don't want to add stashed metadata to the initial state. Otherwise,
|
189
|
+
# there will be duplicate metadata in the intial_state and the current
|
190
|
+
# state. We will add initial_state metadata support at a later date
|
191
|
+
if state != self.class.aasm.initial_state && @_state_tracking_pending_meta
|
192
|
+
update_state_meta(@_state_tracking_pending_meta)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end # InstanceMethods
|
196
|
+
end # StateTracking
|
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: state_tracking
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Robert Honer
|
8
|
+
- Kayvon Ghaffari
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-01-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: aasm
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 3.3.2
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 3.3.2
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
requirements:
|
28
|
+
- - "~>"
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 3.3.2
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.3.2
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rails
|
36
|
+
requirement: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.0'
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 4.0.0
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - "~>"
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '4.0'
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 4.0.0
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: announcer
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0.5'
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 0.5.2
|
64
|
+
type: :runtime
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - "~>"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0.5'
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 0.5.2
|
74
|
+
- !ruby/object:Gem::Dependency
|
75
|
+
name: rspec
|
76
|
+
requirement: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
type: :development
|
82
|
+
prerelease: false
|
83
|
+
version_requirements: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
- !ruby/object:Gem::Dependency
|
89
|
+
name: database_cleaner
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
type: :development
|
96
|
+
prerelease: false
|
97
|
+
version_requirements: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: rspec-rails
|
104
|
+
requirement: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
type: :development
|
110
|
+
prerelease: false
|
111
|
+
version_requirements: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
- !ruby/object:Gem::Dependency
|
117
|
+
name: sqlite3
|
118
|
+
requirement: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
type: :development
|
124
|
+
prerelease: false
|
125
|
+
version_requirements: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
description: ''
|
131
|
+
email: engineering@payout.com
|
132
|
+
executables: []
|
133
|
+
extensions: []
|
134
|
+
extra_rdoc_files: []
|
135
|
+
files:
|
136
|
+
- lib/state_tracking.rb
|
137
|
+
- lib/state_tracking/version.rb
|
138
|
+
homepage: http://github.com/payout/state_tracking
|
139
|
+
licenses: []
|
140
|
+
metadata: {}
|
141
|
+
post_install_message:
|
142
|
+
rdoc_options: []
|
143
|
+
require_paths:
|
144
|
+
- lib
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
requirements: []
|
156
|
+
rubyforge_project:
|
157
|
+
rubygems_version: 2.4.3
|
158
|
+
signing_key:
|
159
|
+
specification_version: 4
|
160
|
+
summary: Provides the StateTracking mixin.
|
161
|
+
test_files: []
|