picky 1.5.2 → 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/lib/picky/analyzer.rb +154 -0
  2. data/lib/picky/application.rb +53 -33
  3. data/lib/picky/character_substituters/west_european.rb +10 -6
  4. data/lib/picky/cli.rb +18 -18
  5. data/lib/picky/index/base.rb +44 -13
  6. data/lib/picky/index_bundle.rb +13 -4
  7. data/lib/picky/indexed/indexes.rb +26 -10
  8. data/lib/picky/indexing/indexes.rb +26 -24
  9. data/lib/picky/interfaces/live_parameters.rb +23 -16
  10. data/lib/picky/internals/extensions/object.rb +13 -6
  11. data/lib/picky/internals/frontend_adapters/rack.rb +30 -34
  12. data/lib/picky/internals/index/backend.rb +1 -2
  13. data/lib/picky/internals/index/file/basic.rb +18 -14
  14. data/lib/picky/internals/index/files.rb +16 -6
  15. data/lib/picky/internals/index/redis/basic.rb +12 -5
  16. data/lib/picky/internals/index/redis.rb +2 -2
  17. data/lib/picky/internals/indexed/bundle/base.rb +58 -14
  18. data/lib/picky/internals/indexed/bundle/memory.rb +40 -14
  19. data/lib/picky/internals/indexed/bundle/redis.rb +9 -30
  20. data/lib/picky/internals/indexed/categories.rb +19 -14
  21. data/lib/picky/internals/indexed/category.rb +44 -20
  22. data/lib/picky/internals/indexed/index.rb +23 -13
  23. data/lib/picky/internals/indexed/wrappers/bundle/wrapper.rb +27 -9
  24. data/lib/picky/internals/indexers/serial.rb +1 -1
  25. data/lib/picky/internals/indexing/bundle/base.rb +28 -28
  26. data/lib/picky/internals/indexing/bundle/memory.rb +14 -7
  27. data/lib/picky/internals/indexing/categories.rb +15 -11
  28. data/lib/picky/internals/indexing/category.rb +30 -20
  29. data/lib/picky/internals/indexing/index.rb +22 -14
  30. data/lib/picky/internals/query/allocations.rb +0 -15
  31. data/lib/picky/internals/query/combinations/base.rb +0 -4
  32. data/lib/picky/internals/query/combinations/redis.rb +19 -8
  33. data/lib/picky/internals/query/indexes.rb +3 -6
  34. data/lib/picky/internals/query/token.rb +0 -4
  35. data/lib/picky/internals/query/weights.rb +2 -11
  36. data/lib/picky/internals/results/base.rb +3 -10
  37. data/lib/picky/internals/tokenizers/base.rb +64 -28
  38. data/lib/picky/internals/tokenizers/index.rb +8 -8
  39. data/lib/picky/loader.rb +59 -53
  40. data/lib/picky/query/base.rb +23 -29
  41. data/lib/picky/sources/base.rb +10 -10
  42. data/lib/picky/sources/couch.rb +14 -10
  43. data/lib/picky/sources/csv.rb +21 -14
  44. data/lib/picky/sources/db.rb +37 -31
  45. data/lib/picky/sources/delicious.rb +11 -8
  46. data/lib/picky/sources/wrappers/base.rb +3 -1
  47. data/lib/picky/statistics.rb +66 -0
  48. data/lib/tasks/application.rake +3 -0
  49. data/lib/tasks/checks.rake +11 -0
  50. data/lib/tasks/framework.rake +3 -0
  51. data/lib/tasks/index.rake +9 -11
  52. data/lib/tasks/routes.rake +3 -2
  53. data/lib/tasks/shortcuts.rake +17 -5
  54. data/lib/tasks/statistics.rake +20 -12
  55. data/lib/tasks/try.rake +14 -14
  56. data/spec/lib/application_spec.rb +3 -3
  57. data/spec/lib/index/base_spec.rb +25 -3
  58. data/spec/lib/internals/extensions/object_spec.rb +46 -20
  59. data/spec/lib/internals/frontend_adapters/rack_spec.rb +3 -3
  60. data/spec/lib/internals/index/redis/basic_spec.rb +67 -0
  61. data/spec/lib/internals/indexers/serial_spec.rb +1 -1
  62. data/spec/lib/internals/results/base_spec.rb +0 -12
  63. data/spec/lib/internals/tokenizers/base_spec.rb +49 -1
  64. data/spec/lib/query/allocations_spec.rb +0 -56
  65. data/spec/lib/query/base_spec.rb +25 -21
  66. data/spec/lib/query/combinations/redis_spec.rb +6 -1
  67. data/spec/lib/sources/delicious_spec.rb +2 -2
  68. data/spec/lib/statistics_spec.rb +31 -0
  69. metadata +9 -2
