Недавно заботливые товарищи от Mephisto и Radiant закоммитили в рельсы смену template_root на view_paths, дабы можно было автоматом доливать пути к шаблонам в контроллеры.

Тем не менее они забыли о простом кейсе:

  1. У нас есть фирмы А, Б, В
  2. Все они пользуются одной админкой с похожим функционалом, но их сайты оформлены отдельно и у каждого сайта есть своя директория views
  3. При получении запроса мы вычисляем, на какой домен он пришел и используем темплейты фирм А, Б или В соответственно.

Дабы сэкономить читателям время, теперь это делается так:

class MyController < ...
   def view_paths
    @future_path ||= TemplatePathPromise.new
    super + [@future_path]
  end
   
   before_filter :add_custom_templates
   protected
   def add_custom_templates
      @future_path.string = RAILS_ROOT + '/client-templates/' + @client.domain
   end
end

При этом тот самый TemplatePathPromise выглядит так

class TemplatePathPromise
  attr_accessor :string
  def to_s; path; end
  def to_str; path; end
  def path
    @computed ||= (File.exist?(@string.to_s) ? @string.to_s : (RAILS_ROOT + '/app/views'))
  end
end

В чем здесь хитрость, спросят читатели (и зачем собственно?) Ответ прост – view_paths у вашего контроллера спросят до того как вызовутся ваши фильтры, и даже более – до того как ваш контроллер получит обьект request.

Соответственно при вызове этого метода невозможно иньектировать в пути к шаблонам уже просчитанное значение с учетом домена, поскольку домен взять неоткуда (request еще нет).

Следовательно мы делаем трюк и пользуемся Promise Objects.

A promise is an object which delays computing some value until such time as that value is actually needed. If it’s never needed, the computation is never performed.

Подробно трюк описан (и плотно заюзан) ребятами из SixApart еще несколько лет назад.

То есть примерно то же самое что и ассоциация ActiveRecord. В данном случае у нас должен получиться обьект, который сам умеет прикинуться строкой. В Ruby надо для этого определить два метода, а не один только to_s - для автоматического приведения типов необходимы оба. Поскольку все всегда работает по ссылке, мы делаем простой трюк:

  1. Создаем обьект “почти строка” и сохраняем на него ссылку и в результате view_paths и в контроллере.
  2. Учим этот обьект отдавать директорию стандартных шаблонов если кастомная директория переданная ему не существует
  3. Уже в нашем фильтре устанавливаем истинное значение обьекта на нужную строку.

Просто занятный казус, но при необходимости трюк можно повторить и для других случаев.