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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f1516f63b4ece62151501ea60f15ae1d5c2c3ea5738f22881ad5e2456a74b68
4
- data.tar.gz: c9b88ce48a93a03fd21ade6eccfb0210e788c26ea78150ce92ee5ecf5f0ce8b0
3
+ metadata.gz: a83b9c51a6624a462debf10d93389d093ab732e9b7e836ee90bde0f1ae5239e5
4
+ data.tar.gz: 88e96def76873eaa415431ce3ddb05076f59234d68583bad4d8c4f298fe9c0c4
5
5
  SHA512:
6
- metadata.gz: ea013a28e4c1246f511ae736951a89a2c292668a6d3ce1ecbf2ada940cee52576144f5de4cfebd2f10a38feb3a80f94082cc1d6ab340d692fe14f75e329b0913
7
- data.tar.gz: 9b714d9919bcf14c9fda34bada054f49af0202ab1189f0e487f38d1140df4f27e5580ef3b737adf0a59efc55e19bf381f74cdf2ccdeeb2fcf73d3a49b8135049
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) ? 'Global' : 'Model-specific'
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::colorize('✗', :red)} #{type} class not found: #{class_name}"
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::colorize('', :green)
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.present? && @pending_changes != current_list_from_database
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 @pending_changes.any?
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
- @pending_changes.each do |tag_name|
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", @pending_changes.size) if setup[:counter_cache]
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 @pending_changes.any?
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
- @model.send(@context.to_s).size
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
- "#<#{self.class.name} tags=#{current_list.inspect} transformer_with=#{transformer_name} >"
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 = current_list + new_tags
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
- old_list = current_list.dup
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
- @pending_changes = current_list - tags_to_remove
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
- old_list = current_list.dup
277
+ @clear_operation = true
224
278
  @pending_changes = []
225
- mark_record_dirty if @pending_changes != old_list
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 @pending_changes.size <= @limit
352
+ return if pending_list.size <= @limit
295
353
 
296
- errors.add(:base, "Cannot have more than #{@limit} tags (attempting to save #{@pending_changes.size})")
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 @pending_changes.empty?
359
+ return if pending_list.empty?
302
360
 
303
361
  # Transform tags to lowercase for comparison
304
- normalized_changes = @pending_changes.map(&:downcase)
362
+ normalized_changes = pending_list.map(&:downcase)
305
363
  existing_tags = @tag_model.where("LOWER(name) IN (?)", normalized_changes).pluck(:name)
306
- missing_tags = @pending_changes - existing_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
- context = @context.to_sym
320
- @model.class._no_fly_list.tag_contexts[context]
321
- end
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
- def save_changes
333
- # Clear existing tags
334
- model.send(context_taggings).delete_all
335
-
336
- # Create new tags
337
- @pending_changes.each do |tag_name|
338
- tag = find_or_create_tag(tag_name)
339
- next unless tag
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
- if @pending_changes.any?
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: ['name'],
17
- tagging: ['tag_id', 'taggable_id', 'context']
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
-
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NoFlyList
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
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.6.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: 2024-12-20 00:00:00.000000000 Z
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.5.22
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: []