I needed a URL validator - it needed to check that URL's had HTTP or HTTPS at the beginning. I initially wrote a "before save" action, but didn't like it cluttering up my model. I found a reference to basically "monkey patching" Rail's default Validator class, so that's what I did!
# concerns/url_validator.rb
require 'uri'
class UrlValidator < ActiveModel::Validator
def validate(record)
if record.url?
uri = URI.parse(record.url)
unless uri.kind_of?(URI::HTTP) || uri.kind_of?(URI::HTTPS)
raise URI::InvalidURIError
record.errors.add(:url, "is invalid: #{uri}")
end
end
end
end
# spec/models/link_spec.rb
RSpec.describe Link do
context "validations" do
it { should validate_presence_of(:title)}
it { should validate_presence_of(:url)}
it "should not save invalid ULR" do
title = "test url"
invalid_url = "www.google"
link = Link.new(title: title, url: invalid_url)
expect {link.save}.to raise_error(URI::InvalidURIError)
end
it "should save valid URL" do
title = "test url"
valid_url = "http://google.com"
Link.create!(title: title, url: valid_url)
expect(Link.count).to eq(1)
end
end
# more model tests here
# controllers/linkds_controller.rb
class LinksController < ApplicationController
.
.
.
def create
link = current_user.links.create(link_params)
# now checking only for "if link.save", instead of chained validations
# refactored the following down
# if link.valid_url? && link.save
if link.save
flash[:success] = "created new link"
.
.
.
end
# models/link.rb
class Link < ActiveRecord::Base
include ActiveModel::Validations
validates_with UrlValidator
validates :title, :url, presence: true