Таинственные гемы - Prepend Routes

April 27, 2012

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

Если говорить серьезно, я сталкивался с дискуссиями о том, что пространство имен рубиджемс очень сильно замусоренно и лучше не создавать гемы для своих внутренних нужд. Мне кажется проблема надуманная. Я не выкидываю бумажки из под мороженного на землю на основании того, что наша планета и так замусоренная, но экономия на именах мне напоминает шуточною статью, которая рекомендовала беречь GUID'ы, так как их осталось очень мало. (Если вы программировали под виндовс, то заметили, что гуиды там используются очень активно. Это смешная шутка, правда?)

Итак гемы. Вообще наибольший интерес для меня представляют так называемые энджины. Они эмитируют маленький рейлс-проект и позволяют выносить в гем контроллеры, модели, миграции, ассеты, рауты, все что захочется. Одна из проблема как раз связана с раутами. Вы можете добавить рауты следующим образом:

# lib/custom_gem/engine.rb
module CustomGem
  class Engine < ::Rails::Engine
  end
end

# app/controllers/custom_controller.rb
class CustomController
  def index
    # ...
  end
end

# config/routes.rb
Rails.application.routes.draw do
  get 'custom/index'
end

Это будет работать прекрасно во всех случаях, кроме одного, если кто-то использует раут со звездочкой для мэтчинга по шаблону (например для статических страниц о компании, контакты и т.д.):

MyApp::Application.routes.draw do
  # ...
  get '*permalink' => 'home#page'
end

В этом случае кастомный раут у гема будет затерт. Я столкнулся с этим в своем форке rails-footnotes, когда контроллер, который в девелопменте позволяет посмотреть исходный руби-файл прямо в браузере, перестал работать. Есть очень простая, но недокументированная возможность - метод prepend actiondispatch/routing/routeset.rb#L306, с помощью которого можно поместить раут на самый верх:

# 1-й вариант (НЕ ПРАВИЛЬНО)
# ------------
# Вот этот вариант НЕ ЗАРАБОТАЛ! Не знаю почему, мне лень стало разбираться.

# config/routes.rb
# Заменяем draw на prepend
Rails.application.routes.prepend do
  get 'custom/index'
end
# 2-й вариант (рабочий)
# ------------

# config/routes.rb - удаляем вообще

# lib/custom_gem/engine.rb
module CustomGem
  class Engine < ::Rails::Engine
    initializer 'custom' do |app|
      ActiveSupport.on_load :after_initialize do
        app.routes.prepend do
          get 'custom/index'
        end
      end
    end
  end
end

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

comments powered by Disqus