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}
.