active_archive 5.3.0 → 6.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5506e5337187ed5218702b644331efa366c60c44aec95f04825a53961915a138
4
- data.tar.gz: 347f68b2b28c200090dbda43423bfc8647757b4d992b2f3b4e97d91dc8820a30
3
+ metadata.gz: fbe2d37d4d70cc6930e4b7fea4f2248418375434cd4d7a704327214e61b36eb5
4
+ data.tar.gz: 44bcde3d05e859deaf2839356c2c13258fe73f47225177587777f1ca18483400
5
5
  SHA512:
6
- metadata.gz: a73ad42afa55fee0e6628d82e0e484ed8064e2eed19a80f053a47a35d13e21086f82d4fed1771a14c445aec653d342f67f3927abf3e2e17797b847d9404228b2
7
- data.tar.gz: '096868b1dc4e290062767e4b7574fe90512933b22c710964ff980b5d61c9e781123bd2742edc64a853a83e4f371b3687c117ce02de2aa742b9c6875a66d408de'
6
+ metadata.gz: 711465df49e3c0f94edc7d0db2231ef2c2dda02f41cb494e6b78de4b2b5ccda7efb73156fc81f322c67359d4f2a7c9568ee00447951bbdf2683b1d209192358b
7
+ data.tar.gz: 849e0c7fed67835e44a7d54213c39fb3793001e5b2531c380ed370bf4ee073d7a0fac7bd5d67640c05c6616186cbfdf6c505357ed4b6aae6d82978fefea8f93d
data/README.md CHANGED
@@ -5,6 +5,8 @@
5
5
 
6
6
  ActiveArchive is a library for preventing database records from being destroyed.
7
7
 
8
+ **NOTE: version >= '6.0.0' has a small breaking change with the timestamp key and initializer file.**
9
+
8
10
  ## Installation
9
11
 
10
12
  Add this line to your application's Gemfile:
@@ -24,6 +26,7 @@ Or install it yourself as:
24
26
  ## Table of Contents
25
27
 
