Home > Back-end >  Eager loading mobility translations on a model collection
Eager loading mobility translations on a model collection

Time:09-26

I have a simple problem with the mobility gem. I have a simple relation in my models. Let's say Company has many Employees, and Employees have a translated attribute :job_function that uses backend: :table.

class Company < ApplicationRecord
    has_many :employees
end

class Employee < ApplicationRecord
    extend Mobility

    translates :job_function, type: :string, locale_accessors: true, backend: :table
end

If I try to do:

Company.first.employees.map(&:job_function)

I get the n 1 problem. Each of the :job_function translations is loaded individually.

How do I tell Mobility to eager load them all in one go before I start mapping over the collection?

I could not find any example of this in the documentation...

CodePudding user response:

You can just use pluck, which is supported by Mobility:

Company.first.employees.pluck(:job_function)

CodePudding user response:

You could includes employees theirs job_function translation, it's n 2 query

here's my demo, with Product model contains many Versions model which has been set up mobility for the name attribute, those corresponding to your Company, Employee, job_function respectively.

class Version < ApplicationRecord
  belongs_to :product

  extend Mobility
  translates :name, locale_accessors: [:en, :ja], backend: :table
end

class Product < ApplicationRecord
 has_many :versions

 has_many :translation_versions, -> { 
   i18n{name.not_eq(nil)}
   .select("versions.*, 
            version_translations_#{Mobility.locale}.name AS translate_name")
  }, class_name: "Version"
end

With default locale :en

Product.includes(:translation_versions).first.translation_versions
   .map(&:translate_name)

# SELECT "products".* FROM "products" ...
# SELECT versions.*, version_translations_en.name AS translate_name 
#  FROM "versions" LEFT OUTER JOIN "version_translations" "version_translations_en" 
#  ON "version_translations_en"."version_id" = "versions"."id" 
#  AND "version_translations_en"."locale" = 'en' 
#  WHERE "version_translations_en"."name" IS NOT NULL AND "versions"."product_id" = ? ...

# => ["v1", "v2"]

With locale :ja

Mobility.locale = :ja
Product.includes(:translation_versions).first.translation_versions
   .map(&:translate_name)
# ...
# => ["ja v1", "ja v2"]

So just one query.

In case your backend setting is KeyValue, the translation tables separate not only locale but also the type (String, Text, ...), but you already decide which type for the attribute, right ? for example: name:string. So that you still only need to setup dynamic locale.

class Product < ApplicationRecord
 has_many :translation_versions, -> { 
  i18n{name.not_eq(nil)}
   .select("versions.*, 
     Version_name_#{Mobility.locale}_string_translations.value AS translate_name")
  }, class_name: "Version"
end

the query is same as above.

Of course, you could make translation_versions more generally by separate it into a module, replace Version by class name, name by the target attribute and make a dynamic function something like translation_#{table_name}.

  • Related