I think the comma after the before_validation method is comma right?
–
sq1020Mar 31 '13 at 18:57

2

This shouldn't be a validation, but merely a before_save hook. The purpose of validations are to invalidate the instance (preventing it from saving), which is not the case here.
–
koffeinfreiNov 20 '13 at 8:30

1

You're getting confused. This isn't a validation. It's a method that's run before the validations are run.
–
Douglas F ShearerDec 9 '13 at 15:27

Don't do this with a regex, use URI.parse to pull it apart and then see if there is a scheme on the 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

Using the URI library for this also makes it easy to clean up any stray nonsense (such as userinfo) that someone might try to put into a URL.

We use Addressable for exactly this. It's a little weird if there's no scheme in the URL, however, since it considers the host to be the path.
–
d11wtqOct 26 '11 at 21:33

@d11wtq I just switched to Addressable to get sane and consistent UTF-8 support in URLs.
–
mu is too shortFeb 6 '14 at 5:58

I must advice against this dirty trick. It "works" with simple paths like /pancakes, but why would anybody want to enforce protocol on paths? However, if we're talking about "web addresses" as normal human beings understand and write them, then using URI will not parse them correctly. That is because most people leave out the double forward slashes indicating the beginning of authority definition out of "web addresses". In fact I believe many think they belong to the protocol definition, but they do not. To be continued... (sorry for the super long double post)
–
Timo LehtoApr 14 '14 at 11:41

When "URLs" like these are then parsed using some specifications abiding component like Rubys URI the result is that there is no host in the URI object, but it is inferred as a mere path in its entirety. This is unlikely what is intended and gives a false impression that you have a properly parsed URI object at your disposal, but if someone was to modify any of its components the results would be surprising. To properly parse a "web address" as a URI you should always ensure first that the forward dashes are in place.
–
Timo LehtoApr 14 '14 at 11:41

@TimoLehto: I don't get your point. Do you have an example where this fails?
–
mu is too shortApr 14 '14 at 16:39

Preface, justification and how it should be done

I hate it when people change model in a before_validation hook. Then when someday it happens that for some reason models need to be persisted with save(validate: false), then some filter that was suppose to be always run on assigned fields does not get run. Sure, having invalid data is usually something you want to avoid, but there would be no need for such option if it wasn't used. Another problem with it is that every time you ask from a model is it valid these modifications also take place. The fact that simply asking if a model is valid may result in the model getting modified is just unexpected, perhaps even unwanted. There for if I'd have to choose a hook I'd go for before_save hook. However, that won't do it for me since we provide preview views for our models and that would break the URIs in the preview view since the hook would never get called. There for, I decided it's best to separate the concept in to a module or concern and provide a nice way for one to apply a "monkey patch" ensuring that changing the fields value always runs through a filter that adds a default protocol if it is missing.

In your model

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"

As a concern

If for some reason, you'd rather use the Rails Concern pattern it is easy to convert the above module to a concern module (it is used in an exactly similar way, except you use 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. The above approaches were tested with Rails 3 and Mongoid 2.
P.P.S If you find this method redefinition and aliasing too magical you could opt not to override the method, but rather use the virtual field pattern, much like password (virtual, mass assignable) and encrypted_password (gets persisted, non mass assignable) and use a sanitize_url (virtual, mass assignable) and url (gets persisted, non mass assignable).