我在我的模型中使用此正则表达式来验证用户提交的URL。我不想强迫用户输入http部分,但如果它不在那里,我想自己添加。
validates :url, :format => { :with => /^((http|https):\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+).[a-z]{2,5}(:[0-9]{1,5})?(\/.)?$/ix, :message => " is not valid" }
知道我怎么能这样做吗?我对验证和正则表达式的经验很少..
答案 0 :(得分:69)
如果不存在,请使用前置过滤器添加:
before_validation :smart_add_url_protocol
protected
def smart_add_url_protocol
unless self.url[/\Ahttp:\/\//] || self.url[/\Ahttps:\/\//]
self.url = "http://#{self.url}"
end
end
保留您的验证,如果他们输错,他们可以更正协议。
答案 1 :(得分:36)
不要使用正则表达式执行此操作,使用URI.parse
将其拆分,然后查看网址上是否有方案:
u = URI.parse('/pancakes')
if(!u.scheme)
# prepend http:// and try again
elsif(%w{http https}.include?(u.scheme))
# you're okay
else
# you've been give some other kind of
# URL and might want to complain about it
end
使用URI库还可以轻松清除某人可能尝试放入URL的任何流浪废话(例如userinfo)。
答案 2 :(得分:5)
接受的答案非常好。
但是,如果字段(url)是可选的,则可能会为undefined method
类引发错误,例如nil
+。
以下内容应解决这个问题:
def smart_add_url_protocol
if self.url && !url_protocol_present?
self.url = "http://#{self.url}"
end
end
def url_protocol_present?
self.url[/\Ahttp:\/\//] || self.url[/\Ahttps:\/\//]
end
答案 3 :(得分:4)
根据mu的回答,这是我在模型中使用的代码。这在以下情况下运行:保存链接而无需模型过滤器。要求Super调用默认保存方法。
def link=(_link)
u=URI.parse(_link)
if (!u.scheme)
link = "http://" + _link
else
link = _link
end
super(link)
end
答案 4 :(得分:4)
当人们在before_validation
钩子中改变模型时,我讨厌它。然后当有一天发生由于某种原因需要使用save(validate:false)持久化模型时,那么假设总是在指定字段上运行的某些过滤器不会运行。当然,拥有无效数据通常是您想要避免的,但如果没有使用,则不需要这样的选项。另一个问题是,每次从模型中询问是否有效,这些修改也会发生。简单地询问模型是否有效的事实可能导致模型被修改只是意外,甚至可能是不需要的。如果我必须选择一个钩子,我会选择before_save
钩子。但是,这对我来说不会这样做,因为我们为我们的模型提供了预览视图,并且会破坏预览视图中的URI,因为钩子永远不会被调用。在那里,我决定最好将概念分离到模块或关注点,并提供一个很好的方法来应用“猴子补丁”,确保更改字段值始终通过过滤器运行,如果是,则添加默认协议丢失。
#app/models/helpers/uri_field.rb
module Helpers::URIField
def ensure_valid_protocol_in_uri(field, default_protocol = "http", protocols_matcher="https?")
alias_method "original_#{field}=", "#{field}="
define_method "#{field}=" do |new_uri|
if "#{field}_changed?"
if new_uri.present? and not new_uri =~ /^#{protocols_matcher}:\/\//
new_uri = "#{default_protocol}://#{new_uri}"
end
self.send("original_#{field}=", new_uri)
end
end
end
end
extend Helpers::URIField
ensure_valid_protocol_in_uri :url
#Should you wish to default to https or support other protocols e.g. ftp, it is
#easy to extend this solution to cover those cases as well
#e.g. with something like this
#ensure_valid_protocol_in_uri :url, "https", "https?|ftp"
如果由于某种原因,你宁愿使用Rails Concern模式,很容易将上面的模块转换为一个关注模块(除了使用include Concerns::URIField
之外,它的使用方式完全相同:
#app/models/concerns/uri_field.rb
module Concerns::URIField
extend ActiveSupport::Concern
included do
def self.ensure_valid_protocol_in_uri(field, default_protocol = "http", protocols_matcher="https?")
alias_method "original_#{field}=", "#{field}="
define_method "#{field}=" do |new_uri|
if "#{field}_changed?"
if new_uri.present? and not new_uri =~ /^#{protocols_matcher}:\/\//
new_uri = "#{default_protocol}://#{new_uri}"
end
self.send("original_#{field}=", new_uri)
end
end
end
end
end
P.S。用Rails 3和Mongoid 2测试了上述方法 PPS如果您发现此方法重新定义和别名过于神奇,您可以选择不覆盖该方法,而是使用虚拟字段模式,非常类似于密码(虚拟,可分配质量)和encrypted_password(获取持久性,非质量可分配)并使用sanitize_url(虚拟,质量可分配)和url(获取持久化,非质量可分配)。
答案 5 :(得分:1)
使用上面提到的一些正则表达式,这里有一个方便的方法来覆盖模型上的默认网址(例如,如果你的ActiveRecord模型有一个' url'列)
def url
_url = read_attribute(:url).try(:downcase)
if(_url.present?)
unless _url[/\Ahttp:\/\//] || _url[/\Ahttps:\/\//]
_url = "http://#{_url}"
end
end
_url
end
答案 6 :(得分:0)
我不会尝试在验证中这样做,因为它不是验证的一部分。
验证是否可选择检查;如果他们搞砸了,那将是一个验证错误,这很好。
考虑使用回调(after_create
,after_validation
,无论如何),如果没有协议,则使用前置协议。
(我投了其他答案;我认为他们都比我的好。但这是另一种选择:)
答案 7 :(得分:0)
我必须为同一模型上的多个列执行此操作。
before_validation :add_url_protocol
def add_url_protocol
[
:facebook_url, :instagram_url, :linkedin_url,
:tiktok_url, :youtube_url, :twitter_url, :twitch_url
].each do |url_method|
url = self.send(url_method)
if url.present? && !(%w{http https}.include?(URI.parse(url).scheme))
self.send("#{url_method.to_s}=", 'https://'.concat(url))
end
end
end