Пользовательские параметры в URL для действия show
Я работаю над реализацией SEO-hiarchy, а это значит, что мне нужно добавить параметры для действия show.
Пример использования - это сайт поиска, на котором находится URL-структура:
/cars/(:brand)/
= > страница списка
/cars/(:brand)/(:model_name)?s=query_params
= > действие поиска
/cars/:brand/:model_name/:variant/:id
= > действие шоу автомобиля
Моя проблема заключается в том, чтобы заставить URL-адреса действия показывать, не предоставляя :brand
, :model_name
и :variant
как отдельные аргументы. Они всегда доступны из значений на ресурсе.
Что у меня есть:
/cars/19330-Audi-A4-3.0-TDI
Что я хочу
/cars/Audi/A4/3.0-TDI/19330
Раньше это выглядело как routes.rb
:
# Before
resources :cars. only: [:show] do
member do
get 'favourize'
get 'unfavourize'
end
Следующей была моя первая попытка:
# First attempt
scope '/cars/:brand/:model_name/:variant' do
match ":id" => 'cars_controller#show'
match ":car_id/favourize" => 'cars_controller#favourize', as: :favourize_car
match ":car_id/unfavourize" => 'cars_controller#unfavourize', as: :unfavourize_car
end
Это позволяет сделать:
cars_path(car, brand: car.brand, model_name: car.model_name, variant: car.variant)
Но это явно не идеально.
Как можно настроить маршруты (и, возможно, метод .to_param
?) таким образом, чтобы не было утомительной задачей изменить все вызовы link_to
?
Спасибо заранее!
- ОБНОВЛЕНИЕ -
С предложением @tharrisson это то, что я пробовал:
# routes.rb
match '/:brand/:model_name/:variant/:id' => 'cars#show', as: :car
# car.rb
def to_param
# Replace all non-alphanumeric chars with - , then merge adjacent dashes into one
"#{brand}/#{model_name}/#{variant.downcase.gsub(/[^[:alnum:]]/,'-').gsub(/-{2,}/,'-')}/#{id}"
end
Маршрут работает нормально, например. /cars/Audi/A4/3.0-TDI/19930
отображает правильную страницу. Однако создание ссылки с to_param
не работает. Пример:
link_to "car link", car_path(@car)
#=> ActionView::Template::Error (No route matches {:controller=>"cars", :action=>"show", :locale=>"da", :brand=>#<Car id: 487143, (...)>})
link_to "car link 2", car_path(@car, brand: "Audi")
#=> ActionView::Template::Error (No route matches {:controller=>"cars", :action=>"show", :locale=>"da", :brand=>"Audi", :model_name=>#<Car id: 487143, (...)>})
Rails, похоже, не знает, как перевести to_param в действительную ссылку.
Ответы
Ответ 1
Я не вижу никакого способа сделать это с помощью Rails без настройки распознавания URL или генерации URL.
С вашей первой попытки вы получили работу по распознаванию URL, но не поколение. Решение, которое я вижу, чтобы заставить поколение работать, было бы переопределить вспомогательный метод car_path
.
Другим решением может быть, как и вы, в UPDATE, переопределить метод to_param
Car. Обратите внимание, что ваша проблема не в методе to_param
, а в определении маршрута: вам нужно предоставить параметры :brand
, :model_name
и :variant
, если вы хотите сгенерировать маршрут. Чтобы справиться с этим, вы можете использовать Подстановочный сегмент на вашем маршруте.
Наконец, вы также можете использовать жемчужина фильтра маршрутизации, который позволит вам добавлять логику до и после распознавания/генерации URL.
Для меня это похоже на то, что все эти решения немного тяжелы и не так просты, как должно быть, но я считаю, что это было связано с вашими потребностями, так как вы хотите добавить некоторые уровни в URL-адрес без строгого поведения рельсов, которое будет укажите URL как /brands/audi/models/A3/variants/19930
Ответ 2
Хорошо, так вот, что у меня есть. Это работает в моем маленьком тестовом случае. Очевидно, нужны некоторые исправления, и я уверен, что это было бы более кратким и элегантным, но мой девиз: "заставить его работать, сделать его красивым, сделать его быстрым": -)
В routes.rb
controller :cars do
match 'cars', :to => "cars#index"
match 'cars/:brand', :to => "cars#list_brand", :as => :brand
match 'cars/:brand/:model', :to => "cars#list_model_name", :as => :model_name
match 'cars/:brand/:model/:variant', :to => "cars#list_variant", :as => :variant
end
В модели автомобиля
def to_param
"#{brand}/#{model_name}/#{variant}"
end
И, очевидно, хрупкий и не суточный, в cars_controller.rb
def index
@cars = Car.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @cars }
end
end
def list_brand
@cars = Car.where("brand = ?", params[:brand])
respond_to do |format|
format.html { render :index }
end
end
def list_model_name
@cars = Car.where("brand = ? and model_name = ?", params[:brand], params[:model])
respond_to do |format|
format.html { render :index }
end
end
def list_variant
@cars = Car.where("brand = ? and model_name = ? and variant = ?", params[:brand], params[:model], params[:variant])
respond_to do |format|
format.html { render :index }
end
end
Ответ 3
Вам просто нужно создать два маршрута, один для распознавания, один для поколения.
Обновлено: используйте соответствующие маршруты.
# config/routes.rb
# this one is used for path generation
resources :cars, :only => [:index, :show] do
member do
get 'favourize'
get 'unfavourize'
end
end
# this one is used for path recognition
scope '/cars/:brand/:model_name/:variant' do
match ':id(/:action)' => 'cars#show', :via => :get
end
И настройте to_param
# app/models/car.rb
require 'cgi'
class Car < ActiveRecord::Base
def to_param
parts = [brand,
model_name,
variant.downcase.gsub(/[^[:alnum:]]/,'-').gsub(/-{2,}/,'-'),
id]
parts.collect {|p| p.present? ? CGI.escape(p.to_s) : '-'}.join('/')
end
end
Пример помощников пути:
link_to 'Show', car_path(@car)
link_to 'Edit', edit_car_path(@car)
link_to 'Favourize', favourize_car_path(@car)
link_to 'Unfavourize', unfavourize_car_path(@car)
link_to 'Cars', cars_path
form_for(@car) # if resources :cars is not
# restricted to :index and :show
Ответ 4
Вы хотите, чтобы ограниченные параметры передавались в URL-адрес, некоторые параметры которого являются необязательными, а некоторые из них строго должны присутствовать.
Направляющие Rails показывают, что у вас могут быть строгие, а также необязательные параметры, а также вы можете указать имя определенного маршрута для упрощения его использования.
Руководство по маршрутизации рельсов
связанные параметры
Пример использования -
В приведенном ниже маршруте
-
brand
optional parameter
, поскольку его окружение circular bracket
-
Также обратите внимание, что внутри маршрута могут быть необязательные параметры, но им необходимо добавить наконец /cars(/:brand)(/:make)(/:model)
match '/cars/(:brand)', :to => 'cars#index', :as => cars
здесь cars_url
будет отображаться индексное действие контроллера автомобилей.
снова cars_url("Totoya")
будет маршрутизировать действие индекса контроллера автомобилей вместе с params[:brand]
как Toyota
Показать URL-адрес маршрута может быть следующим, где id является обязательным, а другие могут быть необязательными
match '/cars/:id(/:brand(/:model_name/)(/:variant)', :to => "cars#show", :as => car
В приведенном выше случае id
является обязательным. Другие параметры являются необязательными.
поэтому вы можете получить к нему доступ, как car_url(car.id)
или car_url(12, 'toyota')
или car_url(12, 'toyota', 'fortuner')
или car_url(12, 'toyota', 'fortuner', 'something else)