qfill 0.0.3 → 0.1.1

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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.github/dependabot.yml +8 -0
  3. data/.github/workflows/style.yml +37 -0
  4. data/.github/workflows/test.yml +55 -0
  5. data/.rspec +3 -2
  6. data/.rubocop.yml +26 -0
  7. data/.rubocop_todo.yml +163 -0
  8. data/.simplecov +6 -0
  9. data/CODE_OF_CONDUCT.md +133 -0
  10. data/Gemfile +31 -0
  11. data/Guardfile +12 -0
  12. data/{LICENSE.txt → LICENSE} +1 -1
  13. data/README.md +2 -2
  14. data/Rakefile +24 -1
  15. data/lib/qfill/errors/invalid_index.rb +8 -0
  16. data/lib/qfill/filter.rb +5 -3
  17. data/lib/qfill/list.rb +4 -2
  18. data/lib/qfill/list_set.rb +17 -7
  19. data/lib/qfill/manager.rb +90 -61
  20. data/lib/qfill/origin.rb +4 -3
  21. data/lib/qfill/popper.rb +31 -26
  22. data/lib/qfill/pusher.rb +34 -23
  23. data/lib/qfill/result.rb +78 -40
  24. data/lib/qfill/strategy/base.rb +65 -0
  25. data/lib/qfill/strategy/drain_to_empty.rb +73 -0
  26. data/lib/qfill/strategy/drain_to_limit.rb +46 -0
  27. data/lib/qfill/strategy/sample.rb +42 -0
  28. data/lib/qfill/strategy/time_slice.rb +105 -0
  29. data/lib/qfill/strategy.rb +13 -0
  30. data/lib/qfill/version.rb +3 -1
  31. data/lib/qfill.rb +13 -10
  32. data/maintenance-branch +1 -0
  33. data/qfill.gemspec +15 -13
  34. data/spec/qfill/filter_spec.rb +35 -26
  35. data/spec/qfill/list_set_spec.rb +28 -23
  36. data/spec/qfill/list_spec.rb +35 -27
  37. data/spec/qfill/manager_spec.rb +715 -284
  38. data/spec/qfill/origin_spec.rb +45 -35
  39. data/spec/qfill/popper_spec.rb +36 -30
  40. data/spec/qfill/pusher_spec.rb +32 -26
  41. data/spec/qfill/result_spec.rb +49 -38
  42. data/spec/qfill_spec.rb +6 -5
  43. data/spec/spec_helper.rb +11 -37
  44. data/spec/support/helper.rb +13 -0
  45. data/spec/support/random_object.rb +30 -0
  46. metadata +52 -23
data/lib/qfill/manager.rb CHANGED
@@ -1,91 +1,120 @@
1
- #Qfill::Manager.new(
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ # A Qfill::Manager builds a set of result data (as `result`) from the source data in Qfill::Popper,
6
+ # according to the Qfill::Result definitions in the Qfill::Pusher, and the selected strategy.
7
+ #
8
+ # Qfill::Manager.new(
2
9
  # :all_list_max => 40,
3
10
  # :popper => popper,
4
11
  # :pusher => pusher,
5
- #)
12
+ # )
6
13
  module Qfill
7
14
  class Manager
8
- attr_accessor :all_list_max, :popper, :pusher, :fill_count, :strategy
15
+ extend Forwardable
16
+ def_delegators :@strategy,
17
+ :popper,
18
+ :pusher,
19
+ :result,
20
+ :remaining_to_fill
21
+ attr_accessor :all_list_max,
22
+ :primary_list_total,
23
+ :popper,
24
+ :pusher,
25
+ :fill_count,
26
+ :result,
27
+ :strategy_options
9
28
 
10
- STRATEGY_OPTIONS = [:drain, :sample]
29
+ STRATEGY_OPTIONS = %i[drain_to_limit drain_to_empty sample time_slice].freeze
11
30
 
12
31
  def initialize(options = {})
13
- unless options[:popper] && options[:pusher]
14
- raise ArgumentError, "#{self.class}: popper and pusher are required options for #{self.class}.new(options)"
15
- end
16
32
  unless options[:strategy].nil? || STRATEGY_OPTIONS.include?(options[:strategy])
17
- raise ArgumentError, "#{self.class}: strategy is optional, but must be one of #{STRATEGY_OPTIONS.inspect} if provided"
33
+ raise ArgumentError,
34
+ "#{self.class}: strategy is optional, but must be one of #{STRATEGY_OPTIONS.inspect} if provided"
18
35
  end