@@ -3,35 +3,35 @@
3
3
  module Internals
4
4
 
5
5
  module Indexing # :nodoc:all
6
-
6
+
7
7
  module Bundle
8
-
8
+
9
9
  # This is the indexing bundle.
10
10
  # It does all menial tasks that have nothing to do
11
11
  # with the actual index running etc.
12
12
  #
13
13
  class Base < SuperBase
14
-
14
+
15
15
  attr_accessor :partial_strategy, :weights_strategy
16
-
16
+
17
17
  # Path is in which directory the cache is located.
18
18
  #
19
19
  def initialize name, configuration, similarity_strategy, partial_strategy, weights_strategy
20
20
  super name, configuration, similarity_strategy
21
-
21
+
22
22
  @partial_strategy = partial_strategy
23
23
  @weights_strategy = weights_strategy
24
24
  end
25
-
25
+
26
26
  # Sets up a piece of the index for the given token.
27
27
  #
28
28
  def initialize_index_for token
29
29
  index[token] ||= []
30
30
  end
31
-
31
+
32
32
  # Generation
33
33
  #
34
-
34
+
35
35
  # This method
36
36
  # * loads the base index from the db
37
37
  # * generates derived indexes
@@ -50,16 +50,16 @@ module Internals
50
50
  generate_derived
51
51
  end
52
52
  def cache_from_memory_generation_message
53
- timed_exclaim "CACHE FROM MEMORY #{identifier}."
53
+ timed_exclaim %Q{"#{identifier}": Caching from intermediate in-memory index.}
54
54
  end
55
-
55
+
56
56
  # Generates the weights and similarity from the main index.
57
57
  #
58
58
  def generate_derived
59
59
  generate_weights
60
60
  generate_similarity
61
61
  end
62
-
62
+
63
63
  # Load the data from the db.
64
64
  #
65
65
  def load_from_index_file
@@ -68,7 +68,7 @@ module Internals
68
68
  retrieve
69
69
  end
70
70
  def load_from_index_generation_message
71
- timed_exclaim "LOAD INDEX #{identifier}."
71
+ timed_exclaim %Q{"#{identifier}": Loading index.}
72
72
  end
73
73
  # Retrieves the prepared index data into the index.
74
74
  #
@@ -83,7 +83,7 @@ module Internals
83
83
  index[token] << id.send(key_format) # TODO Rewrite. Move this into the specific indexing.
84
84
  end
85
85
  end
86
-
86
+
87
87
  # Generates a new index (writes its index) using the
88
88
  # partial caching strategy of this bundle.
89
89
  #
@@ -94,7 +94,7 @@ module Internals
94
94
  # Generate a partial index from the given exact index.
95
95
  #
96
96
  def generate_partial_from exact_index
97
- timed_exclaim "PARTIAL GENERATE #{identifier}."
97
+ timed_exclaim %Q{"#{identifier}": Generating partial index for index.}
98
98
  self.index = exact_index
99
99
  self.generate_partial
100
100
  self
@@ -125,28 +125,28 @@ module Internals
125
125
  # Dumps the core index.
126
126
  #
127
127
  def dump_index
128
- timed_exclaim "DUMP INDEX #{identifier}."
128
+ timed_exclaim %Q{"#{identifier}": Dumping index.}
129
129
  backend.dump_index index
130
130
  end
131
131
  # Dumps the weights index.
132
132
  #
133
133
  def dump_weights
134
- timed_exclaim "DUMP WEIGHTS #{identifier}."
134
+ timed_exclaim %Q{"#{identifier}": Dumping weights of index.}
135
135
  backend.dump_weights weights
136
136
  end
137
137
  # Dumps the similarity index.
138
138
  #
139
139
  def dump_similarity
140
- timed_exclaim "DUMP SIMILARITY #{identifier}."
140
+ timed_exclaim %Q{"#{identifier}": Dumping similarity of index.}
141
141
  backend.dump_similarity similarity
