ffaker 2.20.0 → 2.25.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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +163 -17
  3. data/Gemfile +15 -0
  4. data/README.md +19 -7
  5. data/REFERENCE.md +1479 -1395
  6. data/Rakefile +16 -47
  7. data/bin/console +9 -0
  8. data/ffaker.gemspec +9 -23
  9. data/lib/ffaker/address.rb +5 -5
  10. data/lib/ffaker/address_br.rb +14 -2
  11. data/lib/ffaker/address_fr.rb +3 -5
  12. data/lib/ffaker/address_it.rb +1 -5
  13. data/lib/ffaker/address_ua.rb +1 -1
  14. data/lib/ffaker/animal_br.rb +12 -0
  15. data/lib/ffaker/avatar.rb +11 -3
  16. data/lib/ffaker/bank.rb +13 -1
  17. data/lib/ffaker/bank_us.rb +37 -0
  18. data/lib/ffaker/book.rb +15 -7
  19. data/lib/ffaker/boolean.rb +4 -0
  20. data/lib/ffaker/cheesy_lingo.rb +2 -2
  21. data/lib/ffaker/code.rb +1 -1
  22. data/lib/ffaker/company.rb +5 -8
  23. data/lib/ffaker/company_fr.rb +2 -2
  24. data/lib/ffaker/crypto.rb +14 -0
  25. data/lib/ffaker/data/address_br/complement +3 -0
  26. data/lib/ffaker/data/address_br/neighborhood_prefixes +3 -0
  27. data/lib/ffaker/data/address_de/state +1 -0
  28. data/lib/ffaker/data/animal_br/common_names +423 -0
  29. data/lib/ffaker/data/jo_jo/first_names +356 -0
  30. data/lib/ffaker/data/jo_jo/full_names +225 -0
  31. data/lib/ffaker/data/jo_jo/last_names +153 -0
  32. data/lib/ffaker/data/jo_jo/stands +149 -0
  33. data/lib/ffaker/data/job_tw/job_nouns +201 -0
  34. data/lib/ffaker/data/lorem_tw/words +789 -0
  35. data/lib/ffaker/date.rb +40 -0
  36. data/lib/ffaker/filesystem.rb +12 -2
  37. data/lib/ffaker/geolocation.rb +2 -2
  38. data/lib/ffaker/guid.rb +7 -1
  39. data/lib/ffaker/html_ipsum.rb +7 -1
  40. data/lib/ffaker/identification_br.rb +3 -12
  41. data/lib/ffaker/identification_ec.rb +39 -0
  42. data/lib/ffaker/identification_es_cl.rb +2 -2
  43. data/lib/ffaker/identification_es_co.rb +1 -1
  44. data/lib/ffaker/identification_fi.rb +73 -0
  45. data/lib/ffaker/identification_kr.rb +11 -4
  46. data/lib/ffaker/identification_mx.rb +1 -1
  47. data/lib/ffaker/identification_pl.rb +6 -6
  48. data/lib/ffaker/identification_tw.rb +4 -3
  49. data/lib/ffaker/image.rb +28 -4
  50. data/lib/ffaker/jo_jo.rb +24 -0
  51. data/lib/ffaker/job_tw.rb +12 -0
  52. data/lib/ffaker/lorem_ja.rb +1 -1
  53. data/lib/ffaker/lorem_ru.rb +1 -1
  54. data/lib/ffaker/lorem_tw.rb +40 -0
  55. data/lib/ffaker/movie.rb +1 -1
  56. data/lib/ffaker/name_fr.rb +4 -3
  57. data/lib/ffaker/name_pl.rb +3 -3
  58. data/lib/ffaker/number.rb +6 -2
  59. data/lib/ffaker/phone_number_de.rb +3 -3
  60. data/lib/ffaker/skill.rb +1 -1
  61. data/lib/ffaker/ssn.rb +4 -2
  62. data/lib/ffaker/ssn_se.rb +2 -2
  63. data/lib/ffaker/string.rb +7 -7
  64. data/lib/ffaker/time.rb +4 -4
  65. data/lib/ffaker/tweet.rb +1 -1
  66. data/lib/ffaker/utils/module_utils.rb +10 -20
  67. data/lib/ffaker/utils/unique_utils.rb +34 -21
  68. data/lib/ffaker/uuid.rb +175 -0
  69. data/lib/ffaker/vehicle.rb +63 -1
  70. data/lib/{version.rb → ffaker/version.rb} +1 -1
  71. data/lib/ffaker.rb +64 -190
  72. data/scripts/reference.rb +21 -21
  73. data/test/helper.rb +7 -9
  74. data/test/test_address_br.rb +15 -2
  75. data/test/test_address_da.rb +3 -3
  76. data/test/test_address_fi.rb +2 -2
  77. data/test/test_address_se.rb +2 -2
  78. data/test/test_address_ua.rb +1 -1
  79. data/test/test_animal_br.rb +17 -0
  80. data/test/test_array_utils.rb +1 -1
  81. data/test/test_avatar.rb +30 -7
  82. data/test/test_bank.rb +15 -1
  83. data/test/test_bank_us.rb +37 -0
  84. data/test/test_book.rb +29 -0
  85. data/test/test_boolean.rb +32 -1
  86. data/test/test_cheesy_lingo.rb +1 -1
  87. data/test/test_color.rb +3 -3
  88. data/test/test_crypto.rb +15 -0
  89. data/test/test_date.rb +47 -0
  90. data/test/test_filesystem.rb +30 -4
  91. data/test/test_freedom_ipsum.rb +1 -1
  92. data/test/test_gender_it.rb +1 -1
  93. data/test/test_gender_ja.rb +1 -1
  94. data/test/test_gender_jp.rb +1 -1
  95. data/test/test_gender_pl.rb +1 -1
  96. data/test/test_guid.rb +1 -1
  97. data/test/test_healthcare_ru.rb +1 -1
  98. data/test/test_hipster_ipsum.rb +1 -1
  99. data/test/test_html_ipsum.rb +6 -0
  100. data/test/test_identification.rb +12 -2
  101. data/test/test_identification_ec.rb +33 -0
  102. data/test/test_identification_es_mx.rb +3 -1
  103. data/test/test_identification_fi.rb +39 -0
  104. data/test/test_identification_it.rb +12 -1
  105. data/test/test_identification_kr.rb +3 -3
  106. data/test/test_image.rb +51 -5
  107. data/test/test_internet.rb +3 -1
  108. data/test/test_internet_se.rb +5 -2
  109. data/test/test_jo_jo.rb +29 -0
  110. data/test/test_job_tw.rb +21 -0
  111. data/test/test_lorem_br.rb +3 -3
  112. data/test/test_lorem_cn.rb +3 -3
  113. data/test/test_lorem_fr.rb +3 -3
  114. data/test/test_lorem_ie.rb +2 -2
  115. data/test/test_lorem_kr.rb +3 -3
  116. data/test/test_lorem_pl.rb +2 -2
  117. data/test/test_lorem_ru.rb +3 -3
  118. data/test/test_lorem_tw.rb +54 -0
  119. data/test/test_lorem_ua.rb +3 -3
  120. data/test/test_module_utils.rb +42 -0
  121. data/test/test_music.rb +4 -4
  122. data/test/test_name_da.rb +1 -1
  123. data/test/test_name_ph.rb +1 -1
  124. data/test/test_name_ru.rb +2 -2
  125. data/test/test_name_ua.rb +2 -2
  126. data/test/test_number.rb +14 -2
  127. data/test/test_phone_number_nl.rb +1 -1
  128. data/test/test_phone_number_se.rb +1 -1
  129. data/test/test_phone_number_sg.rb +8 -8
  130. data/test/test_ssn_se.rb +1 -1
  131. data/test/test_unique_utils.rb +29 -5
  132. data/test/test_units.rb +6 -6
  133. data/test/test_units_english.rb +15 -15
  134. data/test/test_units_metric.rb +15 -15
  135. data/test/test_uuid.rb +73 -0
  136. data/test/test_vehicle.rb +27 -1
  137. metadata +41 -245