26
28
  * [Configurations](#configurations)
29
+ * [Usage](#usage)
27
30
  * [Methods](#methods)
28
31
  * [Scopes](#scopes)
29
32
 
@@ -35,7 +38,27 @@ Or install it yourself as:
35
38
  ```ruby
36
39
  ActiveArchive.configure do |config|
37
40
  config.all_records_archivable = false
38
- config.dependent_record_window = 3.seconds
41
+ end
42
+ ```
43
+
44
+ ## Usage
45
+ To work properly, models which could be archived must have column `archived_at` with type `datetime`. If the model table has not this column, record will be destroy instead of archived.
46
+
47
+
48
+ For adding this column, you can use this migration, as example:
49
+
50
+ ```ruby
51
+ class AddArchivedAtColumns < ActiveRecord::Migration
52
+ def change
53
+ # Adds archived_at automatically
54
+ t.timestamp
55
+
56
+ # Does NOT add archived_at automatically
57
+ t.timestamp archive: false
58
+
59
+ # Manual column
60
+ add_column :your_model, :archived_at, :datetime
61
+ end
39
62
  end
40
63
  ```
41
64
 
@@ -44,43 +67,26 @@ end
44
67
  **Options:**
45
68
  * `archive`
46
69
  * `archive_all`
47
- * `archive_all!`
48
- * `destroy`
49
- * `destroy_all`
50
- * `destroy_all!`
51
- * `delete`
52
- * `delete_all`
53
70
  * `unarchive`
54
71
  * `unarchive_all`
55
- * `undestroy`
56
- * `undestroy_all`
57
72
 
58
73
  ```ruby
59
74
  User.first.archive #=> archives User record and dependents
60
- User.first.destroy #=> archives User record and dependents
61
- User.first.delete #=> deletes User record and dependents
62
75
  User.first.unarchive #=> unarchives User record and dependents
63
76
 
64
77
  User.first.to_archival #=> returns archival state string
65
78
 
66
- User.first.archive(:force) #=> destroys User record and dependents
67
- User.first.destroy(:force) #=> destroys User record and dependents
68
-
69
79
  User.archive_all #=> archives all User records and dependents
70
- User.destroy_all #=> archives all User records and dependents
71
- User.delete_all #=> deletes all User records and dependents
72
80
  User.unarchive_all #=> unarchives all User record and dependents
73
81
  ```
74
82
 
75
83
  ## Scopes
76
84
 
77
85
  **Options:**
78
- * `default`
79
86
  * `archived`
80
87
  * `unarchived`
81
88
 
82
89
  ```ruby
83
- User.all #=> returns all records
84
90
  User.archived.all #=> returns only archived record
85
91
  User.unarchived.all #=> returns only unarchived record
86
92
  ```
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # require "active_record/attribute_mutation_tracker"
4
-
5
3
  module ActiveArchive
6
4
  module Base
7
5
 
@@ -9,7 +7,16 @@ module ActiveArchive
9
7
  base.extend Methods
10
8
  base.extend Scopes
11
9
 
12
- base.instance_eval { define_model_callbacks(:unarchive) }
10
+ base.instance_eval do
11
+ define_model_callbacks :archive, only: %i[before after]
12
+ define_model_callbacks :unarchive, only: %i[before after]
13
+ end
14
+ end
15
+
16
+ def archival?
17
+ return destroyed? if unarchivable?
18
+
19
+ (will_save_change_to_archived_at? || saved_change_to_archived_at?) && archived?
13
20
  end
14
21
 
15
22
  def archivable?
@@ -20,230 +27,113 @@ module ActiveArchive
20
27
  archivable? ? !archived_at.nil? : destroyed?
21
28
  end
22
29
 
23
- def unarchived?
24
- !archived?
25
- end
30
+ def archive
31
+ return destroy if unarchivable?
26
32
 
27
- def unarchivable?
28
- !archivable?
29
- end
30
-
31
- def unarchive(opts = nil)
32
33
  with_transaction_returning_status do
33
- records = should_unarchive_parent_first?(opts) ? unarchival.reverse : unarchival
34
- records.each { |rec| rec.call(opts) }
35
-
36
- self
37
- end
38
- end
34
+ run_callbacks :archive do
35
+ mark_as_archived
36
+ mark_relections_as_archived
39
37
 
40
- alias_method :undestroy, :unarchive
41
-
42
- def destroy(force = nil)
43
- with_transaction_returning_status do
44
- if unarchivable? || should_force_destroy?(force)
45
- permanently_delete_records_after { super() }
46
- else
47
- destroy_with_active_archive(force)
48
-
49
- if ::ActiveRecord::VERSION::MAJOR >= 5
50
- archived_at_will_change!
51
- elsif ::ActiveRecord::VERSION::MAJOR >= 4
52
- attribute_will_change!('archived_at')
53
- end
38
+ self
54
39
  end
55
40
  end
56
41
  end
57
42
 
58
- alias_method :archive, :destroy
43
+ def unarchival?
44
+ return !destroyed? if unarchivable?
59
45
 
60
- def to_archival
61
- I18n.t("active_archive.archival.#{archived? ? :archived : :unarchived}")
46
+ (will_save_change_to_archived_at? || saved_change_to_archived_at?) && unarchived?
62
47
  end
63
48
 
64
- private
65
-
66
- def unarchival
67
- [
68
- lambda do |validate|
69
- unarchive_destroyed_dependent_records(validate)
70
- end,
71
- lambda do |validate|
72
- run_callbacks(:unarchive) do
73
- set_archived_at(nil, validate)
74
-
75
- each_counter_cache do |assoc_class, counter_cache_column, assoc_id|
76
- assoc_class.increment_counter(counter_cache_column, assoc_id)
77
- end
78
-
79
- true
80
- end
81
- end
82
- ]
49
+ def unarchived?
50
+ !archived?
83
51
  end
84
52
 
85
- def get_archived_record
86
- self.class.unscoped.find(id)
53
+ def unarchivable?
54
+ !archivable?
87
55
  end
88
56
 
89
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
90
- def set_archived_at(value, force = nil)
91
- return self unless archivable?
92
-
93
- record = get_archived_record
94
-
95
- if ::ActiveRecord::VERSION::MAJOR >= 5
96
- record.archived_at_will_change!
97
- elsif ::ActiveRecord::VERSION::MAJOR >= 4
98
- record.attribute_will_change!('archived_at')
99
- end
57
+ def unarchive
58
+ return self if unarchivable?
100
59
 
101
- record.archived_at = value
102
-
103
- begin
104
- should_ignore_validations?(force) ? record.save(validate: false) : record.save!
60
+ with_transaction_returning_status do
61
+ run_callbacks :unarchive do
62
+ mark_as_unarchived
63
+ mark_relections_as_unarchived
105
64
 
106
- if ::ActiveRecord::VERSION::MAJOR >= 5 && ::ActiveRecord::VERSION::MINOR >= 2
107
- @attributes_changed_by_setter = record.send(:saved_changes)
108
- elsif ::ActiveRecord::VERSION::MAJOR >= 5 && ::ActiveRecord::VERSION::MINOR >= 1
109
- @changed_attributes = record.send(:saved_changes)
110
- elsif ::ActiveRecord::VERSION::MAJOR >= 5 && ::ActiveRecord::VERSION::MINOR >= 0
111
- @changed_attributes = record.send(:previous_changes)
112
- elsif ::ActiveRecord::VERSION::MAJOR >= 4
113
- @previously_changed = record.instance_variable_get('@previously_changed')
65
+ self
114
66
  end
115
-
116
- @attributes = record.instance_variable_get('@attributes')
117
- rescue => error
118
- record.destroy
119
- raise error
120
67
  end
121
68
  end
122
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
123
-
124
- def each_counter_cache
125
- _reflections.each do |name, reflection|
126
- next unless respond_to?(name.to_sym)
127
69
 
128
- association = send(name.to_sym)
129
-
130
- next if association.nil?
131
- next unless reflection.belongs_to? && reflection.counter_cache_column
132
-
133
- yield(association.class, reflection.counter_cache_column, send(reflection.foreign_key))
134
- end
70
+ def to_archival
71
+ I18n.t("active_archive.archival.#{archived? ? :archived : :unarchived}")
135
72
  end
136
73
 
137
- def destroy_with_active_archive(force = nil)
138
- run_callbacks(:destroy) do
139
- if archived? || new_record?
140
- save
141
- else
142
- set_archived_at(Time.now, force)
143
-
144
- each_counter_cache do |assoc_class, counter_cache_column, assoc_id|
145
- assoc_class.decrement_counter(counter_cache_column, assoc_id)
146
- end
147
- end
148
-
149
- true
150
- end
74
+ private
151
75
 
152
- archived? ? self : false
76
+ def mark_as_archived
77
+ self.archived_at = Time.now
78
+ save(validate: false)
153
79
  end
154
80
 
155
- def add_record_window(_request, name, reflection)
156
- qtn = reflection.table_name
157
- window = ActiveArchive.configuration.dependent_record_window
158
- query = "#{qtn}.archived_at > ? AND #{qtn}.archived_at < ?"
159
-
160
- send(name).unscope(where: :archived_at)
161
- .where(query, archived_at - window, archived_at + window)
81
+ def mark_as_unarchived
82
+ self.archived_at = nil
83
+ save(validate: false)
162
84
  end
163
85
 
164
- def unarchive_destroyed_dependent_records(force = nil)
165
- destroyed_dependent_relations.each do |relation|
166
- relation.to_a.each do |destroyed_dependent_record|
167
- destroyed_dependent_record.try(:unarchive, force)
168
- end
169
- end
170
-
171
- reload
172
- end
173
-
174
- # rubocop:disable Metrics/AbcSize
175
- def destroyed_dependent_relations
176
- dependent_permanent_reflections(self.class).map do |name, relation|
177
- case relation.macro.to_s.gsub('has_', '').to_sym
178
- when :many
179
- if archived_at
180
- add_record_window(send(name), name, relation)
181
- else
182
- send(name).unscope(where: :archived_at)
183
- end
184
- when :one, :belongs_to
185
- self.class.unscoped { Array(send(name)) }
186
- end
187
- end
188
- end
189
- # rubocop:enable Metrics/AbcSize
86
+ def mark_relections_as_archived
87
+ self.class.reflections.each do |table_name, reflection|
88
+ next unless dependent_destroy?(reflection)
190
89
 
191
- def attempt_notifying_observers(callback)
192
- notify_observers(callback)
193
- rescue NoMethodError
194
- # do nothing
195
- end
90
+ dependents = reflection_dependents(table_name)
91
+ next if dependents.nil?
196
92
 
197
- def dependent_record_ids
198
- dependent_reflections(self.class).reduce({}) do |records, (key, _)|
199
- found = Array(send(key)).compact
200
- next records if found.empty?
93
+ action = case [reflection_marco(reflection), archivable?]
94
+ when [:one, true] then :archive
95
+ when [:one, false] then :destroy
96
+ when [:many, true] then :archive_all
97
+ when [:many, false] then :destroy_all
98
+ end
201
99
 
202
- records.update(found.first.class => found.map(&:id))
100
+ dependents.send(action)
203
101
  end
204
102
  end
205
103
 
206
- def permanently_delete_records_after(&block)
207
- dependent_records = dependent_record_ids
208
- result = yield(block)
209
- permanently_delete_records(dependent_records) if result
210
- result
211
- end
104
+ def mark_relections_as_unarchived
105
+ self.class.reflections.each do |table_name, reflection|
106
+ next unless dependent_destroy?(reflection)
212
107
 
213
- def permanently_delete_records(dependent_records)
214
- dependent_records.each do |klass, ids|
215
- ids.each do |id|
216
- record = klass.unscoped.where(klass.primary_key => id).first
217
- next unless record
108
+ klass = relection_klass(table_name)
109
+ next unless klass.archivable?
218
110
 
219
- record.archived_at = nil
220
- record.destroy(:force)
221
- end
222
- end
223
- end
111
+ dependents = reflection_dependents(table_name)
112
+ next if dependents.nil?
113
+
114
+ action = case reflection_marco(reflection)
115
+ when :one then :unarchive
116
+ when :many then :unarchive_all
117
+ end
224
118
 
225
- def dependent_reflections(klass)
226
- klass.reflections.select do |_, reflection|
227
- reflection.options[:dependent] == :destroy
119
+ dependents.send(action)
228
120
  end
229
121
  end
230
122
 
231
- def dependent_permanent_reflections(klass)
232
- dependent_reflections(klass).select do |_name, reflection|
233
- reflection.klass.archivable?
234
- end
123
+ def dependent_destroy?(reflection)
124
+ reflection.options[:dependent] == :destroy
235
125
  end
236
126
 
237
- def should_force_destroy?(force)
238
- force.is_a?(Hash) ? force[:force] : (force == :force)
127
+ def reflection_dependents(table_name)
128
+ send(table_name)
239
129
  end
240
130
 
241
- def should_ignore_validations?(force)
242
- force.is_a?(Hash) && (force[:validate] == false)
131
+ def relection_klass(table_name)
132
+ table_name.classify.constantize
243
133
  end
244
134
 
245
- def should_unarchive_parent_first?(order)
246
- order.is_a?(Hash) && (order[:reverse] == true)
135
+ def reflection_marco(reflection)
136
+ reflection.macro.to_s.gsub('has_', '').to_sym
247
137
  end
248
138
 
249
139
  end
@@ -3,11 +3,10 @@
3
3
  module ActiveArchive
4
4
  class Configuration
5
5
 
6
- attr_accessor :all_records_archivable, :dependent_record_window
6
+ attr_accessor :all_records_archivable
7
7
 
8
8
  def initialize
9
9
  @all_records_archivable = false
10
- @dependent_record_window = 3.seconds
11
10
  end
12
11
 
13
12
  end
@@ -8,20 +8,18 @@ module ActiveArchive
8
8
  end
9
9
 
10
10
  def archive_all(conditions = nil)
11
- (conditions ? where(conditions) : all).to_a.each(&:archive)
11
+ records(conditions, action: :archive)
12
12
  end
13
13
 
14
- def archive_all!(conditions = nil)
15
- (conditions ? where(conditions) : all).to_a.each { |r| r.send(:archive, :force) }
14
+ def unarchive_all(conditions = nil)
15
+ records(conditions, action: :unarchive)
16
16
  end
17
17
 
18
- alias_method :destroy_all!, :archive_all!
18
+ private
19
19
 
20
- def unarchive_all(conditions = nil)
21
- (conditions ? where(conditions) : all).to_a.each(&:unarchive)
20
+ def records(conditions = nil, action:)
21
+ where(conditions).find_each(&action)
22
22
  end
23
23
 
24
- alias_method :undestroy_all, :unarchive_all
25
-
26
24
  end
27
25
  end
@@ -10,7 +10,7 @@ module ActiveArchive
10
10
  column(:updated_at, :datetime, options)
11
11
 
12
12
  return unless ActiveArchive.configuration.all_records_archivable == true
13
- return if options[:skip]
13
+ return if options[:archive] == false
14
14
 
15
15
  options[:null] = true
16
16
  column(:archived_at, :datetime, options)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveArchive
4
- VERSION ||= '5.3.0'
4
+ VERSION ||= '6.0.0'
5
5
  end
@@ -2,5 +2,4 @@
2
2
 
3
3
  ActiveArchive.configure do |config|
4
4
  config.all_records_archivable = false
5
- config.dependent_record_window = 3.seconds
6
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_archive
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.3.0
4
+ version: 6.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juan Gomez
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-09-13 00:00:00.000000000 Z
11
+ date: 2018-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails