Rails 4.1.2 - to_param избегает слешей (и ломает приложение)

Я использую в своем приложении to_param создать собственный URL (этот пользовательский путь содержит косую черту):

class Machine < ActiveRecord::Base
  def to_param
    MachinePrettyPath.show_path(self, cut_model_text: true)
  end
end

Дело в том, что с Rails 4.1.2 изменилось поведение, и Rails не позволяет использовать косые черты в URL (при использовании пользовательских URL), поэтому он избегает косых черт.

У меня были такие маршруты:

Rails.application.routes.draw do
  scope "(:locale)", locale: /#{I18n.available_locales.join("|")}/ do
      resources :machines, except: :destroy do
          collection do
            get  :search
            get  'search/:ad_type(/:machine_type(/:machine_subtype(/:brand)))', action: 'search', as: :pretty_search

            get  ':subcategory/:brand(/:model)/:id', action: 'show', as: :pretty
            patch ':subcategory/:brand(/:model)/:id', action: 'update'                                  # To be able to update machines with new rich paths.
          end
      end
  end
end

Я попытался по рекомендации в потоке использовать параметр glob только для метода show, чтобы убедиться, что он работает:

resources :machines, except: :destroy do
 #...
end

scope format: false do
 get '/machines/*id', to: "machines#show"
end

Но это абсолютно не работает. У меня еще есть такие неработающие ссылки:

http://localhost:3000/machines/tractor%2Fminitractor%2Fmodel1%2F405

Конечно, если я заменю на себя спасшиеся косые черты:

http://localhost:3000/machines/tractor/minitractor/model1/405

И попробуйте зайти в путь, тогда страница откроется.

Любые идеи, как я могу это исправить?

3 ответа

Решение

У меня возникла та же проблема при использовании автоматически сгенерированных помощников URL. Я использовал отладчик для отслеживания нового поведения до его источника (где-то около ActionDispatch:: Journey:: Customers:: Formatter), но не нашел многообещающих решений. Похоже, что параметризованная модель теперь строго обрабатывается как одиночный сегмент пути с разделителями-косыми чертами и соответственно экранируется, без параметров, которые могли бы указать форматеру иначе.

Насколько я могу судить, единственный способ получить помощника url для получения старого результата - использовать исходный файл маршрутов и передавать каждый сегмент отдельно, что-то вроде:

pretty_machine_path(machine.subcategory, machine.brand, machine.model, machine.id)

Это чертовски уродливо и, очевидно, не то, что вы захотите делать снова и снова. Вы можете добавить метод в MachinePrettyPath, чтобы генерировать сегменты в виде массива и анализировать результат для помощника (скажем, pretty_machine_path(*MachinePrettyPath.show_path_segments(machine))) но это все еще довольно многословно.

Между вышеупомянутыми головными болями и отношением "вы делаете это неправильно" от разработчиков в билете Rails, на который вы ссылались, для меня самым простым вариантом было укусить маркер и написать собственный помощник по URL вместо использования to_param. Я еще не нашел хороший пример "правильного" способа сделать это, но что-то вроде этого простого примера должно служить цели:

#app/helpers/urls_helper.rb
module UrlsHelper
  def machine_path(machine, options = {})
    pretty_machine_path(*MachinePrettyPath.show_path_segments(machine), options)
  end
end

#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  helper :urls #for the views
  include UrlsHelper #for controllers
  #...
end

Если вы уверены, что возвращенный URL-адрес безопасен, вы должны добавить.html_safe к возвращаемой строке:

MachinePrettyPath.show_path(self, cut_model_text: true).html_safe

(больше нигде не видел, где его можно экранировать, но проверьте весь поток в вашем приложении, возможно, вручную тестируйте метод по методам)

Как насчет определения URL самостоятельно?

def to_param
  "#{ subcategory.title.parameterize }/#{ brand.name.parameterize }/#{ model.name.parameterize) }/#{ id }"
end

И тогда в ваших маршрутах что-то вроде этого:

get 'machines/*id', to: "machines#show"

Вы также должны разделить свои параметры [:id], когда вы делаете поиск по своей модели.

Machine.find( params[:id].split("/").last ) 
Другие вопросы по тегам