在Ruby/Rails中验证UUID字符串

12

我正在开发一个API。为了让开发者有更好的体验,我想要向用户报告任何易于发现的params问题。我的代码验证字符串、整数、布尔值、ISO8601日期和特定于域的值列表。我正在寻找一种方法来验证字符串是否是有效的UUID。我正在研究可能的选项。


1
您可以使用正则表达式验证 uuid 的格式。请参见 https://dev59.com/wmsz5IYBdhLWcg3wwKqh。 - Samy Kacimi
PostgreSQL适配器对UUID有一些验证,您可以检查其实现并在模型中使用。https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb#L6 - Naren Sisodiya
感谢@NarenSisodiya,我应该在问题中提到验证并不直接作用于类的属性,因此我不确定如何使用它。另外,我们的Rails项目正在使用Sequel。底层正则表达式的提示是有用的。 - bunufi
6个回答

15

基于广泛的建议使用正则表达式:

def validate_uuid_format(uuid)
  uuid_regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
  return true if uuid_regex.match?(uuid.to_s.downcase)

  log_and_raise_error("Given argument is not a valid UUID: '#{format_argument_output(uuid)}'")
end
请注意,这仅检查字符串是否符合 8-4-4-4-12 格式,并忽略任何版本检查。

请确保您的正则表达式仅寻找有效的UUID - anothermh

9

尽管我的回答会稍微限制问题的普适性,但我希望它仍然足够有趣。这个限制是假设您基于要检查的参数集实例化一个新对象,开始验证,然后返回错误对象,除非为nil。

# params[:lot] = { material_id: [SOME STRING], maybe: more_attributes }
lot = Lot.new params[:lot]
lot.valid?

使用Rails内置的验证机制即可。然而,截至2020年5月,似乎仍没有本地支持验证属性格式为UUID的功能。所谓本地支持是指类似于以下内容:
# models/lot.rb
# material_id is of type string, as per db/schema.rb
validates :material_id,
  uuid: true

在Rails 6.0.3中键入此内容,会得到:
ArgumentError (Unknown validator: 'UuidValidator')

因此,验证属性是否为UUID的关键是生成一个UuidValidator类,并确保Rails内部自然地找到并使用它。
受coderwall.com的Doug Puchalski提出的解决方案启发, 结合Rails API文档, 我想出了这个解决方案:
# lib/uuid_validator.rb
class UuidValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
      msg = options[:message] || "is not a valid UUID"
      record.errors.add(attribute, msg)
    end
  end
end

现在,假设您实例化了一个新的Lot实例,并错误地将整数分配为material_id的外键:
lot = Lot.new({material_id: 1})
lot.material_id
=> "1" # note the auto type cast based on schema definition
lot.valid?
=> false
lot.errors.messages
=> {:material_id=>["is not a valid UUID"]}
# now, assign a valid uuid to material_id
lot.material_id = SecureRandom.uuid
=> "8c0f2f01-8f8e-4e83-a2a0-f5dd2e63fc33"
lot.valid?
=> true

重要提示:
一旦您将属性的数据类型更改为uuid,

# db/schema.rb
create_table "lots", id: false, force: :cascade do |t|
  #t.string "material_id"
  t.uuid "material_id"
end

Rails 6将自动仅接受有效的uuids来分配给material_id。当尝试分配除有效UUID字符串以外的任何内容时,它将优雅地失败。
lot = Lot.new
# trying to assign an integer...
lot.material_id({material_id: 1})
# results in gracious failure
=> nil
# the same is true for 'nearly valid' UUID strings, note the four last chars
lot.material_id = "44ab2cc4-f9e5-45c9-a08d-de6a98c0xxxx"
=> nil

然而,您仍将获得正确的验证响应:
lot.valid?
=> false
lot.errors.messages
=> {:material_id=>["is not a valid UUID"]}

你是否了解在无效UUID上如何使用优雅失败的机制?这正困扰着我,我想更了解代码的哪个部分负责此行为。 - David Revelo
1
@DavidRevelo 目前我没有。然而,挖掘Rails源代码的正确起点肯定是“valid?”方法。您可以通过使用my_model_instance.method(:valid?).source_location来询问实例以获取该方法的定义。然后从源代码中导航并从那里跟进。 - Andreas Gebhard
在Rails 7中,UUID优雅失败类型也适用于ActiveModel上的create!save!update!方法;UuidValidator方法不再可行,不会导致.valid?返回false,也不会更新errors - undefined

3
使用https://github.com/dpep/rspec-uuid
gem 'rspec-uuid'

然后只需测试它是否为uuid:
it { expect(user_uuid).to be_a_uuid }

或者,您可以检查特定的UUID版本:

it { expect(user_uuid).to be_a_uuid(version: 4) }

2

如果你需要在将参数传递给Postgres之前验证它,只需检查字符串是否遵循8-4-4-4-12十六进制格式即可。

参数的简短检查:

uuid.to_s.match /\h{8}-(\h{4}-){3}\h{12}/

人话翻译:

  • 8个十六进制字符
  • 3组,每组4个十六进制字符
  • 12个十六进制字符

1

4
可以举个例子来说明如何应用它,而不仅仅是分享一个链接吗? - Stephan Hogenboom
这是一个简单的类方法,它返回一个布尔值:UUID.validate(my_string) - Alexandre
20
"Native" 意味着它是 Ruby 标准库的一部分。UUID 不属于 Ruby。你在这里链接一个 gem,在 Ruby 的基础上提供了这个类。 - Robin

-2

使用正则表达式匹配器验证它,这取决于您要验证的UUID版本。我相信有很多资源可以提供每个UUID版本的正则表达式模式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接