active_archive 5.3.0 → 6.0.0

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