36
+
37
+ @fill_count = 0
38
+
19
39
  @popper = options[:popper]
20
40
  @pusher = options[:pusher]
21
- # Provided by user, or defaults to the total number of primary elements in popper list set
22
- @all_list_max = options[:all_list_max] ? [options[:all_list_max], self.popper.get_primary_elements].min : self.popper.get_primary_elements
23
- @fill_count = 0
24
- @strategy = options[:strategy] || :drain # or :sample
41
+ @strategy_name = options[:strategy] || :drain_to_limit # or :drain_to_empty or :sample
42
+ @strategy_options = options[:strategy_options]
43
+
44
+ # Allow the strategy to define the pusher when not defined by user
45
+ @pusher ||= strategy.default_pusher
46
+ unless @popper && @pusher
47
+ raise ArgumentError, "#{self.class}: popper and pusher (except where defined by the strategy) are required options for #{self.class}.new(options)"
48
+ end
49
+
50
+ # Provided by user, or defaults to the total number of elements in popper list set
51
+ @all_list_max = if options[:all_list_max]
52
+ [options[:all_list_max],
53
+ popper.count_all_elements].min
54
+ else
55
+ popper.count_all_elements
56
+ end
57
+ @primary_list_total = popper.count_primary_elements
58
+ end
59
+
60
+ def strategy
61
+ @strategy ||= case @strategy_name
62
+ when :drain_to_empty
63
+ Qfill::Strategy::DrainToEmpty.new(self)
64
+ when :drain_to_limit
65
+ Qfill::Strategy::DrainToLimit.new(self)
66
+ when :sample
67
+ Qfill::Strategy::Sample.new(self)
68
+ when :time_slice
69
+ Qfill::Strategy::TimeSlice.new(self)
70
+ end
25
71
  end
26
72
 
27
73
  def fill!
28
- while !is_full? && !self.popper.primary_empty? && (result = self.pusher.current_list)
29
- self.fill_to_ratio!(result, self.all_list_max)
30
- self.pusher.set_next_as_current!
74
+ while !is_full? && !popper.primary_empty? && (self.result = pusher.current_list)
75
+ strategy.on_fill!
76
+ fill_to_ratio!
77
+ pusher.set_next_as_current!
78
+ result.elements.shuffle! if result.shuffle
31
79
  end
32
80
  end
33
81
 
34
- def fill_to_ratio!(result, all_list_max)
35
- result.max = Qfill::Result.get_limit_from_max_and_ratio(all_list_max, result.ratio)
36
- if !result.list_ratios.empty?
37
- self.fill_according_to_list_ratios!(result)
82
+ def fill_to_ratio!
83
+ strategy.result_max!
84
+ if result.list_ratios.empty?
85
+ fill_up_to_ratio!
38
86
  else
39
- self.fill_up_to_ratio!(result)
87
+ fill_according_to_list_ratios!
40
88
  end
41
89
  end
42
90
 
43
- def fill_according_to_list_ratios!(result)
44
- added = 0
45
- if self.strategy == :drain
46
- result.list_ratios.each do |list_name, list_ratio|
47
- #puts "fill_according_to_list_ratios!, :drain, #{list_name}: Primary remaining => #{self.popper.get_primary_elements}"
48
- max_from_list = Qfill::Result.get_limit_from_max_and_ratio(result.max, list_ratio)
49
- array_to_push = self.popper.next_objects!(list_name, max_from_list)
50
- added = result.push(array_to_push, list_name)
51
- end
52
- self.fill_count += added
53
- elsif self.strategy == :sample
54
- while !is_full? && !result.is_full? && !self.popper.totally_empty? && (list_ratio_tuple = result.current_list_ratio)
55
- #puts "fill_according_to_list_ratios!, :sample, #{list_ratio_tuple[0]}: Primary remaining => #{self.popper.get_primary_elements}"
56
- max_from_list = Qfill::Result.get_limit_from_max_and_ratio(result.max, list_ratio_tuple[1])
57
- array_to_push = self.popper.next_objects!(list_ratio_tuple[0], max_from_list)
58
- added = result.push(array_to_push, list_ratio_tuple[0])
59
- self.fill_count += added
60
- result.set_next_as_current!
61
- end
62
- end
91
+ def remaining_to_fill
92
+ primary_list_total - fill_count
63
93
  end