data/lib/ffaker/string.rb CHANGED
@@ -21,7 +21,7 @@ module FFaker
21
21
  @last_token = nil
22
22
 
23
23
  # Drop surrounding /'s and split into characters
24
- tokens = exp.inspect[1...-1].split(//)
24
+ tokens = exp.inspect[1...-1].chars
25
25
  result << process_token(tokens) until tokens.empty?
26
26
 
27
27
  result
@@ -53,15 +53,15 @@ module FFaker
53
53
  when '?'
54
54
  # TODO: Let ? generate nothing
55
55
  '' # We already printed its target
56
- when '+' then
56
+ when '+'
57
57
  tokens.unshift(token) if rand(0..1) == 1 # Leave the `+` on to run again
58
58
  process_token(@last_token) # Run the last one at least once
59
- when '*' then
59
+ when '*'
60
60
  tokens.unshift(token) if rand(0..1) == 1 # Leave the `*` on to run again
61
61
  return '' if rand(0..1) == 1 # Or maybe do nothing
62
62
 
63
63
  process_token(@last_token) # Else run the last one again
64
- when '{' then
64
+ when '{'
65
65
  number = +''
66
66
  while (ch = tokens.shift) != '}'
67
67
  number << ch
@@ -76,14 +76,14 @@ module FFaker
76
76
 
77
77
  def generate_token(token, tokens)
78
78
  case token
79
- when /\w/ then
79
+ when /\w/
80
80
  @last_token = [token]
81
81
  token
82
- when BACKSLASH then
82
+ when BACKSLASH
83
83
  token = tokens.shift
84
84
  @last_token = ['\\', token]
85
85
  special(token)
86
- when '[' then
86
+ when '['
87
87
  set = []
88
88
  while (ch = tokens.shift) != ']'
89
89
  set << ch
data/lib/ffaker/time.rb CHANGED
@@ -22,17 +22,17 @@ module FFaker
22
22
 
23
23
  def datetime(params = {})
24
24
  years_back = params[:year_range] || 5
25
- latest_year = params [:year_latest] || 0
25
+ latest_year = params[:year_latest] || 0
26
26
  year = (rand * years_back).ceil + (::DateTime.now.year - latest_year - years_back)
27
27
  month = rand(1..12)
28
- day = rand(1..Date.new(year, month, -1).day)
28
+ day = rand(1..::Date.new(year, month, -1).day)
29
29
  hours = params[:hours] || rand(0..23)
30
30
  minutes = params[:minutes] || rand(0..59)
31
31
  series = [date = ::DateTime.new(year, month, day, hours, minutes)]
32
32
  return date unless params[:series]
33
33
 
34
34
  params[:series].each do |some_time_after|
35
- series << series.last + (rand * some_time_after).ceil
35
+ series << (series.last + (rand * some_time_after).ceil)
36
36
  end
37
37
  series
38
38
  end
@@ -44,7 +44,7 @@ module FFaker
44
44
  def between(from, to)
45
45
  from_value = convert_to_time(from)
46
46
  to_value = convert_to_time(to)
47
- ::Time.at(from_value + rand * (to_value.to_f - from_value.to_f))
47
+ ::Time.at(from_value + (rand * (to_value.to_f - from_value.to_f)))
48
48
  end
49
49
 
50
50
  private
data/lib/ffaker/tweet.rb CHANGED
@@ -19,7 +19,7 @@ module FFaker
19
19
  }.merge(args)
