type_balancer 0.1.3 → 0.1.4

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: f73ec86fddfda3cd22f19b0081824a8a488e1a47359976e32eb6b999074b92a3
4
- data.tar.gz: f25f85b6caeeeb3a5f8012ae295b96ae02413e4b4342aba946438bbcbb9eb063
3
+ metadata.gz: 6c93fa5dcb75821b9f9ea3340f06ca63d67f84781bcf90f13ac089abac7283b7
4
+ data.tar.gz: fbfadcf9eed52f9f82a5f0a99f05fe3801c8529c32735f3d2eac5b0d42648a4b
5
5
  SHA512:
6
- metadata.gz: c8895eea255feccab33cd33ba70d9bfb486a4ff9108ba1a828d1be72b501db1635cb35d5aab574298923533d1080c29d7b45a50bb8d1547ffdacfd882dc4d491
7
- data.tar.gz: '09f755338981a1523cae4eddc3468b17d7693b9183e7039b7e81f1d24bd6beab29133d9d9a797487ea3745195f3f4e577311c89258b41fa237bd63cc3fb63cd2'
6
+ metadata.gz: 316f4c79fb96b3ff7362a61a4524f2d08e67be712b23f6c1a6f36d4b24332ae58266ce2fbd6dd39a7deea190f9f2f9e965c0bafe94443dd24be571ed62f302fd
7
+ data.tar.gz: 054a3439add0798dc20593ace1681734fbed87f4d162baeb9e41b6f672cbbb43c7c2b17cd67a92d2c1a4a047b9418707139c2168cdcbde08a105376af53eb39e
data/CHANGELOG.md CHANGED
@@ -1,11 +1,23 @@
1
1
  # Changelog
2
2
 
3
- ## [0.1.2] - 2024-04-11
3
+ ## [0.1.4] - 2025-04-29
4
+
5
+ ### Fixed
6
+ - Fixed issue with providing a custom type field
7
+
8
+ ## [0.1.3] - 2025-04-27
9
+
10
+ ### Fixed
11
+ - Fixed type balancing behavior to properly handle edge cases where type ratios need to be maintained while respecting original collection order
12
+ - Enhanced position calculation to ensure consistent type distribution across the balanced collection
13
+ - Improved test coverage to verify correct type ratio preservation
14
+
15
+ ## [0.1.2] - 2025-04-11
4
16
 
5
17
  - Re-release of 0.1.1 due to RubyGems.org publishing issue
6
18
  - No functional changes from 0.1.1
7
19
 
8
- ## [0.1.1] - 2024-03-XX
20
+ ## [0.1.1] - 2025-04-10
9
21
 
10
22
  ### Refactoring
11
23
  - Major refactoring of core components to follow SOLID principles:
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- type_balancer (0.1.3)
4
+ type_balancer (0.1.4)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/examples/quality.rb CHANGED
@@ -23,6 +23,7 @@ class QualityChecker
23
23
  check_available_positions_edge_cases
24
24
  check_balance_method_robust
25
25
  check_real_world_feed
26
+ check_custom_type_field
26
27
 
27
28
  print_summary
28
29
  exit(@issues.empty? ? 0 : 1)
@@ -328,6 +329,28 @@ class QualityChecker
328
329
  puts " Balanced items with custom order: #{ordered_result.map { |i| i[:type] }.inspect}"
329
330
  end
330
331
 
332
+ def check_custom_type_field
333
+ @examples_run += 1
334
+ puts "\nCustom Type Field Example:"
335
+ data = [
336
+ { category: 'A', payload: 1 },
337
+ { category: 'B', payload: 2 },
338
+ { category: 'C', payload: 3 },
339
+ { category: 'A', payload: 4 }
340
+ ]
341
+ balanced = TypeBalancer.balance(data, type_field: :category)
342
+ found = balanced.map { |i| i[:category] }.uniq.sort
343
+ expected = %w[A B C]
344
+ if found == expected
345
+ @examples_passed += 1
346
+ puts "#{GREEN}Custom field respected: #{found.inspect}#{RESET}"
347
+ else
348
+ record_issue("Expected #{expected.inspect}, got #{found.inspect}")
349
+ puts "#{RED}Custom field test failed: #{found.inspect}#{RESET}"
350
+ end
351
+ print_section_table('custom_type_field', 1, found == expected ? 1 : 0)
352
+ end
353
+
331
354
  def print_summary