64
94
 
65
- def fill_up_to_ratio!(result)
66
- ratio = 1.0 / self.popper.primary.length
67
- max_from_list = Qfill::Result.get_limit_from_max_and_ratio(result.max, ratio)
68
- added = 0
69
- if self.strategy == :drain
70
- self.popper.primary.each do |queue|
71
- #puts "fill_up_to_ratio!, :drain max #{max_from_list}, #{queue.name}: Primary remaining => #{self.popper.get_primary_elements}"
72
- array_to_push = self.popper.next_objects!(queue.name, max_from_list)
73
- added = result.push(array_to_push, queue.name)
74
- end
75
- self.fill_count += added
76
- elsif self.strategy == :sample
77
- while !is_full? && !result.is_full? && !self.popper.totally_empty? && (origin_list = self.popper.current_list)
78
- #puts "fill_up_to_ratio!, :sample max #{max_from_list}, #{origin_list.name}: Primary remaining => #{self.popper.get_primary_elements}"
79
- array_to_push = self.popper.next_objects!(origin_list.name, max_from_list)
80
- added = result.push(array_to_push, origin_list.name)
81
- self.fill_count += added
82
- self.popper.set_next_as_current!
83
- end
84
- end
95
+ # Go through the queues this result should be filled from and push elements from them onto the current result list.
96
+ def fill_according_to_list_ratios!
97
+ strategy.fill_according_to_list_ratios!
98
+ end
99
+
100
+ # Go through the primary (non backfill) queues in the popper and push elements from them onto the current result list.
101
+ def fill_up_to_ratio!
102
+ strategy.fill_up_to_ratio!
85
103
  end
86
104
 
87
105
  def is_full?
88
- self.fill_count >= self.all_list_max
106
+ fill_count >= all_list_max
107
+ end
108
+
109
+ def each(&block)
110
+ # NOTE: on magic: http://blog.arkency.com/2014/01/ruby-to-enum-for-enumerator/
111
+ return enum_for(:each) unless block # Sparkling magic!
112
+
113
+ pusher.each(&block)
114
+ end
115
+
116
+ def to_s
117
+ "[#{strategy_name}][Result Max:#{result.max}][All Max:#{all_list_max}][Current Max:#{result.max}][Filled:#{fill_count}][Primary #:#{popper.count_primary_elements}]"
89
118
  end
90
119
  end
91
120
  end
data/lib/qfill/origin.rb CHANGED
@@ -1,4 +1,6 @@
1
- #Qfill::Origin.new(:name => "High List",
1
+ # frozen_string_literal: true
2
+
3
+ # Qfill::Origin.new(:name => "High List",
2
4
  # :elements => [Thing1, Thing3],
3
5
  # :backfill => "Medium List",
4
6
  # :filter => filter1),
@@ -12,8 +14,7 @@ module Qfill
12
14
  end
13
15
 
14
16
  def has_backfill?
15
- !!self.backfill
17
+ !!backfill
16
18
  end
17
-
18
19
  end
19
20
  end
data/lib/qfill/popper.rb CHANGED
@@ -1,4 +1,10 @@
1
- #popper = Qfill::Popper.new(
1
+ # frozen_string_literal: true
2
+
3
+ # A Qfill::Popper (which inherits from Qfill::ListSet) is a set of source data
4
+ # which will be added to the Qfill::Pusher, by the Qfill::Manager, when generating the result data.
5
+ # Qfill::Popper is made up of an array (called queues) of Qfill::Origin objects (which inherit from Qfill::List).
6
+ #
7
+ # popper = Qfill::Popper.new(
2
8
  # Qfill::Origin.new( :name => "High List",
3
9
  # :elements => [Thing1, Thing3],
4
10
  # :backfill => "Medium List",
@@ -11,9 +17,9 @@
11
17
  # :elements => [Thing4, Thing5],
12
18
  # :backfill => nil,
13
19
  # :filter => filter1),
14
- #)
20
+ # )
15
21
  #
16
- #popper = Qfill::Popper.from_array_of_hashes([
22
+ # popper = Qfill::Popper.from_array_of_hashes([
17
23
  # { :name => "High List",
18
24
  # :elements => [Thing1, Thing3, Thing7, Thing8, Thing12, Thing15, Thing17],
19
25
  # :backfill => "Medium List",
