Class: ConsistentRandom

Inherits:
Object
  • Object
show all
Defined in:
lib/consistent_random.rb,
lib/consistent_random/testing.rb,
lib/consistent_random/active_job.rb,
lib/consistent_random/rack_middleware.rb,
lib/consistent_random/sidekiq_middleware.rb,
lib/consistent_random/sidekiq_client_middleware.rb

Defined Under Namespace

Modules: ActiveJob Classes: RackMiddleware, SidekiqClientMiddleware, SidekiqMiddleware

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name) ⇒ ConsistentRandom

Returns a new instance of ConsistentRandom.

Parameters:

  • name (Object)

    a name used to identifuy a consistent random value



66
67
68
# File 'lib/consistent_random.rb', line 66

def initialize(name)
  @name = (name.is_a?(String) ? name.dup : name.to_s).freeze
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



63
64
65
# File 'lib/consistent_random.rb', line 63

def name
  @name
end

Class Method Details

.current_seedString?

Get the current seed used to generate random numbers. This will return nil if called outside of a scope. The value can be passed to a new scope to generate the same random values in another thread or fiber.

Returns:

  • (String, nil)

    the seed value for the current scope or nil if called outside of a scope.



54
55
56
# File 'lib/consistent_random.rb', line 54

def current_seed
  Thread.current[:consistent_random_seed]
end

.scope(seed = nil) { ... } ⇒ Object

Define a scope where consistent random values will be generated.

Parameters:

  • seed (String, Symbol, Integer, Array<String>, Array<Integer>, nil) (defaults to: nil)

    optional value to use for generating random numbers. By default a random value will be generated. If the scope is nested in another scope block, then the seed from the parent scope will be used by default.

Yields:

  • block of code to execute within the scope

Returns:

  • the result of the block



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/consistent_random.rb', line 27

def scope(seed = nil)
  existing_seed = Thread.current[:consistent_random_seed]
  seed_value = case seed
  when nil
    existing_seed || SecureRandom.hex
  when String, Symbol, Integer
    seed.to_s
  when Array
    seed.join("\x1C")
  else
    raise ArgumentError, "Invalid seed value: #{seed.inspect}"
  end

  begin
    Thread.current[:consistent_random_seed] = seed_value
    yield
  ensure
    Thread.current[:consistent_random_seed] = existing_seed
  end
end

.testingObject



58
59
60
# File 'lib/consistent_random.rb', line 58

def testing
  Testing.new
end

Instance Method Details

#==(other) ⇒ Boolean

Returns true if the other object is a ConsistentRandom that returns the same random number generator. If called outside of a scope, then it will always return false. The functionality is designed to be similar to the same behavior as Random.

Returns:

  • (Boolean)

    true if the other object is a ConsistentRandom that returns the same random number generator. If called outside of a scope, then it will always return false. The functionality is designed to be similar to the same behavior as Random.



123
124
125
# File 'lib/consistent_random.rb', line 123

def ==(other)
  other.is_a?(self.class) && other.seed == seed
end

#bytes(size) ⇒ String

Generate a random array of bytes. The same number will be generated within a scope block. This method works the same as Random#bytes.

Parameters:

  • size (Integer)

    the number of bytes to generate.

Returns:

  • (String)

    a string of random bytes.



96
97
98
99
100
101
102
103
104
105
# File 'lib/consistent_random.rb', line 96

def bytes(size)
  test_bytes = Testing.current&.bytes_for(name)
  chunk_size = (test_bytes ? test_bytes.length : 20)

  bytes = []
  (size / chunk_size.to_f).ceil.times do |i|
    bytes << (test_bytes || seed_hash("#{name}#{i}").to_s)
  end
  bytes.join[0, size]
end

#rand(max = nil) ⇒ Numeric

Generate a random number. The same number will be generated within a scope block. This method works the same as Kernel#rand. It will generate a consistent value even across Ruby versions and platforms.

Parameters:

  • max (Integer, Range) (defaults to: nil)

    the maximum value of the random float or a range indicating the minimum and maximum values.

Returns:

  • (Numeric)

    a random number. If the max argument is a range, then the result will be a number in that range. If max is an number, then it will be an integer between 0 and that value. Otherwise, it will be a float between 0 and 1.



79
80
81
82
83
84
85
86
87
88
89
# File 'lib/consistent_random.rb', line 79

def rand(max = nil)
  value = Testing.current&.rand_for(name) || (seed >> 11) / FLOAT_DIVISOR
  case max
  when nil
    value
  when Numeric
    (value * max.to_i).to_i
  when Range
    cap_to_range(value, max)
  end
end

#randomRandom

Generate a random number generator for the given name. The generator will always have the same seed within a scope.

This value is dependent on the Ruby Random class and may not generate consistent values across Ruby versions and platforms.

Returns:

  • (Random)

    a random number generator



134
135
136
# File 'lib/consistent_random.rb', line 134

def random
  Random.new(seed)
end

#seedInteger

Generate a seed that can be used to generate random numbers. This seed will be return a consistent value when called within a scope.

Returns:

  • (Integer)

    a 64 bit integer for seeding random values



111
112
113
114
115
116
117
# File 'lib/consistent_random.rb', line 111

def seed
  test_seed = Testing.current&.seed_for(name)
  return test_seed if test_seed

  hash = seed_hash(name)
  hash.byteslice(0, 8).unpack1("Q>")
end