Dynamic routes with Rails 3

2019-01-13 16:36发布

问题:

I have a task to develop a rails application following the model for routing.

I need to have PageController and Page model. Page urls must be like /contacts, /shipping, /some_page.

Also i need have CatalogController and Category model. Categories urls must be like /laptops, /smartphones/android.

And it will be ProductsController and Product model, urls of products must be line /laptops/toshiba_sattelite_l605, /smartphones/android/htc_magic

I understand that this problem can be solved by using URLs like

  • /page/shipping
  • /catalog/smartphones/android

But the customer does not want to see the insertion of "/page" or "/catalog" in the URL.

Please tell me the direction for solving this problem. Sorry for my bad English.

回答1:

You'll have to write a "catch-all" rule:

On routes.rb:

get '*my_precioussss' => 'sauron#one_action_to_rule_them_all'

Your precious controller:

class SauronController < ApplicationController
  def one_action_to_rule_them_all
    @element = find_by_slug(params[:my_precioussss])
    render @element.kind # product, category, etc
  end
end

Then you write one view for each "kind" of element: product.html.erb, category.html.erb, etc.

Make sure you write your find_by_slug implementation.

You can change one_action_to_rule_them_all to pikachu_i_choose_you and SauronController to PokemonController, will work too.



回答2:

You need to write a catch all

match '*page' => 'pages#show'

then handle the page path in your page controller

def show
  if params[:page] 
    @page = Page.find_by_page(params[:page])
  else
    @page = Page.find(params[:id])
  end
...

if you use Omniauth or another rails lib that uses it's own catch all you may need to exclude some urls from this, use a lambda in your constraint

match '*page' => 'pages#show', :constraints => lambda{|req|  !req.env["REQUEST_URI"].include? "auth"}

If you want to have nested paths (e.g. "smartphones/android") you'll need to split the :page param apart and handle the path segments in your controller

path = :page.split("/")  // you now have an array of path segments you can use in your controller


回答3:

I found this guide to rails 3 routing incredibly helpful:

http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/



回答4:

i have done it in combination of two approaches

  1. use friendly_id gem to operate with slugs in find requests instead of numeric ids

  2. define routes with :constraints=>ConditionClass which has to return true or false (so route will be determined or not)

small example:

#routes.rb
    class CategoryBrandGoodConstraint
      def self.matches?(request)
        path_parts = request.path.split('/')
        Category.find(path_parts[1]).present?&&Brand.find(path_parts[2]).present?&&Good.find(path_parts[3]).present? rescue false
      end
    end

    get ':category_friendly_id/:brand_friendly_id/:good_friendly_id' => 'goods#index', :constraints => CategoryBrandGoodConstraint
    #another conditional routes with another :constraints=>ConditionClass2 definition
    #...
    #until you define all necessary conditional application routes

#goods_controller.rb

    def index

      #operate with params[:category_friendly_id], params[:brand_friendly_id], params[:good_friendly_id] to collect all you need for view rendering

    end

of course, you can route to another controller to render another view get '/:whatever' => 'another_controller#another_action', :constraints=>ConditionClassN

hope it helps!

cheers



回答5:

Rails routes are based solely on URL. If you don't have /pages or /categories or whatever before the name, then how is Rails going to tell if a URL like http://example.com/smartphones is a page URL or category URL? Unless you have some predefined list of pages names in the routes conditions, it cannot.

The only way to do that in this example would be to actually search for a page by smartphones and if none is found assume that this is a category name and search for a category. This can't be done in rails routes. To do that, you'd need a single controller that would handle all those URLs and do that search and then render a proper template (you can't redirect, because that would change the url). And that is an ugly way to solve this problem.

If I were you I'd try to convince the customer to agree on some kind of prefix (like /p_pagename) or preferably different URLs. It could be something like /p/shipping for pages and all other URLs could be routed to CategoriesController or ProductsController.