docbrown/app/models/profile.rb
Daniel Uber 6f84f4afe8
Validate profile field "website url" is in fact a url (don't generate local path links). (#14302)
* Validate website_url profile field is a url

related to issue #14300

May need a data update script to (fixup? remove?) invalid profiles
since not being able to save existing profiles will cause problems.

* Check that URI is a valid url

Require the scheme to be one of https or http, not something like
mailto:// or telnet:// (nobody would do that, but don't try and link
to it if they did).

* Rubocop fixup for validation rule

URI::regexp is obsolete and should not be used. Instead, use URI::DEFAULT_PARSER.make_regexp

Prefer %w[] literals for arrays of words

Prefer the new style validations `validates :column, format: value` to validates_format_of

* Permit empty website_url fields

The previous validation was rejecting nil, which was the default. This
caused a lot of user factory calls to fail (since users didn't need
website urls in the profiles during testing unless the test was about
the website url link).

* Use :url validation as suggested

* Update validation error message

The validate_url gem gives a more complete error message to the
user. Update the spec to expect this.

* Use url_field rather than text_field in profile form

https://apidock.com/rails/v6.1.3.1/ActionView/Helpers/FormHelper/url_field

* Add data update to either fixup or clear invalid website urls

Checking blazer there are about 2600 profiles with "invalid" website
urls, typically a hostname, sometimes a hostname + path component,
which should be fixed up.

Naively add https:// to the front of the url, check that a valid
scheme and host are present on the resulting url, and save.

If an invalid url is generated (specifically, if host or scheme are
nil), just set the website url (with the invalid link) to an empty
string.

It's possible we'd want to notify users affected by this, that was not
included in this pass.

* Skip validations for intentionally invalid test cases
2021-07-22 12:02:29 -05:00

65 lines
2 KiB
Ruby

class Profile < ApplicationRecord
belongs_to :user
validates :user_id, uniqueness: true
validates :location, :website_url, length: { maximum: 100 }
validates :website_url, url: { allow_blank: true, no_local: true, schemes: %w[https http] }
validates_with ProfileValidator
# Static fields are columns on the profiles table; they have no relationship
# to a ProfileField record. These are columns we can safely assume exist for
# any profile on a given Forem.
STATIC_FIELDS = %w[summary location website_url].freeze
SPECIAL_DISPLAY_ATTRIBUTES = %w[
summary
employment_title
employer_name
employer_url
location
].freeze
# NOTE: @citizen428 This is a temporary mapping so we don't break DEV during
# profile migration/generalization work.
MAPPED_ATTRIBUTES = {
education: :education,
skills_languages: :mostly_work_with
}.with_indifferent_access.freeze
# Generates typed accessors for all currently defined profile fields.
def self.refresh_attributes!
return if ENV["ENV_AVAILABLE"] == "false"
return unless Database.table_available?("profiles")
ProfileField.find_each do |field|
# Don't generate accessors for static fields stored on the table.
# TODO: [@jacobherrington] Remove this when ProfileFields for the static
# fields are dropped from production and the associated data is removed.
# https://github.com/forem/forem/pull/13641#discussion_r637641185
next if STATIC_FIELDS.any?(field.attribute_name)
store_attribute :data, field.attribute_name.to_sym, field.type
end
end
# Set up all profile attributes when this class loads so all store_attribute
# accessors get defined immediately.
refresh_attributes!
# Returns an array of all currently defined `store_attribute`s on `data`.
def self.attributes
(stored_attributes[:data] || []).map(&:to_s)
end
def self.special_attributes
SPECIAL_DISPLAY_ATTRIBUTES
end
def self.static_fields
STATIC_FIELDS
end
def clear!
update(data: {})
end
end