【问题标题】:rails validating url with URI.parse throwing unexpected resultsrails 使用 URI.parse 验证 url 抛出意外结果
【发布时间】:2017-06-15 20:43:33
【问题描述】:

我正在关注solution 以验证 Rails 应用程序中服务类中的 url。测试返回 false,但在控制台中,当我使用 Ruby 的 URI 类解析给定的 url 时,不会引发错误。我认为我的代码中有任何拼写错误 - 有什么建议我哪里出错了吗?

class UrlService
  attr_accessor :uri

  def initialize(uri)
    @uri = uri
  end

  def valid?
    begin
      uri = URI.parse(uri)
      !uri.host.nil? && uri.kind_of?(URI::HTTP)
    rescue URI::InvalidURIError
      false
    end
  end

end

测试

require "rails_helper"

RSpec.describe UrlService do
  subject {described_class.new(url)}

  describe "#valid?" do
    context "success" do
      let(:url) { "https://www.google.com/" }

      it "returns true" do
        expect(subject.valid?).to be_truthy
      end
    end
  end

end

【问题讨论】:

  • 附带说明 - 如果您打算在模型中使用它,将其写为 custom validator 而不是服务是有意义的。

标签: ruby-on-rails ruby


【解决方案1】:

你的问题实际上是一个 Ruby 问题,在这一行:

uri = URI.parse(uri)

想要发生的事情是将uri 重新定义为uri 的解析版本。 实际上发生的事情是 Ruby 看到“哦,你正在定义一个新的局部变量 uri”;这个新的局部变量现在优先于您使用 attr_accessor 定义的方法uri。出于某种原因,Ruby 在局部变量赋值期间评估自引用为nil。这是shadowing 的示例。

因此,上面的语句会导致 URI.parse 始终在值 nil 上执行,这就是为什么无论您将该 URI 设置为什么,您的测试都会失败。要修复它,只需使用不同的变量名:

parsed_uri = URI.parse(uri)

附录:证明我没有编造的简短示例

irb(main):016:0> z
NameError: undefined local variable or method `z' for main:Object
        from (irb):16
        from /Users/rnubel/.rubies/ruby-2.3.3/bin/irb:11:in `<main>'
irb(main):017:0> z = z
=> nil

第一条语句失败,因为它只是引用了一个不存在的本地变量。第二条语句成功,因为 Ruby 将 z 赋值中的 z 变量计算为 nil

irb(main):011:0> class Foo; def bar; "http://www.google.com"; end; end
=> :bar
irb(main):012:0> Foo.new.instance_exec { bar }
=> "http://www.google.com"
irb(main):013:0> Foo.new.instance_exec { bar = (puts bar.inspect) }
nil
=> nil

这与您遇到的问题相同;只需使用puts 来检查内联值。它打印nil

【讨论】:

  • 我以为是这样的。感谢您的阴影解释。
【解决方案2】:

如果预期用途是验证模型,您可能希望将其编写为自定义验证器而不是服务。

# lib/validators/uri_validator.rb
class UriValidator < ActiveModel::Validator
  def validate_each(record, attribute, value)
    begin
      uri = URI.parse(value)
    rescue URI::InvalidURIError
    end
    if uri && uri.host && uri.kind_of?(URI::HTTP)
      record.errors.add attribute,
    end
  end
end

然后您可以通过以下方式使用它:

validates :some_attribute, presence: true, url: true

最直接的测试方法是通过使用验证器的模型类:

require "rails_helper"
RSpec.describe Thing, type: :model do
  describe "validations" do
    describe "url" do
      it "does not allow an invalid URL" do
        thing = Thing.new(url: 'foo')
        thing.valid
        expect(thing.errors).to have_key :url
      end
      it "allows a valid URL" do
        thing = Thing.new(url: 'https://www.google.com/')
        thing.valid
        expect(thing.errors).to have_key :url
      end
    end
  end
end

【讨论】:

  • 感谢您的评论。我在最初发布的链接中看到了该选项。我的课程不是 ActiveRecord 或 ActiveModel 课程。
猜你喜欢
  • 1970-01-01
  • 2012-02-03
  • 2018-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-07
  • 1970-01-01
相关资源
最近更新 更多