state_tracking 1.1.2

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