@@ -26,32 +32,39 @@
26
32
  # :elements => [Thing4, Thing5, Thing9, Thing10, Thing13, Thing14, Thing18, Thing19, Thing20],
27
33
  # :backfill => nil,
28
34
  # :filter => filter1},
29
- #])
35
+ # ])
30
36
  #
31
- # Popper is made up of an array (called queues) of Origin objects.
32
37
  module Qfill
33
38
  class Popper < Qfill::ListSet
34
-
35
39
  attr_accessor :total_elements
36
40
 
41
+ class << self
42
+ def from_array_of_hashes(array_of_hashes = [])
43
+ args = array_of_hashes.map do |hash|
44
+ Qfill::Origin.new(hash)
45
+ end
46
+ Qfill::Popper.new(*args)
47
+ end
48
+ end
49
+
37
50
  def initialize(*args)
38
51
  super(*args)
39
- @total_elements = get_total_elements
52
+ @total_elements = count_all_elements
40
53
  end
41
54
 
42
55
  def primary
43
- @primary ||= self.queues.select {|x| x.backfill != true}
56
+ @primary ||= queues.reject { |x| x.backfill == true }
44
57
  end
45
58
 
46
59
  def current_list
47
- self.primary[self.current_index]
60
+ primary[current_index]
48
61
  end
49
62
 
50
63
  def set_next_as_current!
51
- next_index = self.current_index + 1
52
- if (next_index) >= self.primary.length
64
+ next_index = current_index + 1
65
+ if (next_index) >= primary.length
53
66
  # If we have iterated through all the queues, then we reset
54
- self.reset!
67
+ reset!
55
68
  else
56
69
  self.current_index = next_index
57
70
  end
@@ -60,7 +73,7 @@ module Qfill
60
73
  def next_objects!(list_name, n = 1)
61
74
  origin_list = self[list_name]
62
75
  if origin_list.elements.length >= n
63
- return origin_list.elements.pop(n)
76
+ origin_list.elements.pop(n)
64
77
  else
65
78
  result = origin_list.elements.pop(n)
66
79
  while result.length < n && origin_list.has_backfill?
@@ -69,28 +82,20 @@ module Qfill
69
82
  result += secondary_list.elements.pop(remaining)
70
83
  origin_list = secondary_list
71
84
  end
72
- return result
85
+ result
73
86
  end
74
87
  end
75
88
 
76
- def self.from_array_of_hashes(array_of_hashes = [])
77
- args = array_of_hashes.map do |hash|
78
- Qfill::Origin.new(hash)
79
- end
80
- Qfill::Popper.new(*args)
81
- end
82
-
83
89
  def primary_empty?
84
- self.get_primary_elements == 0
90
+ count_primary_elements.zero?
85
91
  end
86
92
 
87
93
  def totally_empty?
88
- self.get_total_elements == 0
94
+ count_all_elements.zero?
89
95
  end
90
96
 
91
- def get_primary_elements
92
- self.primary.inject(0) {|counter, queue| counter += queue.elements.length}
97
+ def count_primary_elements
98
+ primary.inject(0) { |counter, queue| counter += queue.elements.length }
93
99
  end
94
-
95
100
  end
96
101
  end
data/lib/qfill/pusher.rb CHANGED
@@ -1,4 +1,10 @@
1
- #pusher = Qfill::Pusher.new(
1
+ # frozen_string_literal: true
2
+
3
+ # A Qfill::Pusher (which inherits from Qfill::ListSet) is a set of result data
4
+ # which contribute to the definition of the result set created by the Qfill::Manager.
5
+ # Qfill::Pusher is made up of an array (called queues) of Qfill::Result objects (which inherit from Qfill::List).
6
+ #
7
+ # pusher = Qfill::Pusher.new(
2
8
  # Qfill::Result.new( :name => "Best Results",
3
9
  # :filter => filter3,
4
10
  # :ratio => 0.5,
@@ -16,9 +22,9 @@
16
22
  # "Low List" => 0.4
17
23
  # }
18
24
  # )
19
- #)
25
+ # )
20
26
  #
21
- #pusher = Qfill::Pusher.from_array_of_hashes([
27
+ # pusher = Qfill::Pusher.from_array_of_hashes([
22
28
  # { :name => "First Result",
23
29
  # :ratio => 0.125,
24
30
  # :filter => filter3,
@@ -34,41 +40,40 @@
34
40
  # :ratio => 0.125 },
35
41
  # { :name => "Fourth Result",
