Files
nairobi/app/models/concerns/globalizable.rb
Javi Martín a76ed31a94 Remove unneeded scope
In this case using `joins` doesn't prevent N+1 queries to get titles for
every record, and since we cannot order translations with just SQL due
to fallbacks, we don't need it.

Automatic SQL injection checks were showing a false positive in this
scope; there was no real vulnerability here because foreign keys, table
names and locales were under our control.
2019-11-13 19:52:15 +01:00

100 lines
3.1 KiB
Ruby

module Globalizable
MIN_TRANSLATIONS = 1
extend ActiveSupport::Concern
included do
globalize_accessors
accepts_nested_attributes_for :translations, allow_destroy: true
validate :check_translations_number, on: :update, if: :translations_required?
after_validation :copy_error_to_current_translation, on: :update
def locales_not_marked_for_destruction
translations.reject(&:marked_for_destruction?).map(&:locale)
end
def locales_marked_for_destruction
I18n.available_locales - locales_not_marked_for_destruction
end
def locales_persisted_and_marked_for_destruction
translations.select { |t| t.persisted? && t.marked_for_destruction? }.map(&:locale)
end
def translations_required?
translated_attribute_names.any? { |attr| required_attribute?(attr) }
end
if self.paranoid? && translation_class.attribute_names.include?("hidden_at")
translation_class.send :acts_as_paranoid, column: :hidden_at
end
private
def required_attribute?(attribute)
presence_validators = [ActiveModel::Validations::PresenceValidator,
ActiveRecord::Validations::PresenceValidator]
attribute_validators(attribute).any? { |validator| presence_validators.include? validator }
end
def attribute_validators(attribute)
self.class.validators_on(attribute).map(&:class)
end
def check_translations_number
errors.add(:base, :translations_too_short) unless traslations_count_valid?
end
def traslations_count_valid?
translations.reject(&:marked_for_destruction?).count >= MIN_TRANSLATIONS
end
def copy_error_to_current_translation
return unless errors.added?(:base, :translations_too_short)
if locales_persisted_and_marked_for_destruction.include?(I18n.locale)
locale = I18n.locale
else
locale = locales_persisted_and_marked_for_destruction.first
end
translation = translation_for(locale)
translation.errors.add(:base, :translations_too_short)
end
def searchable_globalized_values
values = {}
translations.each do |translation|
Globalize.with_locale(translation.locale) do
values.merge! searchable_translations_definitions
end
end
values
end
end
class_methods do
def validates_translation(method, options = {})
validates(method, options.merge(if: lambda { |resource| resource.translations.blank? }))
if options.include?(:length)
lenght_validate = { length: options[:length] }
translation_class.instance_eval do
validates method, lenght_validate.merge(if: lambda { |translation| translation.locale == I18n.default_locale })
end
if options.count > 1
translation_class.instance_eval do
validates method, options.reject { |key| key == :length }
end
end
else
translation_class.instance_eval { validates method, options }
end
end
def translation_class_delegate(method)
translation_class.instance_eval { delegate method, to: :globalized_model }
end
end
end