mongo_mapper custom data types for localization

530 views Asked by At

i have created a LocalizedString custom data type for storing / displaying translations using mongo_mapper.

This works for one field but as soon as i introduce another field they get written over each and display only one value for both fields. The to_mongo and from_mongo seem to be not workings properly. Please can any one help with this ? her is the code :

class LocalizedString

  attr_accessor :translations

  def self.from_mongo(value)

    puts self.inspect
    @translations ||= if value.is_a?(Hash)
        value
      elsif value.nil?
        {}
      else
        { I18n.locale.to_s => value }
    end

    @translations[I18n.locale.to_s]
  end

  def self.to_mongo(value)
    puts self.inspect
    if value.is_a?(Hash)
      @translations = value  
    else
      @translations[I18n.locale.to_s] = value
    end

    @translations
  end
end

Thank alot Rick

3

There are 3 answers

1
jared On BEST ANSWER

The problem is that from within your [to|from]_mongo methods, @translations refers to a class variable, not the instance variable you expect. So what's happening is that each time from_mongo is called, it overwrites the value.

A fixed version would be something like this:

class LocalizedString
  attr_accessor :translations

  def initialize( translations = {} ) 
    @translations = translations
  end 

  def self.from_mongo(value)
    if value.is_a?(Hash)
      LocalizedString.new(value)
    elsif value.nil?
      LocalizedString.new()
    else
      LocalizedString.new( { I18n.locale.to_s => value })
    end
  end

  def self.to_mongo(value)
    value.translations if value.present?
  end

end
2
adamnickerson On

I found that jared's response didn't work for me -- I would get that translations was not found when using LocalizedString in an EmbeddedDocument.

I would get a similar problem on rick's solution where translations was nil when using embedded documents. To get a working solution, I took Rick's solution, changed the translation variable to be an instance variable so it wouldn't be overwritten for each new field that used LocalizedString, and then added a check to make sure translations wasn't nil (and create a new Hash if it was).

Of all the LocalizedString solutions floating around, this is the first time I've been able to get it working on EmbeddedDocuments and without the overwritting problem -- there still may be other issues! :)

class LocalizedString
  attr_accessor :translations

    def self.from_mongo(value)

        puts self.inspect
        translations ||= if value.is_a?(Hash)
            value
          elsif value.nil?
            {}
          else
            { I18n.locale.to_s => value }
        end

        translations[I18n.locale.to_s]
      end

      def self.to_mongo(value)
        puts self.inspect
        if value.is_a?(Hash)
          translations = value
        else
          if translations.nil?
            translations = Hash.new()
          end
          translations[I18n.locale.to_s] = value
        end

        translations
      end

    end
0
adamnickerson On

I found this post: which was very helpful. He extended HashWithIndifferentAccess to work as a LocalizedString. The only thing I didn't like about it was having to explicly specify the locale when setting it each time -- I wanted it to work more like a string. of course, you can't overload the = operator (at least I don't think you can) so I used <<, and added a to_s method that would output the string of the current locale....

class LocalizedString < HashWithIndifferentAccess
  def self.from_mongo(value)
    LocalizedString.new(value || {})
  end

  def available_locales
    symbolize_keys.keys
  end

  def to_s
    self[I18n.locale]
  end

  def in_current_locale=(value)
    self[I18n.locale] = value
  end

   def << (value)
    self[I18n.locale] = value
   end

end

and then I have a class like:

class SimpleModel
  include MongoMapper::Document

  key :test, LocalizedString
end

and can do things like

 I18n.locale = :en
  a = SimpleModel.new
  a.test << "English"
  I18n.locale = :de
  a.test << "German"
  puts a.test # access the translation for the current locale
  I18n.locale = :en
  puts a.test # access the translation for the current locale
  puts a.test[:de] # access a translation explicitly 
  puts a.test[:en]
  puts a.test.inspect

and get

German
English
German
English
{"en"=>"English", "de"=>"German"}

so there we go -- this one actually seems to work for me. Comments welcome, and hope this helps someone!