36
42
  # :ratio => 0.50 },
37
- #])
43
+ # ])
38
44
  #
39
45
  # Pusher is made up of an array (called queues) of Result objects.
40
46
  module Qfill
41
47
  class Pusher < Qfill::ListSet
42
-
43
48
  def initialize(*args)
44
49
  super(*args)
45
- with_ratio = self.queues.map {|x| x.ratio}.compact
46
- ratio_to_split = (1 - with_ratio.inject(0, :+))
47
- if ratio_to_split < 0
48
- raise ArgumentError, "#{self.class}: mismatched ratios for queues #{with_ratio.join(' + ')} must not total more than 1"
49
- end
50
- num_without_ratio = self.queues.length - with_ratio.length
51
- if num_without_ratio > 0 && ratio_to_split <= 1
50
+ with_ratio = queues.map(&:ratio).compact
51
+ ratio_to_split = (1 - with_ratio.sum)
52
+ # if ratio_to_split < 0
53
+ # raise ArgumentError, "#{self.class}: mismatched ratios for queues #{with_ratio.join(' + ')} must not total more than 1"
54
+ # end
55
+ num_without_ratio = queues.length - with_ratio.length
56
+ if num_without_ratio.positive? && ratio_to_split <= 1
52
57
  equal_portion = ratio_to_split / num_without_ratio
53
- self.queues.each do |queue|
54
- if queue.ratio.nil?
55
- queue.tap do |q|
56
- q.ratio = equal_portion
57
- end
58
+ queues.each do |queue|
59
+ next unless queue.ratio.nil?
60
+
61
+ queue.tap do |q|
62
+ q.ratio = equal_portion
58
63
  end
59
64
  end
60
65
  end
61
66
  end
62
67
 
63
68
  def current_list
64
- self.queues[self.current_index]
69
+ queues[current_index]
65
70
  end
66
71
 
67
72
  def set_next_as_current!
68
- next_index = self.current_index + 1
69
- if (next_index) == self.queues.length
73
+ next_index = current_index + 1
74
+ if (next_index) == queues.length
70
75
  # If we have iterated through all the queues, then we reset
71
- self.reset!
76
+ reset!
72
77
  else
73
78
  self.current_index = next_index
74
79
  end
@@ -82,12 +87,18 @@ module Qfill
82
87
  end
83
88
 
84
89
  def more_to_fill?
85
- !self.queues.select {|x| !x.is_full?}.empty?
90
+ !queues.reject(&:is_full?).empty?
86
91
  end
87
92
 
88
93
  def next_to_fill
89
- self.queues.select {|x| !x.is_full?}.first
94
+ queues.reject(&:is_full?).first
90
95
  end
91
96
 
97
+ def each(&block)
98
+ # NOTE: on magic: http://blog.arkency.com/2014/01/ruby-to-enum-for-enumerator/
99
+ return enum_for(:each) unless block # Sparkling magic!
100
+
101
+ queues.each(&block)
102
+ end
92
103
  end
93
104
  end
data/lib/qfill/result.rb CHANGED
@@ -1,105 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :preferred is used for :drain_to_empty
4
+ # :ratio is used for the other strategies
1
5
  # Qfill::Result.new(:name => "Best Results",
2
6
  # :filter => filter3,
3
7
  # :ratio => 0.5,
4
8
  # :list_ratios => {
5
9
  # "High List" => 0.4,
6
10
  # "Medium List" => 0.2,
7
- # "Low List" => 0.4 } )
11
+ # "Low List" => 0.4 },
12
+ # :preferred => ["High List", "Medium List"]
13
+ # )
8
14
  module Qfill
9
15
  class Result < Qfill::List
10
- attr_accessor :ratio, :list_ratios, :fill_tracker, :fill_count, :validate, :filter, :current_list_ratio_index, :max
16
+ attr_accessor :ratio,
17
+ :list_ratios,
18
+ :fill_tracker,
19
+ :total_count,
20
+ :current_count,
21
+ :validate,
22
+ :current_list_ratio_index,
23
+ :max,
24
+ :shuffle,
25
+ :preferred,
26
+ :preferred_potential,
27
+ :preferred_potential_ratio,
28
+ :max_tracker
29
+
30
+ def self.get_limit_from_max_and_ratio(all_list_max, ratio, remain = nil)
31
+ return 1 if remain == 1
11
32
 
