state_tracking 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|