20
20
 
21
21
  my_reply = options[:reply] ? "#{mention} " : ''
22
- my_mentions = (options[:num_mentions]).positive? ? "#{mentions(options[:num_mentions])} " : ''
22
+ my_mentions = options[:num_mentions].positive? ? "#{mentions(options[:num_mentions])} " : ''
23
23
  my_tags = tags(options[:num_hashtags])
24
24
 
25
25
  remaining = [
@@ -13,8 +13,8 @@ module FFaker
13
13
  end
14
14
 
15
15
  def const_missing(const_name)
16
- if const_name =~ /[a-z]/ # Not a constant, probably a class/module name.
17
- super const_name
16
+ if const_name.match?(/[a-z]/) # Not a constant, probably a class/module name.
17
+ super
18
18
  else
19
19
  mod_name = ancestors.first.to_s.split('::').last
20
20
  data_path = "#{FFaker::BASE_LIB_PATH}/ffaker/data/#{underscore(mod_name)}/#{underscore(const_name.to_s)}"
@@ -25,7 +25,7 @@ module FFaker
25
25
  end
26
26
 
27
27
  def underscore(string)
28
- string.gsub(/::/, '/')
28
+ string.gsub('::', '/')
29
29
  .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
30
30
  .gsub(/([a-z\d])([A-Z])/, '\1_\2')
31
31
  .tr('-', '_')
@@ -33,27 +33,17 @@ module FFaker
33
33
  end
34
34
 
35
35
  def unique(max_retries = 10_000)
36
- @unique_generator ||= FFaker::UniqueUtils.new(self, max_retries)
36
+ FFaker::UniqueUtils.add_instance(self, max_retries)
37
37
  end
38
38
 
39
39
  # http://en.wikipedia.org/wiki/Luhn_algorithm
40
40
  def luhn_check(number)
41
- multiplications = []
42
-
43
- number.split(//).each_with_index do |digit, i|
44
- multiplications << i.even? ? digit.to_i * 2 : digit.to_i
45
- end
46
-
47
- sum = 0
48
- multiplications.each do |num|
49
- num.to_s.each_byte do |character|
50
- sum += character.chr.to_i
51
- end
52
- end
53
-
54
- control_digit = (sum % 10).zero? ? 0 : (sum / 10 + 1) * 10 - sum
55
- control_digit.to_s
41
+ sum = number.chars
42
+ .map(&:to_i)
43
+ .reverse
44
+ .each_with_index
45
+ .sum { |digit, index| index.even? ? (2 * digit).digits.sum : digit }
46
+ ((10 - (sum % 10)) % 10).to_s
56
47
  end
57
-
58
48
  end
59
49
  end
@@ -1,43 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
4
+
3
5
  module FFaker
4
6
  class UniqueUtils
5
- def initialize(generator, max_retries)
6
- @generator = generator
7
- @max_retries = max_retries
8
- @previous_results = Hash.new { |hash, key| hash[key] = Set.new }
9
- end
7
+ RetryLimitExceeded = Class.new(StandardError)
10
8
 
11
- def method_missing(name, *arguments)
12
- @generator.respond_to?(name) ? add_results_to_hash(name, *arguments) : super
13
- end
9
+ class << self
10
+ def add_instance(generator, max_retries)
11
+ instances[generator] ||= FFaker::UniqueUtils.new(generator, max_retries)
12
+ end
14
13
 
15
- def respond_to_missing?(method_name, include_private = false)
16
- super
17
- end
14
+ def instances
15
+ Thread.current[:ffaker_unique_utils] ||= {}
16
+ end
18
17
 
19
- RetryLimitExceeded = Class.new(StandardError)
18
+ def clear
19
+ instances.each_value(&:clear)
20
+ instances.clear
21
+ end
22
+ end
20
23
 
21
- def clear
22
- @previous_results.clear
24
+ def initialize(generator, max_retries)
25
+ @generator = generator
26
+ @max_retries = max_retries
23
27
  end
24
28
 
25
- def self.clear
26
- ObjectSpace.each_object(self, &:clear)
29
+ def clear
30
+ previous_results.clear
27
31
  end
28
32
 
29
33
  private
30
34
 
31
- def add_results_to_hash(name, *arguments)
35
+ def method_missing(name, *args, **kwargs)
32
36
  @max_retries.times do
33
- result = @generator.send(name, *arguments)
37
+ result = @generator.public_send(name, *args, **kwargs)
34
38
 
35
- next if @previous_results[[name, arguments]].include?(result)
39
+ next if previous_results[[name, args, kwargs]].include?(result)
36
40
 
37
- @previous_results[[name, arguments]] << result
41
+ previous_results[[name, args, kwargs]] << result
38
42
  return result
39
43
  end
40
- raise RetryLimitExceeded
44
+
45
+ raise RetryLimitExceeded, "Retry limit exceeded for #{name}"
46
+ end
47
+
48
+ def respond_to_missing?(name, *args)
49
+ @generator.respond_to?(name, *args) || super
50
+ end
51
+
52
+ def previous_results
53
+ @previous_results ||= Hash.new { |hash, key| hash[key] = Set.new }
41
54
  end
42
55
  end
43
56
  end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module FFaker
6
+ # UUIDs are a 128-bit value (16 bytes), often represented as a
7
+ # 32-character hexadecimal string in the format
8
+ # `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`.
9
+ #
10
+ # @note This generates lowercase strings, but UUIDs are case-insensitive.
11
+ #
12
+ # @see https://www.rfc-editor.org/rfc/rfc4122#section-4
13
+ # @see https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/
14
+ module UUID
15
+ extend ModuleUtils
16
+ extend self
17
+
18
+ # > UUID version 4 is meant for generating UUIDs from truly-random or
19
+ # > pseudo-random numbers.
20
+ def uuidv4
21
+ uuid = 0
22
+ # random_a
23
+ # > The first 48 bits of the layout that can be filled with random data
24
+ # > as specified in Section 6.9. Occupies bits 0 through 47 (octets 0-5).
25
+ uuid |= rand((2**48) - 1) << 80
26
+ # ver
27
+ # > The 4 bit version field as defined by Section 4.2, set to 0b0100 (4).
28
+ # > Occupies bits 48 through 51 of octet 6.
29
+ uuid |= 0b0100 << 76
30
+ # random_b
31
+ # > 12 more bits of the layout that can be filled random data as per
32
+ # > Section 6.9. Occupies bits 52 through 63 (octets 6-7).
33
+ uuid |= rand((2**12) - 1) << 64
34
+ # var
35
+ # > The 2 bit variant field as defined by Section 4.1, set to 0b10.
36
+ # > Occupies bits 64 and 65 of octet 8.
37
+ uuid |= 0b10 << 62
38
+ # random_c
39
+ # > The final 62 bits of the layout immediately following the var field
40
+ # > field to be filled with random data as per Section 6.9. Occupies bits
41
+ # > 66 through 127 (octets 8-15).
42
+ uuid |= rand((2**62) - 1)
43
+
44
+ as_string(uuid)
45
+ end
46
+
47
+ # > UUID version 6 is a field-compatible version of UUIDv1 Section 5.1,
48
+ # > reordered for improved DB locality. It is expected that UUIDv6 will
49
+ # > primarily be used in contexts where UUIDv1 is used. Systems that do not
50
+ # > involve legacy UUIDv1 SHOULD use UUIDv7 instead.
51
+ def uuidv6
52
+ timestamp = rand((2**60) - 1)
53
+
54
+ uuid = 0
55
+ # time_high
56
+ # > The most significant 32 bits of the 60 bit starting timestamp.
57
+ # > Occupies bits 0 through 31 (octets 0-3).
58
+ # @note Shifts 28 bits to remove `time_mid` and `time_low`.
59
+ uuid |= (timestamp >> 28) << 96
60
+ # time_mid
61
+ # > The middle 16 bits of the 60 bit starting timestamp. Occupies bits 32
62
+ # > through 47 (octets 4-5).
63
+ # @note Shifts 12 bits to remove `time_low`.
64
+ uuid |= ((timestamp >> 12) & ((2**16) - 1)) << 80
65
+ # ver
66
+ # > The 4 bit version field as defined by Section 4.2, set to 0b0110 (6).
67
+ # > Occupies bits 48 through 51 of octet 6.
68
+ uuid |= 0b0110 << 76
69
+ # time_low
70
+ # > 12 bits that will contain the least significant 12 bits from the 60
71
+ # > bit starting timestamp. Occupies bits 52 through 63 (octets 6-7).
72
+ uuid |= (timestamp & ((2**12) - 1)) << 64
73
+ # var
74
+ # > The 2 bit variant field as defined by Section 4.1, set to 0b10.
75
+ # > Occupies bits 64 and 65 of octet 8.
76
+ uuid |= 0b10 << 62
77
+ # clk_seq
78
+ # > The 14 bits containing the clock sequence. Occupies bits 66 through
79
+ # > 79 (octets 8-9).
80
+ #
81
+ # (earlier in the document)
82
+ # > The clock sequence and node bits SHOULD be reset to a pseudo-random
83
+ # > value for each new UUIDv6 generated; however, implementations MAY
84
+ # > choose to retain the old clock sequence and MAC address behavior from
85
+ # > Section 5.1.
86
+ uuid |= rand((2**14) - 1) << 48
87
+ # node
88
+ # > 48 bit spatially unique identifier. Occupies bits 80 through 127
89
+ # > (octets 10-15).
90
+ uuid |= rand((2**48) - 1)
91
+
92
+ as_string(uuid)
93
+ end
94
+
95
+ # > UUID version 7 features a time-ordered value field derived from the
96
+ # > widely implemented and well known Unix Epoch timestamp source, the
97
+ # > number of milliseconds since midnight 1 Jan 1970 UTC, leap seconds
98
+ # > excluded. UUIDv7 generally has improved entropy characteristics over
99
+ # > UUIDv1 Section 5.1 or UUIDv6 Section 5.6.
100
+ def uuidv7
101
+ timestamp = rand((2**48) - 1)
102
+
103
+ uuid = 0
104
+ # unix_ts_ms
105
+ # > 48 bit big-endian unsigned number of Unix epoch timestamp in
106
+ # > milliseconds as per Section 6.1. Occupies bits 0 through 47 (octets
107
+ # > 0-5).
108
+ uuid |= timestamp << 80
109
+ # ver
110
+ # > The 4 bit version field as defined by Section 4.2, set to 0b0111 (7).
111
+ # > Occupies bits 48 through 51 of octet 6.
112
+ uuid |= 0b0111 << 76
113
+ # rand_a
114
+ # > 12 bits pseudo-random data to provide uniqueness as per Section 6.9
115
+ # > and/or optional constructs to guarantee additional monotonicity as
116
+ # > per Section 6.2. Occupies bits 52 through 63 (octets 6-7).
117
+ uuid |= rand((2**12) - 1) << 64
118
+ # var
119
+ # > The 2 bit variant field as defined by Section 4.1, set to 0b10.
120
+ # > Occupies bits 64 and 65 of octet 8.
121
+ uuid |= 0b10 << 62
122
+ # rand_b
123
+ # > The final 62 bits of pseudo-random data to provide uniqueness as per
124
+ # > Section 6.9 and/or an optional counter to guarantee additional
125
+ # > monotonicity as per Section 6.2. Occupies bits 66 through 127 (octets
126
+ # > 8-15).
127
+ uuid |= rand((2**62) - 1)
128
+
129
+ as_string(uuid)
130
+ end
131
+
132
+ # > UUID version 8 provides an RFC-compatible format for experimental or
133
+ # > vendor-specific use cases. The only requirement is that the variant and
134
+ # > version bits MUST be set as defined in Section 4.1 and Section 4.2.
135
+ # > UUIDv8's uniqueness will be implementation-specific and MUST NOT be
136
+ # > assumed.
137
+ # >
138
+ # > [...] To be clear: UUIDv8 is not a replacement for UUIDv4 Section 5.4
139
+ # > where all 122 extra bits are filled with random data.
140
+ def uuidv8
141
+ uuid = 0
142
+ # custom_a
143
+ # > The first 48 bits of the layout that can be filled as an
144
+ # > implementation sees fit. Occupies bits 0 through 47 (octets 0-5).
145
+ uuid |= rand((2**48) - 1) << 80
146
+ # ver
147
+ # > The 4 bit version field as defined by Section 4.2, set to 0b1000 (8).
148
+ # > Occupies bits 48 through 51 of octet 6.
149
+ uuid |= 0b1000 << 76
150
+ # custom_b
151
+ # > 12 more bits of the layout that can be filled as an implementation
152
+ # > sees fit. Occupies bits 52 through 63 (octets 6-7).
153
+ uuid |= rand((2**12) - 1) << 64
154
+ # var
155
+ # > The 2 bit variant field as defined by Section 4.1, set to 0b10.
156
+ # > Occupies bits 64 and 65 of octet 8.
157
+ uuid |= 0b10 << 62
158
+ # custom_c
159
+ # > The final 62 bits of the layout immediately following the var field
160
+ # > to be filled as an implementation sees fit. Occupies bits 66 through
161
+ # > 127 (octets 8-15).
162
+ uuid |= rand((2**62) - 1)
163
+
164
+ as_string(uuid)
165
+ end
166
+
167
+ private
168
+
169
+ def as_string(uuid)
170
+ uuid.to_s(16)
171
+ .rjust(32, '0')
172
+ .gsub(/(.{8})(.{4})(.{4})(.{4})(.{12})/, '\1-\2-\3-\4-\5')
173
+ end
174
+ end
175
+ end
@@ -2,6 +2,68 @@
2
2
 
3
3
  module FFaker
4
4
  module Vehicle
5
+ module VIN
6
+ extend ModuleUtils
7
+ extend self
8
+
9
+ # https://en.wikibooks.org/wiki/Vehicle_Identification_Numbers_(VIN_codes)/World_Manufacturer_Identifier_(WMI)
10
+ VALID_WMI_REGIONS = [*'A'..'C', *'J'..'N', 'P', *'R'..'Z', *'1'..'9'].freeze
11
+
12
+ VALID_YEAR_CHARS = %w[
13
+ 5 6 7 8 9 A B C D E F G H J K L M N P R S T V W X Y 1 2 3 4 5 6 7 8 9
14
+ ].freeze # 2005-2039
15
+
16
+ # https://en.wikibooks.org/wiki/Vehicle_Identification_Numbers_(VIN_codes)/Check_digit
17
+ TRANSLITERATION_VALUES = {
18
+ 'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, 'F' => 6, 'G' => 7, 'H' => 8,
19
+ 'J' => 1, 'K' => 2, 'L' => 3, 'M' => 4, 'N' => 5, 'P' => 7, 'R' => 9,
20
+ 'S' => 2, 'T' => 3, 'U' => 4, 'V' => 5, 'W' => 6, 'X' => 7, 'Y' => 8, 'Z' => 9
21
+ }.freeze
22
+ POSITION_WEIGHTS = [8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2].freeze
23
+
24
+ VALID_ALPHA = TRANSLITERATION_VALUES.keys
25
+ VALID_ALPHANUMERIC = [*VALID_ALPHA, *'1'..'9'].freeze
26
+
27
+ # Generate a VIN that is compliant with specifications of US Title 49 Section 565.15
28
+ # https://www.govinfo.gov/content/pkg/CFR-2019-title49-vol6/xml/CFR-2019-title49-vol6-part565.xml#seqnum565.15
29
+ #
30
+ # Position Meaning
31
+ # 1-3 Manufacturer ID aka WMI (alpha and digits)
32
+ # 4-8 Vehicle Description ("For passenger cars ..[position 7].. shall be alphabetic")
33
+ # 9 Check Digit (0-9 or "X")
34
+ # 10 Year (see VIN_VALID_YEARS)
35
+ # 11 Plant of manufacture (alpha and digits)
36
+ # 12-17 Serial number (digits only)
37
+ #
38
+ # I, O and Q are NOT allowed. VIN_VALID_ALPHA has valid alpha characters.
39
+ def vin
40
+ generated_vin = [
41
+ # Manufacturer ID / WMI
42
+ fetch_sample(VALID_WMI_REGIONS),
43
+ fetch_sample(VALID_ALPHANUMERIC, count: 2),
44
+ # Vehicle Description
45
+ fetch_sample(VALID_ALPHANUMERIC, count: 3),
46
+ fetch_sample(VALID_ALPHA),
47
+ fetch_sample(VALID_ALPHANUMERIC),
48
+ '0', # check digit placeholder
49
+ fetch_sample(VALID_YEAR_CHARS), # Year of Manufacture
50
+ fetch_sample(VALID_ALPHANUMERIC), # Plant ID
51
+ FFaker.numerify('######') # Serial Number
52
+ ].join
53
+
54
+ # Calculate the Check Digit
55
+ weighted_sum = generated_vin.chars.each_with_index.sum do |char, idx|
56
+ (TRANSLITERATION_VALUES[char] || char).to_i * POSITION_WEIGHTS[idx]
57
+ end
58
+
59
+ check_digit = weighted_sum % 11
60
+ check_digit = 'X' if check_digit == 10
61
+ generated_vin[8] = check_digit.to_s
62
+
63
+ generated_vin
64
+ end
65
+ end
66
+
5
67
  extend ModuleUtils
6
68
  extend self
7
69
 
@@ -37,7 +99,7 @@ module FFaker
37
99
  end
38
100
 
39
101
  def vin
40
- FFaker.bothify('1#???#####?######').upcase
102
+ VIN.vin
41
103
  end
42
104
 
43
105
  def year
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FFaker
4
- VERSION = '2.20.0'
4
+ VERSION = '2.25.0'
5
5
  end