module Voltron::Translatable

Public Instance Methods

translates(*attributes) click to toggle source
Calls superclass method
# File lib/voltron/translatable.rb, line 4
def translates(*attributes)
  include InstanceMethods

  options = (attributes.extract_options!).with_indifferent_access
  locales = Array.wrap(options[:locales] || Voltron.config.translate.locales).map { |l| l.to_s.underscore }

  attributes.each do |attrib|

    column = self.columns_hash[attrib.to_s]

    raise ::ActiveRecord::UnknownAttributeError.new(self.new, attrib) if column.nil?

    raise ::Voltron::Translate::InvalidColumnTypeError.new("Invalid type '#{column.type}' for attribute: #{attrib}. Translations only work on string and text attribute types.") unless [:string, :text].include?(column.type)

    # Override the attribute with a method that accepts a specific locale as an argument
    # If specified, will attempt to fetch that locale's translation, otherwise the default
    # locale specified for the attribute, and ultimately the current locale translation
    # If still nil, returns the value from super
    define_method :"#{attrib}" do |locale=nil|
      # +action_view/helpers/tags+ exist when this method is called from within
      # ActionView::Helpers. In other words, form helper tags. In that
      # case we want the actual value of the attribute, not whatever the locale is
      return super() if caller.any? { |l| /action_view\/helpers\/tags/.match(l) } || !Voltron.config.translate.enabled?
      try(:"#{attrib}_#{locale.to_s.underscore}") || try(:"#{attrib}_#{options[:default].to_s.underscore}") || try(:"#{attrib}_#{I18n.locale.to_s.underscore}") || super()
    end

    locales.each do |locale|

      attribute :"#{attrib}_#{locale}"

      # Define setter, i.e. - +attribute_es=+
      define_method :"#{attrib}_#{locale}=" do |val|
        attribute_will_change! "#{attrib}_#{locale}"
        instance_variable_set("@#{attrib}_#{locale}", val)
      end

      # Define getter, i.e - +attribute_es+
      # If nil, calling this method will attempt to fetch the value
      # We do this to avoid preloading the translations association records
      define_method :"#{attrib}_#{locale}" do
        if instance_variable_get("@#{attrib}_#{locale}").nil?
          instance_variable_set("@#{attrib}_#{locale}", send(:"#{attrib}_#{locale}_was"))
        end
        instance_variable_get("@#{attrib}_#{locale}")
      end

      # Define the changed? method, i.e. - +attribute_es_changed?+
      define_method :"#{attrib}_#{locale}_changed?" do
        changed.include?("#{attrib}_#{locale}")
      end

      # Define the was method, i.e. - +attribute_es_was+
      define_method :"#{attrib}_#{locale}_was" do
        translations.find_by(attribute_name: attrib, locale: locale).try(:translation)
      end

      define_method :"#{attrib}_#{locale}_will_change!" do
        attribute_will_change! "#{attrib}_#{locale}"
      end

      define_method :"#{attrib}_#{locale}?" do
        instance_variable_get("@#{attrib}_#{locale}").present?
      end

    end
  end

  # In case +translates+ was called multiple times, merge in the new attributes/locales
  # with the pre-existing ones
  all_attributes = @_translations.try(:keys) || []
  all_attributes += attributes
  all_attributes.uniq!

  all_locales = @_translations.try(:values) || []
  all_locales += locales
  all_locales.flatten!
  all_locales.uniq!

  has_many :translations, as: :resource, class_name: 'Voltron::Translation', dependent: :destroy

  before_save :build_translations

  accepts_nested_attributes_for :translations, reject_if: :all_blank, allow_destroy: true
  
  @_translations = all_attributes.map { |a| { a.to_s => all_locales } }.reduce(Hash.new, :merge)
end