12
- def self.get_limit_from_max_and_ratio(all_list_max, ratio)
13
33
  limit = (all_list_max * ratio).round(0)
14
34
  # If we rounded down to zero we have to keep at least one.
15
35
  # This is because with small origin sets all ratios might round down to 0.
16
- if limit == 0
17
- limit += 1
18
- end
19
- limit
36
+ limit += 1 if limit.zero?
37
+ remain ? [limit, remain].min : limit
20
38
  end
21
39
 
22
40
  def initialize(options = {})
23
41
  super(options)
24
42
  @list_ratios = options[:list_ratios] || {}
25
- with_ratio = self.list_ratio_as_array.map {|tuple| tuple[1]}.compact
26
- ratio_leftover = (1 - with_ratio.inject(0, :+))
27
- if ratio_leftover < 0
28
- raise ArgumentError, "#{self.class}: invalid list_ratios for queue '#{self.name}'. List Ratios (#{with_ratio.join(' + ')}) must not total more than 1"
43
+ with_ratio = list_ratio_as_array.map { |tuple| tuple[1] }.compact
44
+ ratio_leftover = (1 - with_ratio.sum)
45
+ if ratio_leftover.negative?
46
+ raise ArgumentError,
47
+ "#{self.class}: invalid list_ratios for queue '#{name}'. List Ratios (#{with_ratio.join(' + ')}) must not total more than 1"
29
48
  end
30
- @ratio = options[:ratio]
49
+
50
+ @ratio = options[:ratio] || 1
31
51
  @max = 0
52
+ @preferred = options[:preferred] # Used by :drain_to_empty and :drain_to_limit
53
+ @preferred_potential = 0
54
+ @preferred_potential_ratio = 0
32
55
  @fill_tracker = {}
33
- @fill_count = 0
56
+ @max_tracker = {}
57
+ # Doesn't reset to 0 on reset!
58
+ @total_count = 0
59
+ # Does reset to 0 on reset!
60
+ @current_count = 0
61
+ @shuffle = options[:shuffle] || false
34
62
  @current_list_ratio_index = 0 # Used by :sample strategy
35
- @validate = self.use_validation?
63
+ @validate = use_validation?
36
64
  end
37
65
 
38
66
  def list_ratio_full?(list_name, max_from_list)
39
- self.fill_tracker[list_name] >= max_from_list
67
+ fill_tracker[list_name] >= max_from_list
40
68
  end
41
69
 
42
70
  def push(objects, list_name)
43
- self.validate!(list_name)
71
+ validate!(list_name)
44
72
  added = 0
73
+ fill_tracker[list_name] ||= 0
45
74
  objects.each do |object|
46
- if self.allow?(object, list_name)
47
- self.bump_fill_tracker!(list_name)
48
- self.add!(object)
49
- added += 1
50
- #self.print(list_name)
51
- end
75
+ # The objects have already been popped.
76
+ # The only valid reason to not push an object at this point is if !allow?.
77
+ # break if is_full?
78
+
79
+ next unless allow?(object, list_name)
80
+
81
+ bump_fill_tracker!(list_name)
82
+ add!(object)
83
+ added += 1
84
+ # self.print(list_name)
52
85
  end
53
- return added
86
+ added
54
87
  end
55
88
 
56
89
  def print(list_name)
57
- puts "Added to #{list_name}.\nResult List #{self.name} now has #{self.elements.length} total objects.\nSources:\n #{self.fill_tracker.inspect} "
90
+ puts "Added to #{list_name}.\nResult List #{name} now has #{elements.length} total objects.\nSources:\n #{fill_tracker.inspect} "
58
91
  end
59
92
 
60
93
  def add!(object)
61
- self.elements << object
94
+ elements << object
62
95
  end
63
96
 
64
97
  def allow?(object, list_name)
65
- !self.filter.respond_to?(:call) ||
98
+ !filter.respond_to?(:call) ||
66
99
  # If there is a filter, then it must return true to proceed
67
- self.filter.run(object, list_name)
100
+ filter.run(object, list_name)
68
101
  end
69
102
 
70
103
  def bump_fill_tracker!(list_name)
71
- self.fill_tracker[list_name] ||= 0
72
- self.fill_tracker[list_name] += 1
73
- self.fill_count += 1
104
+ fill_tracker[list_name] += 1
105
+ self.total_count += 1
106
+ self.current_count += 1
74
107
  end
75
108
 
76
109
  # Does the queue being pushed into match one of the list_ratios
