Динамический URL → Отображение контроллера для маршрутов в Rails

Я хотел бы иметь возможность сопоставлять URL-адреса контроллерам динамически на основе информации в моей базе данных.

Я хочу сделать что-то функционально эквивалентное этому (предполагая модель View):

map.route '/:view_name',
    :controller => lambda { View.find_by_name(params[:view_name]).controller }

Другие предложили динамически перестроить маршруты, но это не сработает для меня, так как там могут быть тысячи просмотров, которые сопоставляются с тем же контроллером

Ответы

Ответ 1

Этот вопрос старый, но я нашел его интересным. Полностью работающее решение может быть создано в Rails 3 с использованием возможности маршрутизатора для маршрутизации на конечную точку Rack.

Создайте следующий класс Rack:

    class MyRouter
      def call(env)
        # Matched from routes, you can access all matched parameters
        view_name= env['action_dispatch.request.path_parameters'][:view_name]

        # Compute these the way you like, possibly using view_name
        controller= 'post' 
        my_action= 'show'

        controller_class= (controller + '_controller').camelize.constantize
        controller_class.action(my_action.to_sym).call(env)
      end
    end

В маршрутах

    match '/:view_name', :to => MyRouter.new, :via => :get

Подсказка взята из http://guides.rubyonrails.org/routing.html#routing-to-rack-applications, в которой говорится: "Для любопытных" posts # index "фактически расширяется до PostsController.action(: index), который возвращает действительное приложение Rack."

Вариант, проверенный в Rails 3.2.13.

Ответ 2

Итак, я думаю, что вы просите, чтобы у вас есть таблица Views и модель View для нее, где таблица выглядит как

id | name | model
===================
1  | aaa  | Post
2  | bbb  | Post
3  | ccc  | Comment

Вы хотите, чтобы URL-адрес/aaa указывал на Post.controller - это правильно?

Если нет, то то, что вы предлагаете, кажется хорошим, если оно работает.

Вы можете отправить его на все действия и посмотреть, как действие будет выглядеть по URL-адресу, запустите find_by_name, а затем вызовите правильный контроллер.

def catch_all
  View.find_by_name('aaa').controller.action
end

Update

Вы можете использовать redirect_to и даже отправлять параметры. В приведенном ниже примере вы отправляем параметры поиска

def catch_all
  new_controller = View.find_by_name('aaa').controller
  redirect_to :controller => new_controller, :action => :index, 
      :search => params[:search] 
end

Ответ 3

Вот хорошее решение Rack Routing для SEO, предоставленное zetetic и Steve ross

Тестирование Rack Routing с использованием rSpec

Он показывает вам, как писать настраиваемый диспетчер (где вы можете выполнить поиск в db, если необходимо), а также с ограничениями и тестированием.

Ответ 4

Как было предложено в вопросе Rails маршрутизации для обработки нескольких доменов в одном приложении, я думаю, вы могли бы использовать Rails Routing - Advanced Constraints, чтобы построить то, что вам нужно.

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

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

match "*path" => "post#show", :constraints => PostConstraint.new
match "*path" => "comment#show", :constraints => CommentConstraint.new

Затем создайте файл lib/post_constraint.rb:

class PostConstraint     
  def matches?(request)
    'post' == Rails.cache.fetch("/view_controller_map/#{request.params[:view_name]}") { View.find_by_name(request.params[:view_name]).controller }
  end
end

Наконец, создайте lib/comment_constraint.rb:

class CommentConstraint     
  def matches?(request)
    'comment' == Rails.cache.fetch("/view_controller_map/#{request.params[:view_name]}") { View.find_by_name(request.params[:view_name]).controller }
  end
end

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