Предисловие, обоснование и как это должно быть сделано
Я ненавижу, когда люди меняют модель на крючке before_validation
. Затем, когда когда-нибудь случится так, что по какой-то причине модели должны быть сохранены с сохранением (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 (сохраняется, не массовый назначаемый).