no_fly_list 0.6.0 → 0.7.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 +4 -4
- data/lib/no_fly_list/railties/tasks.rake +5 -5
- data/lib/no_fly_list/tagging_proxy.rb +96 -50
- data/lib/no_fly_list/task_helpers.rb +6 -7
- data/lib/no_fly_list/version.rb +1 -1
- metadata +3 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a83b9c51a6624a462debf10d93389d093ab732e9b7e836ee90bde0f1ae5239e5
|
4
|
+
data.tar.gz: 88e96def76873eaa415431ce3ddb05076f59234d68583bad4d8c4f298fe9c0c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28621b0b5ed052c16667b41c8398a0533d16b631cc31944a4d48f0f468da34a798e603b568435579afd1b07dae2a11ee9a7276d6b31f2a37baf9212aab5fa80a
|
7
|
+
data.tar.gz: 8740278135b296f312a8ef5189ab25d95c3e6428f27c286b7d10d5d359197e52280682c9adeed0305859663f6d90b4d5596465274d3eb54e5c0893326d305110
|
@@ -20,7 +20,7 @@ namespace :no_fly_list do
|
|
20
20
|
|
21
21
|
classes.each do |klass|
|
22
22
|
color = NoFlyList::TaskHelpers.adapter_color(klass)
|
23
|
-
type = klass.included_modules.include?(NoFlyList::ApplicationTag) ?
|
23
|
+
type = klass.included_modules.include?(NoFlyList::ApplicationTag) ? "Global" : "Model-specific"
|
24
24
|
|
25
25
|
puts "#{color}#{klass.name}#{NoFlyList::TaskHelpers::COLORS[:reset]}"
|
26
26
|
puts " Type: #{type}"
|
@@ -42,8 +42,8 @@ namespace :no_fly_list do
|
|
42
42
|
puts " #{message}"
|
43
43
|
|
44
44
|
[
|
45
|
-
["#{klass.name}Tag", "Tags", :tag],
|
46
|
-
["#{klass.name}::Tagging", "Taggings", :tagging]
|
45
|
+
[ "#{klass.name}Tag", "Tags", :tag ],
|
46
|
+
[ "#{klass.name}::Tagging", "Taggings", :tagging ]
|
47
47
|
].each do |class_name, type, column_type|
|
48
48
|
if (check_class = NoFlyList::TaskHelpers.check_class(class_name))
|
49
49
|
status, message = NoFlyList::TaskHelpers.check_table(check_class)
|
@@ -53,13 +53,13 @@ namespace :no_fly_list do
|
|
53
53
|
puts " #{NoFlyList::TaskHelpers.format_columns(check_class)}"
|
54
54
|
end
|
55
55
|
else
|
56
|
-
puts " #{NoFlyList::TaskHelpers
|
56
|
+
puts " #{NoFlyList::TaskHelpers.colorize('✗', :red)} #{type} class not found: #{class_name}"
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
60
|
klass._no_fly_list.tag_contexts.each do |context, config|
|
61
61
|
puts "\n Context: #{context}"
|
62
|
-
bullet = NoFlyList::TaskHelpers
|
62
|
+
bullet = NoFlyList::TaskHelpers.colorize("•", :green)
|
63
63
|
puts " #{bullet} Tag class: #{config[:tag_class_name]}"
|
64
64
|
puts " #{bullet} Tagging class: #{config[:tagging_class_name]}"
|
65
65
|
puts " #{bullet} Polymorphic: #{config[:polymorphic]}"
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
module NoFlyList
|
4
2
|
class TaggingProxy
|
5
3
|
include Enumerable
|
@@ -30,14 +28,15 @@ module NoFlyList
|
|
30
28
|
@transformer = transformer.is_a?(String) ? transformer.constantize : transformer
|
31
29
|
@restrict_to_existing = restrict_to_existing
|
32
30
|
@limit = limit
|
33
|
-
@pending_changes =
|
31
|
+
@pending_changes = nil # Use nil to indicate no changes yet
|
32
|
+
@clear_operation = false
|
34
33
|
end
|
35
34
|
|
36
35
|
# Determines if tags have changed from database state
|
37
36
|
# @return [Boolean] True if pending changes differ from database
|
38
37
|
# @api private
|
39
38
|
def changed?
|
40
|
-
@pending_changes.
|
39
|
+
@clear_operation || (!@pending_changes.nil? && @pending_changes != current_list_from_database)
|
41
40
|
end
|
42
41
|
|
43
42
|
def method_missing(method_name, *args)
|
@@ -73,7 +72,7 @@ module NoFlyList
|
|
73
72
|
|
74
73
|
# @return [Boolean] true if the proxy is valid
|
75
74
|
def save
|
76
|
-
return true unless
|
75
|
+
return true unless changed?
|
77
76
|
return false unless valid?
|
78
77
|
|
79
78
|
# Prevent recursive validation
|
@@ -92,8 +91,9 @@ module NoFlyList
|
|
92
91
|
|
93
92
|
# Update counter
|
94
93
|
model.update_column("#{@context}_count", 0) if setup[:counter_cache]
|
94
|
+
|
95
95
|
# Create new tags
|
96
|
-
|
96
|
+
pending_list.each do |tag_name|
|
97
97
|
tag = find_or_create_tag(tag_name)
|
98
98
|
next unless tag
|
99
99
|
|
@@ -112,7 +112,7 @@ module NoFlyList
|
|
112
112
|
end
|
113
113
|
end
|
114
114
|
# Update counter to match the actual count
|
115
|
-
model.update_column("#{@context}_count",
|
115
|
+
model.update_column("#{@context}_count", pending_list.size) if setup[:counter_cache]
|
116
116
|
|
117
117
|
refresh_from_database
|
118
118
|
true
|
@@ -133,15 +133,22 @@ module NoFlyList
|
|
133
133
|
|
134
134
|
# @return [Integer]
|
135
135
|
def count
|
136
|
+
# Always return the database count for count operations
|
136
137
|
@model.send(@context.to_s).count
|
137
138
|
end
|
138
139
|
|
139
140
|
# @return [Integer]
|
140
141
|
def size
|
141
|
-
if
|
142
|
+
# For size, return the database count if we've had a validation error
|
143
|
+
if !valid?
|
144
|
+
count
|
145
|
+
# Otherwise show pending changes
|
146
|
+
elsif @clear_operation
|
147
|
+
0
|
148
|
+
elsif !@pending_changes.nil?
|
142
149
|
@pending_changes.size
|
143
150
|
else
|
144
|
-
|
151
|
+
count
|
145
152
|
end
|
146
153
|
end
|
147
154
|
|
@@ -160,9 +167,44 @@ module NoFlyList
|
|
160
167
|
@transformer_name ||= transformer.name
|
161
168
|
end
|
162
169
|
|
170
|
+
# Returns tags that will be added (not in database but in pending changes)
|
171
|
+
# @return [Array<String>] Tags to be added
|
172
|
+
def additions
|
173
|
+
return [] if @clear_operation
|
174
|
+
return [] if @pending_changes.nil?
|
175
|
+
|
176
|
+
@pending_changes - current_list_from_database
|
177
|
+
end
|
178
|
+
|
179
|
+
# Returns tags that will be removed (in database but not in pending changes)
|
180
|
+
# @return [Array<String>] Tags to be removed
|
181
|
+
def removals
|
182
|
+
if @clear_operation
|
183
|
+
current_list_from_database
|
184
|
+
elsif @pending_changes.nil?
|
185
|
+
[]
|
186
|
+
else
|
187
|
+
current_list_from_database - @pending_changes
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
163
191
|
# @return [String]
|
164
192
|
def inspect
|
165
|
-
|
193
|
+
if @clear_operation
|
194
|
+
db_tags = current_list_from_database
|
195
|
+
"#<#{self.class.name} tags=[] changes=[CLEARING ALL (#{db_tags.size}): #{db_tags.inspect}] transformer_with=#{transformer_name}>"
|
196
|
+
elsif !@pending_changes.nil?
|
197
|
+
add_list = additions
|
198
|
+
remove_list = removals
|
199
|
+
changes = []
|
200
|
+
changes << "+#{add_list.inspect}" if add_list.any?
|
201
|
+
changes << "-#{remove_list.inspect}" if remove_list.any?
|
202
|
+
changes_str = changes.join(", ")
|
203
|
+
|
204
|
+
"#<#{self.class.name} tags=#{current_list.inspect} changes=[#{changes_str}] transformer_with=#{transformer_name}>"
|
205
|
+
else
|
206
|
+
"#<#{self.class.name} tags=#{current_list.inspect} transformer_with=#{transformer_name}>"
|
207
|
+
end
|
166
208
|
end
|
167
209
|
|
168
210
|
# Adds one or more tags to the current tag list
|
@@ -174,6 +216,7 @@ module NoFlyList
|
|
174
216
|
def add(*tags)
|
175
217
|
return self if limit_reached?
|
176
218
|
|
219
|
+
@clear_operation = false
|
177
220
|
new_tags = if tags.size == 1 && tags.first.is_a?(String)
|
178
221
|
transformer.parse_tags(tags.first)
|
179
222
|
else
|
@@ -181,8 +224,12 @@ module NoFlyList
|
|
181
224
|
end
|
182
225
|
return self if new_tags.empty?
|
183
226
|
|
184
|
-
@pending_changes
|
227
|
+
# Initialize @pending_changes with database values if not yet initialized
|
228
|
+
@pending_changes = current_list_from_database if @pending_changes.nil?
|
229
|
+
|
230
|
+
@pending_changes = @pending_changes + new_tags
|
185
231
|
@pending_changes.uniq!
|
232
|
+
mark_record_dirty
|
186
233
|
self
|
187
234
|
end
|
188
235
|
|
@@ -199,13 +246,20 @@ module NoFlyList
|
|
199
246
|
# @return [TaggingProxy] Returns self for method chaining
|
200
247
|
# @raise [ActiveRecord::RecordInvalid] If validation fails
|
201
248
|
def remove(*tags)
|
202
|
-
|
249
|
+
@clear_operation = false
|
250
|
+
|
251
|
+
# Initialize @pending_changes with database values if not yet initialized
|
252
|
+
@pending_changes = current_list_from_database if @pending_changes.nil?
|
253
|
+
|
254
|
+
old_list = @pending_changes.dup
|
255
|
+
|
203
256
|
tags_to_remove = if tags.size == 1 && tags.first.is_a?(String)
|
204
257
|
transformer.parse_tags(tags.first)
|
205
258
|
else
|
206
259
|
tags.flatten.map { |tag| tag.to_s.strip }
|
207
260
|
end
|
208
|
-
|
261
|
+
|
262
|
+
@pending_changes = @pending_changes - tags_to_remove
|
209
263
|
mark_record_dirty if @pending_changes != old_list
|
210
264
|
self
|
211
265
|
end
|
@@ -220,9 +274,9 @@ module NoFlyList
|
|
220
274
|
# @example Clear all tags
|
221
275
|
# tags.clear #=> []
|
222
276
|
def clear
|
223
|
-
|
277
|
+
@clear_operation = true
|
224
278
|
@pending_changes = []
|
225
|
-
mark_record_dirty if
|
279
|
+
mark_record_dirty if current_list_from_database.any?
|
226
280
|
model.write_attribute("#{@context}_count", 0) if setup[:counter_cache]
|
227
281
|
self
|
228
282
|
end
|
@@ -235,6 +289,7 @@ module NoFlyList
|
|
235
289
|
def clear!
|
236
290
|
@model.send(@context.to_s).destroy_all
|
237
291
|
@pending_changes = []
|
292
|
+
@clear_operation = false
|
238
293
|
@model.update_column("#{@context}_count", 0) if setup[:counter_cache]
|
239
294
|
self
|
240
295
|
end
|
@@ -276,7 +331,9 @@ module NoFlyList
|
|
276
331
|
end
|
277
332
|
|
278
333
|
def set_list(_context, value)
|
334
|
+
@clear_operation = false
|
279
335
|
@pending_changes = transformer.parse_tags(value)
|
336
|
+
mark_record_dirty
|
280
337
|
valid? # Just check validity without raising
|
281
338
|
self
|
282
339
|
end
|
@@ -286,24 +343,25 @@ module NoFlyList
|
|
286
343
|
end
|
287
344
|
|
288
345
|
def refresh_from_database
|
289
|
-
@pending_changes =
|
346
|
+
@pending_changes = nil
|
347
|
+
@clear_operation = false
|
290
348
|
end
|
291
349
|
|
292
350
|
def validate_limit
|
293
351
|
return unless @limit
|
294
|
-
return if
|
352
|
+
return if pending_list.size <= @limit
|
295
353
|
|
296
|
-
errors.add(:base, "Cannot have more than #{@limit} tags (attempting to save #{
|
354
|
+
errors.add(:base, "Cannot have more than #{@limit} tags (attempting to save #{pending_list.size})")
|
297
355
|
end
|
298
356
|
|
299
357
|
def validate_existing_tags
|
300
358
|
return unless @restrict_to_existing
|
301
|
-
return if
|
359
|
+
return if pending_list.empty?
|
302
360
|
|
303
361
|
# Transform tags to lowercase for comparison
|
304
|
-
normalized_changes =
|
362
|
+
normalized_changes = pending_list.map(&:downcase)
|
305
363
|
existing_tags = @tag_model.where("LOWER(name) IN (?)", normalized_changes).pluck(:name)
|
306
|
-
missing_tags =
|
364
|
+
missing_tags = pending_list - existing_tags
|
307
365
|
|
308
366
|
return unless missing_tags.any?
|
309
367
|
|
@@ -316,9 +374,9 @@ module NoFlyList
|
|
316
374
|
|
317
375
|
def setup
|
318
376
|
@setup ||= begin
|
319
|
-
|
320
|
-
|
321
|
-
|
377
|
+
context = @context.to_sym
|
378
|
+
@model.class._no_fly_list.tag_contexts[context]
|
379
|
+
end
|
322
380
|
end
|
323
381
|
|
324
382
|
def find_or_create_tag(tag_name)
|
@@ -329,36 +387,24 @@ module NoFlyList
|
|
329
387
|
end
|
330
388
|
end
|
331
389
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
attributes = {
|
342
|
-
tag: tag,
|
343
|
-
context: @context.to_s.singularize
|
344
|
-
}
|
345
|
-
|
346
|
-
# Add polymorphic attributes for polymorphic tags
|
347
|
-
if setup[:polymorphic]
|
348
|
-
attributes[:taggable_type] = model.class.name
|
349
|
-
attributes[:taggable_id] = model.id
|
350
|
-
end
|
351
|
-
|
352
|
-
# Use create! to ensure we catch any errors
|
353
|
-
model.send(context_taggings).create!(attributes)
|
390
|
+
# Helper method to get the list of tags that should be saved
|
391
|
+
def pending_list
|
392
|
+
if @clear_operation
|
393
|
+
[]
|
394
|
+
elsif !@pending_changes.nil?
|
395
|
+
@pending_changes
|
396
|
+
else
|
397
|
+
current_list_from_database
|
354
398
|
end
|
355
|
-
|
356
|
-
refresh_from_database
|
357
|
-
true
|
358
399
|
end
|
359
400
|
|
360
401
|
def current_list
|
361
|
-
|
402
|
+
# If validation failed, always return what's in the database
|
403
|
+
if errors.any?
|
404
|
+
current_list_from_database
|
405
|
+
elsif @clear_operation
|
406
|
+
[]
|
407
|
+
elsif !@pending_changes.nil?
|
362
408
|
@pending_changes
|
363
409
|
else
|
364
410
|
current_list_from_database
|
@@ -13,8 +13,8 @@ module NoFlyList
|
|
13
13
|
}.freeze
|
14
14
|
|
15
15
|
REQUIRED_COLUMNS = {
|
16
|
-
tag: [
|
17
|
-
tagging: [
|
16
|
+
tag: [ "name" ],
|
17
|
+
tagging: %w[tag_id taggable_id context]
|
18
18
|
}.freeze
|
19
19
|
|
20
20
|
def self.adapter_color(klass)
|
@@ -28,9 +28,9 @@ module NoFlyList
|
|
28
28
|
|
29
29
|
def self.check_table(klass)
|
30
30
|
klass.table_exists?
|
31
|
-
[true, "#{colorize('✓', :green)} Table exists: #{klass.table_name}"]
|
31
|
+
[ true, "#{colorize('✓', :green)} Table exists: #{klass.table_name}" ]
|
32
32
|
rescue StandardError => e
|
33
|
-
[false, "#{colorize('✗', :red)} Error: #{e.message}"]
|
33
|
+
[ false, "#{colorize('✗', :red)} Error: #{e.message}" ]
|
34
34
|
end
|
35
35
|
|
36
36
|
def self.verify_columns(klass, type)
|
@@ -61,16 +61,15 @@ module NoFlyList
|
|
61
61
|
def self.find_taggable_classes
|
62
62
|
Rails.application.eager_load!
|
63
63
|
ActiveRecord::Base.descendants.select do |klass|
|
64
|
-
klass.included_modules.any? { |mod| mod.in?([NoFlyList::TaggableRecord]) }
|
64
|
+
klass.included_modules.any? { |mod| mod.in?([ NoFlyList::TaggableRecord ]) }
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
68
|
def self.find_tag_classes
|
69
69
|
Rails.application.eager_load!
|
70
70
|
ActiveRecord::Base.descendants.select do |klass|
|
71
|
-
klass.included_modules.any? { |mod| mod.in?([NoFlyList::ApplicationTag, NoFlyList::TagRecord]) }
|
71
|
+
klass.included_modules.any? { |mod| mod.in?([ NoFlyList::ApplicationTag, NoFlyList::TagRecord ]) }
|
72
72
|
end
|
73
73
|
end
|
74
74
|
end
|
75
75
|
end
|
76
|
-
|
data/lib/no_fly_list/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: no_fly_list
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-03-22 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: activerecord
|
@@ -67,7 +66,6 @@ licenses:
|
|
67
66
|
- MIT
|
68
67
|
metadata:
|
69
68
|
rubygems_mfa_required: 'true'
|
70
|
-
post_install_message:
|
71
69
|
rdoc_options: []
|
72
70
|
require_paths:
|
73
71
|
- lib
|
@@ -82,8 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
80
|
- !ruby/object:Gem::Version
|
83
81
|
version: '0'
|
84
82
|
requirements: []
|
85
|
-
rubygems_version: 3.
|
86
|
-
signing_key:
|
83
|
+
rubygems_version: 3.6.2
|
87
84
|
specification_version: 4
|
88
85
|
summary: Tagging system for ActiveRecord models
|
89
86
|
test_files: []
|