142
142
  end
143
143
  # Dumps the similarity index.
144
144
  #
145
145
  def dump_configuration
146
- timed_exclaim "DUMP CONFIGURATION #{identifier}."
146
+ timed_exclaim %Q{"#{identifier}": Dumping configuration for index.}
147
147
  backend.dump_configuration configuration
148
148
  end
149
-
149
+
150
150
  # Alerts the user if an index is missing.
151
151
  #
152
152
  def raise_unless_cache_exists
@@ -171,18 +171,18 @@ module Internals
171
171
  raise_unless_similarity_ok
172
172
  end
173
173
  end
174
-
174
+
175
175
  # Outputs a warning for the given cache.
176
176
  #
177
177
  def warn_cache_small what
178
- puts "Warning: #{what} cache for #{identifier} smaller than 16 bytes."
178
+ warn "Warning: #{what} cache for #{identifier} smaller than 16 bytes."
179
179
  end
180
180
  # Raises an appropriate error message for the given cache.
181
181
  #
182
182
  def raise_cache_missing what
183
183
  raise "#{what} cache for #{identifier} missing."
184
184
  end
185
-
185
+
186
186
  # Warns the user if the similarity index is small.
187
187
  #
188
188
  def warn_if_similarity_small
@@ -193,10 +193,10 @@ module Internals
193
193
  def raise_unless_similarity_ok
194
194
  raise_cache_missing :similarity unless backend.similarity_cache_ok?
195
195
  end
196
-
196
+
197
197
  # TODO Spec on down.
198
198
  #
199
-
199
+
200
200
  # Warns the user if the core or weights indexes are small.
201
201
  #
202
202
  def warn_if_index_small
@@ -209,11 +209,11 @@ module Internals
209
209
  raise_cache_missing :index unless backend.index_cache_ok?
210
210
  raise_cache_missing :weights unless backend.weights_cache_ok?
211
211
  end
212
-
212
+
213
213
  end
214
-
214
+
215
215
  end
216
-
216
+
217
217
  end
218
-
218
+
219
219
  end
@@ -3,23 +3,30 @@
3
3
  module Internals
4
4
 
5
5
  module Indexing # :nodoc:all
6
-
6
+
7
7
  module Bundle
8
-
8
+
9
9
  # The memory version dumps its generated indexes to disk
10
10
  # (mostly JSON) to load them into memory on startup.
11
11
  #
12
12
  class Memory < Base
13
-
13
+
14
14
  # We're using files for the memory backend.
15
15
  # E.g. dump writes files.
16
16
  #
17
17
  alias backend files
18
-
18
+
19
+ def to_s
20
+ <<-MEMORY
21
+ Memory
22
+ #{@backend.indented_to_s}
23
+ MEMORY
24
+ end
25
+
19
26
  end
20
-
27
+
21
28
  end
22
-
29
+
23
30
  end
24
-
31
+
25
32
  end
@@ -1,11 +1,11 @@
1
1
  module Internals
2
2
 
3
3
  module Indexing
4
-
4
+
5
5
  class Categories
6
-
6
+
7
7
  attr_reader :categories
8
-
8
+
9
9
  each_delegate :index,
10
10
  :cache,
11
11
  :generate_caches,
@@ -15,28 +15,32 @@ module Internals
15
15
  :clear_caches,
16
16
  :create_directory_structure,
17
17
  :to => :categories
18
-
18
+
19
19
  def initialize
20
20
  @categories = []
21
21
  end
22
-
22
+
23
+ def to_s
24
+ categories.indented_to_s
25
+ end
26
+
23
27
  def << category
24
28
  categories << category
25
29
  end
26
-
30
+
27
31
  def find category_name
28
32
  category_name = category_name.to_sym
29
-
33
+
30
34
  categories.each do |category|
31
35
  next unless category.name == category_name
32
36
  return category
33
37
  end
34
-
38
+
35
39
  raise %Q{Index category "#{category_name}" not found. Possible categories: "#{categories.map(&:name).join('", "')}".}
36
40
  end
37
-
41
+
38
42
  end
39
-
43
+
40
44
  end
41
-
45
+
42
46
  end
@@ -1,11 +1,14 @@
1
1
  module Internals