332
355
  puts "\n#{'-' * 50}"
333
356
  puts 'Quality Check Summary:'
@@ -3,6 +3,7 @@
3
3
  require_relative 'ratio_calculator'
4
4
  require_relative 'batch_processing'
5
5
  require_relative 'position_calculator'
6
+ require_relative 'type_extractor_registry'
6
7
 
7
8
  module TypeBalancer
8
9
  # Handles balancing of items across batches based on type ratios
@@ -10,9 +11,11 @@ module TypeBalancer
10
11
  # Initialize a new Balancer instance
11
12
  #
12
13
  # @param types [Array<String>, nil] Optional types
14
+ # @param type_field [Symbol] Field to use for type extraction (default: :type)
13
15
  # @param type_order [Array<String>, nil] Optional order of types
14
- def initialize(types = nil, type_order: nil)
16
+ def initialize(types = nil, type_field: :type, type_order: nil)
15
17
  @types = Array(types) if types
18
+ @type_field = type_field
16
19
  @type_order = type_order
17
20
  validate_types! if @types
18
21
  end
@@ -23,7 +26,18 @@ module TypeBalancer
23
26
  # @return [Array] Balanced items
24
27
  def call(collection)
25
28
  validate_collection!(collection)
26
- items_by_type = group_items_by_type(collection)
29
+ extractor = TypeExtractorRegistry.get(@type_field)
30
+
31
+ begin
32
+ items_by_type = extractor.group_by_type(collection)
33
+ rescue TypeBalancer::Error => e
34
+ raise TypeBalancer::Error, "Cannot access type field '#{@type_field}': #{e.message}"
35
+ end
36
+
37
+ # Remove nil types and validate
38
+ items_by_type.delete(nil)
39
+ raise TypeBalancer::Error, "Cannot access type field '#{@type_field}'" if items_by_type.empty?
40
+
27
41
  validate_types_in_collection!(items_by_type)
28
42
 
29
43
  target_counts = calculate_target_counts(items_by_type)
@@ -70,24 +84,7 @@ module TypeBalancer
70
84
  raise TypeBalancer::Error, "Invalid type(s): #{invalid_types.join(', ')}" if invalid_types.any?
71
85
  end
72
86
 
73
- def group_items_by_type(collection)
74
- collection.group_by do |item|
75
- extract_type(item)
76
- end
77
- end
78
-
79
- def extract_type(item)
80
- return item[:type] || item['type'] || raise(TypeBalancer::Error, 'Cannot access type field') if item.is_a?(Hash)
81
-
82
- begin
83
- item.type
84
- rescue NoMethodError
85
- raise TypeBalancer::Error, 'Cannot access type field'
86
- end
87
- end
88
-
89
87
  def calculate_target_counts(items_by_type)
90
- items_by_type.values.sum(&:size)
91
88
  items_by_type.transform_values(&:size)
92
89
  end
93
90
 
@@ -20,10 +20,15 @@ module TypeBalancer
20
20
  if item.respond_to?(@type_field)
21
21
  item.send(@type_field)
22
22
  elsif item.respond_to?(:[])
23
- item[@type_field] || item[@type_field.to_s]
23
+ value = item[@type_field] || item[@type_field.to_s]
24
+ raise TypeBalancer::Error, "Cannot access type field '#{@type_field}' on item #{item.inspect}" if value.nil?
25
+
26
+ value
24
27
  else
25
- raise Error, "Cannot access type field '#{@type_field}' on item #{item}"
28
+ raise TypeBalancer::Error, "Cannot access type field '#{@type_field}' on item #{item.inspect}"
26
29
  end
