ruby-on-rails - image_tag - rails 5 link_to class
Añadir http(s) a la URL si no está allí? (7)
Estoy usando esta expresión regular en mi modelo para validar una URL enviada por el usuario. No quiero obligar al usuario a escribir la parte http, pero me gustaría agregarla yo mismo si no está allí.
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" }
¿Alguna idea de cómo podría hacer eso? Tengo muy poca experiencia con la validación y la expresión regular ...
Prefacio, justificación y cómo se debe hacer
Odio cuando las personas cambian el modelo en un anzuelo before_validation
. Luego, cuando algún día sucede que por alguna razón los modelos necesitan persistir con save (validate: false), entonces no se ejecuta ningún filtro que supuestamente se ejecutará siempre en los campos asignados. Claro, tener datos no válidos es algo que normalmente quieres evitar, pero no habría necesidad de tal opción si no se usara. Otro problema es que cada vez que preguntas desde un modelo es válido que estas modificaciones también se lleven a cabo. El hecho de que simplemente preguntarse si un modelo es válido puede hacer que el modelo que se modifique sea simplemente inesperado, quizás incluso no deseado. Si tuviera que elegir un gancho, before_save
gancho before_save
. Sin embargo, eso no lo hará por mí ya que proporcionamos vistas de vista previa para nuestros modelos y eso rompería los URI en la vista de vista previa ya que nunca se llamaría al gancho. Por eso, decidí que es mejor separar el concepto en un módulo o asunto y proporcionar una buena manera para que uno aplique un "parche mono" asegurando que el cambio del valor de los campos siempre se ejecuta a través de un filtro que agrega un protocolo predeterminado si es desaparecido.
El módulo
#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
En tu modelo
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"
Como una preocupación
Si por alguna razón prefieres utilizar el patrón Rails Concern, es fácil convertir el módulo anterior en un módulo de preocupación (se usa de manera exactamente similar, excepto que uses 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
PS Los enfoques anteriores se probaron con Rails 3 y Mongoid 2.
PPS Si encuentra que este método de redefinición y alias es demasiado mágico, puede optar por no anular el método, sino más bien usar el patrón de campo virtual, al igual que la contraseña (virtual, asignable en masa) y la contraseña cifrada (se mantiene persistente, no asignable en masa) y sanitize_url (virtual, asignable en masa) y url (se mantiene persistente, no asignable en masa).
La respuesta aceptada está bastante bien. Pero si el campo (url) es opcional, puede generar un error como el undefined method
+ para la clase nil
. Lo siguiente debería resolver eso:
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
No haga esto con una expresión regular, use URI.parse
para separarla y luego vea si hay un esquema en la URL:
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
El uso de la biblioteca de URI para esto también facilita la limpieza de cualquier tontería perdida (como userinfo) que alguien pueda intentar poner en una URL.
No trataría de hacer eso en la validación, ya que no es parte de la validación.
Haga que la validación opcionalmente lo verifique; si lo arruinan será un error de validación, lo cual es bueno.
Considere usar una devolución de llamada ( after_create
, after_validation
, whatever) para anteponer un protocolo si ya no hay uno.
(Elegí las otras respuestas, creo que ambas son mejores que las mías. Pero aquí hay otra opción :)
Según la respuesta de mu, aquí está el código que estoy usando en mi modelo. Se ejecuta cuando: el enlace se guarda sin la necesidad de filtros de modelo. Se requiere Super para llamar al método de guardado predeterminado.
def link=(_link)
u=URI.parse(_link)
if (!u.scheme)
link = "http://" + _link
else
link = _link
end
super(link)
end
Usando algunas de las expresiones regulares mencionadas anteriormente, este es un método útil para anular la url predeterminada en un modelo (si su modelo ActiveRecord tiene una columna ''url'', por ejemplo)
def url
_url = read_attribute(:url).try(:downcase)
if(_url.present?)
unless _url[//Ahttp://///] || _url[//Ahttps://///]
_url = "http://#{_url}"
end
end
_url
end
Use un filtro anterior para agregarlo si no está allí:
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
Deje la validación que tiene, de esa manera si hacen un error ortográfico pueden corregir el protocolo.