2
2
 
3
3
  module Indexing
4
-
4
+
5
5
  class Category
6
-
6
+
7
7
  attr_reader :exact, :partial, :name, :configuration, :indexer
8
-
8
+
9
+ delegate :identifier, :prepare_index_directory, :to => :configuration
10
+ delegate :source, :source=, :tokenizer, :tokenizer=, :to => :indexer
11
+
9
12
  # Mandatory params:
10
13
  # * name: Category name to use as identifier and file names.
11
14
  # * index: Index to which this category is attached to.
@@ -17,38 +20,45 @@ module Internals
17
20
  #
18
21
  # Advanced Options (TODO):
19
22
  #
20
- # * weights:
21
- # * tokenizer:
23
+ # * weights:
24
+ # * tokenizer:
22
25
  #
23
26
  def initialize name, index, options = {}
24
27
  @name = name
25
28
  @from = options[:from]
26
-
29
+
27
30
  # Now we have enough info to combine the index and the category.
28
31
  #
29
32
  @configuration = Configuration::Index.new index, self
30
-
33
+
31
34
  @tokenizer = options[:tokenizer] || Tokenizers::Index.default
32
35
  @indexer = Indexers::Serial.new configuration, options[:source], @tokenizer
33
-
36
+
34
37
  # TODO Push into Bundle. At least the weights.
35
38
  #
36
39
  partial = options[:partial] || Generators::Partial::Default
37
40
  weights = options[:weights] || Generators::Weights::Default
38
41
  similarity = options[:similarity] || Generators::Similarity::Default
39
-
42
+
40
43
  bundle_class = options[:indexing_bundle_class] || Bundle::Memory
41
44
  @exact = bundle_class.new(:exact, configuration, similarity, Generators::Partial::None.new, weights)
42
45
  @partial = bundle_class.new(:partial, configuration, Generators::Similarity::None.new, partial, weights)
43
46
  end
44
-
45
- delegate :identifier, :prepare_index_directory, :to => :configuration
46
- delegate :source, :source=, :tokenizer, :tokenizer=, :to => :indexer
47
-
47
+
48
+ def to_s
49
+ <<-CATEGORY
50
+ Category(#{name} from #{from}):
51
+ Exact:
52
+ #{exact.indented_to_s(4)}
53
+ Partial:
54
+ #{partial.indented_to_s(4)}
55
+ CATEGORY
56
+ end
57
+
48
58
  def from
49
59
  @from || name
50
60
  end
51
-
61
+
52
62
  # TODO Spec.
53
63
  #
54
64
  def backup_caches
@@ -71,12 +81,12 @@ module Internals
71
81
  exact.delete
72
82
  partial.delete
73
83
  end
74
-
84
+
75
85
  def index
76
86
  prepare_index_directory
77
87
  indexer.index
78
88
  end
79
-
89
+
80
90
  # Generates all caches for this category.
81
91
  #
82
92
  def cache
@@ -97,7 +107,7 @@ module Internals
97
107
  generate_partial
98
108
  generate_caches_from_memory
99
109
  dump_caches
100
- timed_exclaim "CACHE FINISHED #{identifier}."
110
+ timed_exclaim %Q{"#{identifier}": Caching finished.}
101
111
  end
102
112
  def generate_caches_from_source
103
113
  exact.generate_caches_from_source
@@ -112,9 +122,9 @@ module Internals
112
122
  exact.dump
113
123
  partial.dump
114
124
  end
115
-
125
+
116
126
  end
117
-
127
+
118
128
  end
119
-
129
+
120
130
  end
@@ -3,16 +3,16 @@
3
3
  module Internals
4
4
 
5
5
  module Indexing
6
-
6
+
7
7
  class Index
8
-
8
+
9
9
  attr_reader :name, :source, :categories, :after_indexing
10
-
10
+
11
11
  # Delegators for indexing.
12
12
  #
13
13
  delegate :connect_backend,
14
14
  :to => :source
15
-
15
+
16
16
  delegate :index,
17
17
  :cache,
18
18
  :generate_caches,
@@ -22,27 +22,27 @@ module Internals
22
22
  :clear_caches,
23
23
  :create_directory_structure,
24
24
  :to => :categories
25
-
25
+
26
26
  def initialize name, source, options = {}
27
27
  @name = name
28
28
  @source = source
29
-
29
+
30
30
  @after_indexing = options[:after_indexing]
31
31
  @bundle_class = options[:indexing_bundle_class] # TODO This should actually be a fixed parameter.
32
-
32
+
33
33
  @categories = Categories.new
34
34
  end
35
-
35
+
36
36
  # TODO Spec. Doc.
37
37
  #
38
38
  def define_category category_name, options = {}
39
39
  options = default_category_options.merge options
40
-
40
+
41
41
  new_category = Category.new category_name, self, options
42
42
  categories << new_category
43
43
  new_category
44
44
  end
45
-
45
+
46
46
  # By default, the category uses
47
47
  # * the index's source.
48
48
  # * the index's bundle type.
@@ -53,15 +53,23 @@ module Internals
53
53
  :indexing_bundle_class => @bundle_class
54
54
  }
