ruby-on-railsrubylocalizationeager-loadingglobalize

Rails Globalize - Eager Load Specific Translations by Locale


So I have a model in Rails, with globalize installed and configured:

model.rb

class Model < ApplicationRecord
  include TranslationWrites
  translates :column
  ...
end

I want to Eager Load "English" and "French" translations only, to prevent an N+1 query problem while looping. I've tried a few ways, but the query will not work as expected. Here's what I've tried:

Model.includes(:translations).first

Which executes the following SQL:

Model Load (...)
SELECT * FROM models ORDER BY id ASC LIMIT 1

Model::Translations Load (...)
SELECT * FROM model_translations WHERE model_translations.model_id = 1

That's perfect for Eager Loading all translations, but I only want "English" and "French". I tried this code:

Model.includes(:translations).where(translations: { locale: ['en', 'fr'] }).first

That runs the following incorrect SQL:

Model Load (...)
SELECT * FROM models LEFT OUTER JOIN model_translations ON model_translations.model_id = models.id WHERE translations.locale IN ('en', 'fr')

ActiveRecord::StatementInvalid (Mysql2::Error: Unknown column 'translations.locale' ...)

As you can see, conditional eager loading (as see on this tutorial) doesn't seem to work...

Lastly, Globalize has the with_translations(['en', 'fr']) method, but that scopes the Models returned to only those that have the specified translations. I need those Models regardless of whether or not they have "English" or "French". If you're familiar with Laravel, it's the difference between with() and whereHas():

// Returns all `Model` instances, and each `$model->translations` will have `en` and `fr` only.
Model::with(['translations' => function($query) {
  $query->whereIn('locale', ['en', 'fr'])
})->get();

// Only returns `Model` instances that have `en` or `fr` translations, but `$model->translations` will include all translations
$models = Model::whereHas('translations', function($query) {
  $query->whereIn('locale', ['en', 'fr'])
})->get();

Has anyone come across this? I need an efficient way to only load English and French translations for each Model in the database, regardless of whether or not that translations actually exists.


Solution

  • Generally speaking, in your .where method you should use the real table name, not the relationship's name between your models, so try with:

    Model.includes(:translations).where(model_translations: { locale: ['en', 'fr'] }).first