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.
@@ -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
@@ -0,0 +1,3 @@
1
+ module StateTracking
2
+ VERSION = '1.1.2'
3
+ end
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: []