55
55
  end
56
-
56
+
57
57
  # Indexing.
58
58
  #
59
59
  def take_snapshot
60
60
  source.take_snapshot self
61
61
  end
62
-
62
+
63
+ def to_s
64
+ <<-INDEX
65
+ Indexing(#{name}):
66
+ #{"source: #{source}".indented_to_s}
67
+ #{"Categories:\n#{categories.indented_to_s}".indented_to_s}
68
+ INDEX
69
+ end
70
+
63
71
  end
64
-
72
+
65
73
  end
66
-
74
+
67
75
  end
@@ -58,21 +58,6 @@ module Internals
58
58
  end
59
59
  end
60
60
 
61
- # Returns a random id from the allocations.
62
- #
63
- # Note: This is an ok algorithm for small id sets.
64
- #
65
- # But still TODO try for a faster one.
66
- #
67
- # TODO For the 1 amount random case this needs to be improved.
68
- #
69
- def random_ids amount = 1
70
- return [] if @allocations.empty?
71
- ids = @allocations.first.ids
72
- indexes = Array.new(ids.size) { |id| id }.sort_by { rand }
73
- indexes.first(amount).map { |id| ids[id] }
74
- end
75
-
76
61
  # This is the main method of this class that will replace ids and count.
77
62
  #
78
63
  # What it does is calculate the ids and counts of its allocations
@@ -51,8 +51,6 @@ module Internals
51
51
  # ids that have an associated identifier that is nil.
52
52
  #
53
53
  def keep identifiers = []
54
- # TODO Rewrite to use the category!!!
55
- #
56
54
  @combinations.reject! { |combination| !combination.in?(identifiers) }
57
55
  end
58
56
 
@@ -64,8 +62,6 @@ module Internals
64
62
  # ids that have an associated identifier that is nil.
65
63
  #
66
64
  def remove identifiers = []
67
- # TODO Rewrite to use the category!!!
68
- #
69
65
  @combinations.reject! { |combination| combination.in?(identifiers) }
70
66
  end
71
67
 
@@ -19,7 +19,7 @@ module Internals
19
19
  def initialize combinations
20
20
  super combinations
21
21
 
22
- @@redis ||= ::Redis.new
22
+ @@redis ||= ::Redis.new :db => 15
23
23
  end
24
24
 
25
25
  # Returns the result ids for the allocation.
@@ -32,22 +32,33 @@ module Internals
32
32
  end
33
33
 
34
34
  result_id = generate_intermediate_result_id
35
-
36
- # TODO multi?
35
+
36
+ # Intersect and store.
37
37
  #
38
-
39
38
  @@redis.zinterstore result_id, identifiers
40
39
 
41
- @@redis.zrange result_id, offset, (offset + amount)
40
+ # Get the stored result.
41
+ #
42
+ results = @@redis.zrange result_id, offset, (offset + amount)
43
+
44
+ # Delete the stored result as it was only for temporary purposes.
45
+ #
46
+ @@redis.del result_id
47
+
48
+ results
42
49
  end
43
50
 
44
51
  # Generate a multiple host/process safe result id.
45
52
  #
46
- # TODO How expensive is Process.pid? If it changes once, remember forever?
53
+ require 'socket'
54
+ @@host = Socket.gethostname
55
+ define_method :host do
56
+ @@host
57
+ end
58
+ # Use the host and pid (generated lazily in child processes) for the result.
47
59
  #
48
60
  def generate_intermediate_result_id
49
- # TODO host -> extract host.
50
- :"host:#{Process.pid}:picky:result"
61
+ :"#{host}:#{@pid ||= Process.pid}:picky:result"
51
62
  end
52
63
 
53
64
  end
@@ -52,8 +52,6 @@ module Internals
52
52
  # Add the wrapped possible allocations to the ones we already have.
53
53
  #
54
54
  previous_allocations + expanded_combinations.map! do |expanded_combination|
55
- # TODO Insert Redis here?
56
- #
57
55
  @combinations_type.new(expanded_combination).pack_into_allocation(index.result_identifier) # TODO Do not extract result_identifier. Remove pack_into_allocation.
58
56
  end
59
57
  end)
