app/models/concerns/globalizable.rb
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_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 paranoid? && translation_class.attribute_names.include?("hidden_at")
translation_class.send :acts_as_paranoid, column: :hidden_at
end
private
def required_attribute?(attribute)
self.class.validators_on(attribute).any? do |validator|
validator.kind == :presence && !conditional_validator?(validator)
end
end
def conditional_validator?(validator)
return false unless validator.options[:unless]
if validator.options[:unless].to_proc.arity.zero?
instance_exec(&validator.options[:unless])
else
validator.options[:unless].to_proc.call(self)
end
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)
translation_class.instance_eval do
validates method,
length: options[:length],
if: lambda { |translation| translation.locale == Setting.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
def with_fallback_translation
translations_foreign_key = reflect_on_association(:translations).foreign_key
fallbacks = Globalize.fallbacks(Globalize.locale)
translations_ids = translation_class
.select("DISTINCT ON (#{translations_foreign_key}) id")
.where(locale: fallbacks)
.order(translations_foreign_key)
.in_order_of(:locale, fallbacks)
with_translations(fallbacks).where("#{translations_table_name}.id": translations_ids)
end
end
end