30
+ rescue NoMethodError, TypeError
31
+ raise TypeBalancer::Error, "Cannot access type field '#{@type_field}' on item #{item.inspect}"
27
32
  end
28
33
  end
29
34
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypeBalancer
4
+ # Registry to memoize TypeExtractor per type_field in thread/request scope
5
+ class TypeExtractorRegistry
6
+ STORAGE_KEY = :type_balancer_extractors
7
+
8
+ def self.get(type_field)
9
+ cache[type_field] ||= TypeExtractor.new(type_field)
10
+ end
11
+
12
+ def self.clear!
13
+ Thread.current[STORAGE_KEY] = nil
14
+ end
15
+
16
+ def self.cache
17
+ Thread.current[STORAGE_KEY] ||= {}
18
+ end
19
+ end
20
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TypeBalancer
4
- VERSION = '0.1.3'
4
+ VERSION = '0.1.4'
5
5
  end
data/lib/type_balancer.rb CHANGED
@@ -6,6 +6,8 @@ require_relative 'type_balancer/balancer'
6
6
  require_relative 'type_balancer/ratio_calculator'
7
7
  require_relative 'type_balancer/batch_processing'
8
8
  require 'type_balancer/position_calculator'
9
+ require_relative 'type_balancer/type_extractor'
10
+ require_relative 'type_balancer/type_extractor_registry'
9
11
 
10
12
  module TypeBalancer
11
13
  class Error < StandardError; end
@@ -33,31 +35,27 @@ module TypeBalancer
33
35
  # Input validation
34
36
  raise EmptyCollectionError, 'Collection cannot be empty' if items.empty?
35
37
 
36
- # Extract and validate types
37
- types = extract_types(items, type_field)
38
- raise Error, "Invalid type field: #{type_field}" if types.empty?
39
-
40
- # Group items by type
41
- items.group_by { |item| extract_type(item, type_field) }
38
+ # Use centralized extractor
39
+ extractor = TypeExtractorRegistry.get(type_field)
40
+ begin
41
+ types = extractor.extract_types(items)
42
+ raise Error, "Invalid type field: #{type_field}" if types.empty?
43
+ rescue Error => e
44
+ raise Error, "Cannot access type field '#{type_field}': #{e.message}"
45
+ end
42
46
 
43
- # Initialize balancer with type order if provided
44
- balancer = Balancer.new(types, type_order: type_order)
47
+ # Initialize balancer with type order and type field
48
+ balancer = Balancer.new(types, type_field: type_field, type_order: type_order)
45
49
 
46
50
  # Balance items
47
51
  balancer.call(items)
48
52
  end
49
53
 
54
+ # Backward compatibility methods
50
55
  def self.extract_types(items, type_field)
51
- items.map { |item| extract_type(item, type_field) }.uniq
52
- end
53
-
54
- def self.extract_type(item, type_field)
55
- if item.is_a?(Hash)
56
- item[type_field] || item[type_field.to_s]
57
- else
58
- item.public_send(type_field)
59
- end
60
- rescue NoMethodError
61
- nil
56
+ TypeExtractorRegistry.get(type_field).extract_types(items)
57
+ rescue Error
58
+ # For backward compatibility, return array with nil for inaccessible type fields
59
+ [nil]
62
60
  end
63
61
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: type_balancer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carl Smith
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-04-28 00:00:00.000000000 Z
10
+ date: 2025-04-29 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: Balances types in collections by ensuring each type appears a similar
13
13
  number of times
@@ -56,6 +56,7 @@ files:
56
56
  - lib/type_balancer/ratio_calculator.rb
57
57
  - lib/type_balancer/sequential_filler.rb
58
58
  - lib/type_balancer/type_extractor.rb
59
+ - lib/type_balancer/type_extractor_registry.rb
59
60
  - lib/type_balancer/version.rb
60
61
  - type_balancer.gemspec
61
62
  homepage: https://github.com/llwebconsulting/type_balancer