@@ -116,14 +114,13 @@ module Internals
116
114
  # ones, but I guess I am a bit sentimental.
117
115
  #
118
116
  def expand_combinations_from possible_combinations
117
+ # If an element has size 0, this means one of the
118
+ # tokens could not be allocated.
119
+ #
119
120
  return if possible_combinations.any?(&:empty?)
120
121
 
121
122
  # Generate the first multiplicator "with which" (well, not quite) to multiply the smallest amount of combinations.
122
123
  #
123
- # TODO How does this work if an element has size 0? Since below we account for size 0.
124
- # Should we even continue if an element has size 0?
125
- # This means one of the tokens cannot be allocated.
126
- #
127
124
  single_mult = possible_combinations.inject(1) { |total, combinations| total * combinations.size }
128
125
 
129
126
  # Initialize a group multiplicator.
@@ -8,8 +8,6 @@ module Internals
8
8
  #
9
9
  # It also knows whether it needs to look for similarity (bla~), or whether it is a partial (bla*).
10
10
  #
11
- # TODO Make partial / similarity char configurable.
12
- #
13
11
  class Token # :nodoc:all
14
12
 
15
13
  attr_reader :text, :original
@@ -179,8 +177,6 @@ module Internals
179
177
 
180
178
  # Internal identifier.
181
179
  #
182
- # TODO Uh.
183
- #
184
180
  def identifier
185
181
  "#{similar?? :similarity : :index}:#{@text}"
186
182
  end
@@ -7,14 +7,7 @@ module Query
7
7
  #
8
8
  #
9
9
  def initialize weights = {}
10
- # @weights_cache = {} # TODO
11
- @weights = prepare weights
12
- end
13
-
14
- # Get the category indexes for the given bonuses.
15
- #
16
- def prepare weights
17
- weights
10
+ @weights = weights
18
11
  end
19
12
 
20
13
  # Get the weight of an allocation.
@@ -38,10 +31,8 @@ module Query
38
31
  # Note: Cache this if more complicated weighings become necessary.
39
32
  #
40
33
  def score combinations
41
- # TODO Beautify? Use categories for weights?
34
+ # TODO Or hide: combinations#to_weights_key
42
35
  #
43
- # weight_for combinations.map(&:category).clustered_uniq_fast.map!(&:name)
44
-
45
36
  # TODO combinations could cluster uniq as combinations are added (since combinations don't change).
46
37
  #
47
38
  weight_for combinations.map(&:category_name).clustered_uniq_fast
@@ -15,8 +15,8 @@ module Internals
15
15
  # Takes instances of Query::Allocations as param.
16
16
  #
17
17
  def initialize offset = 0, allocations = Query::Allocations.new
18
- @offset = offset
19
- @allocations = allocations # || Query::Allocations.new
18
+ @offset = offset
19
+ @allocations = allocations
20
20
  end
21
21
  # Create new results and calculate the ids.
22
22
  #
@@ -26,7 +26,7 @@ module Internals
26
26
  results
27
27
  end
28
28
 
29
- #
29
+ # Returns a hash with the allocations, offset, duration and total.
30
30
  #
31
31
  def serialize
32
32
  { allocations: allocations.to_result,
@@ -85,13 +85,6 @@ module Internals
85
85
  def ids amount = 20
86
86
  allocations.ids amount
87
87
  end
88
- # Gets an amout of random ids from the allocations.
89
- #
90
- # Note: Basically delegates to the allocations.
91
- #
92
- def random_ids amount = 1
93
- allocations.random_ids amount
94
- end
95
88
 
96
89
  # Human readable log.
97
90
  #