77
110
  def valid?(list_name)
78
- self.list_ratios.has_key?(list_name)
111
+ list_ratios.key?(list_name)
79
112
  end
80
113
 
81
114
  def validate!(list_name)
82
- raise ArgumentError, "#{self.class}: #{list_name} is an invalid list_name. Valid list_names are: #{self.list_ratios.keys}" if self.validate && !self.valid?(list_name)
115
+ if validate && !valid?(list_name)
116
+ raise ArgumentError,
117
+ "#{self.class}: #{list_name} is an invalid list_name. Valid list_names are: #{list_ratios.keys}"
118
+ end
83
119
  end
84
120
 
85
121
  def use_validation?
86
- !self.list_ratios.empty?
122
+ !list_ratios.empty?
87
123
  end
88
124
 
89
125
  def list_ratio_as_array
90
126
  # [["high",0.4],["medium",0.4],["low",0.2]]
91
- @list_ratio_as_array ||= self.list_ratios.to_a
127
+ @list_ratio_as_array ||= list_ratios.to_a
92
128
  end
93
129
 
94
130
  def current_list_ratio
95
- self.list_ratio_as_array[self.current_list_ratio_index]
131
+ list_ratio_as_array[current_list_ratio_index]
96
132
  end
97
133
 
98
134
  def set_next_as_current!
99
- next_index = self.current_list_ratio_index + 1
100
- if (next_index) == self.list_ratio_as_array.length
135
+ next_index = current_list_ratio_index + 1
136
+ if (next_index) == list_ratio_as_array.length
101
137
  # If we have iterated through all the list_ratios, then we reset
102
- self.reset!
138
+ reset!
103
139
  else
104
140
  self.current_list_ratio_index = next_index
105
141
  end
@@ -107,14 +143,16 @@ module Qfill
107
143
 
108
144
  def reset!
109
145
  self.current_list_ratio_index = 0
146
+ self.current_count = 0
110
147
  end
111
148
 
149
+ # Results can overfill, because fractions.
112
150
  def is_full?
113
- self.fill_count >= self.max
151
+ self.total_count >= max
114
152
  end
115
153
 
116
154
  def to_s
117
- "Qfill::Result: ratio: #{self.ratio}, list_ratios: #{self.list_ratios}, fill_tracker: #{self.fill_tracker}, fill_count: #{self.fill_count}, filter: #{!!self.filter ? 'Yes' : 'No'}, current_list_ratio_index: #{self.current_list_ratio_index}, max: #{self.max}"
155
+ "Qfill::Result: ratio: #{ratio}, list_ratios: #{list_ratios}, fill_tracker: #{fill_tracker}, total_count: #{self.total_count}, current_count: #{self.current_count}, filter: #{!!filter ? 'Yes' : 'No'}, current_list_ratio_index: #{current_list_ratio_index}, max: #{max}"
118
156
  end
119
157
  end
120
158
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Qfill
6
+ module Strategy
7
+ class Base
8
+ extend Forwardable
9
+ def_delegators :@manager,
10
+ :all_list_max,
11
+ :popper,
12
+ :pusher,
13
+ :result,
14
+ :primary_list_total,
15
+ :fill_count,
16
+ :fill_count=,
17
+ :is_full?,
18
+ :strategy_options
19
+ attr_accessor :added,
20
+ :tally,
21
+ :ratio_modifier
22
+
23
+ def initialize(manager)
24
+ @manager = manager
25
+ @added = 0
26
+ @tally = 0
27
+ @ratio_modifier = 1
28
+ end
29
+
30
+ def name
31
+ NAME
32
+ end
33
+
34
+ def on_fill!
35
+ raise NotImplementedError
36
+ end
37
+
38
+ def fill_to_ratio!
39
+ raise NotImplementedError
40
+ end
41
+
42
+ # Go through the queues this result should be filled from and push elements from them onto the current result list.
43
+ def fill_according_to_list_ratios!
44
+ raise NotImplementedError
45
+ end
46
+
47
+ def fill_up_to_ratio!
48
+ raise NotImplementedError
49
+ end
50
+
51
+ def default_pusher
52
+ # NOOP
53
+ end
54
+
55
+ def bump!
56
+ self.tally += added
57
+ self.fill_count += added
58
+ end
59
+
60
+ def remaining
61
+ all_list_max - fill_count
62
+ end
63
+ end
64
+ end
65
+ end