13

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.

Michael Berkowski
  • 267,341
  • 46
  • 444
  • 390
Beer Brother
  • 514
  • 2
  • 6
  • 15
  • 3
    How will you distinguish if `/something` is a page or a category? – Matt Nov 16 '10 at 15:45
  • It may be category(/laptops) or it may be page (/contacts). Of cause i understand that Category and Page models need to have some field, say "slug", and it must be unique along the site. – Beer Brother Nov 16 '10 at 16:00
  • 1
    This is a really bad idea. If they're worried about the url's being "ugly", then they can do something like `/faq/contacts` or `/questions/shipping`. – jonnii Nov 16 '10 at 17:12
  • Checkout the official Rails routing guide @ http://guides.rubyonrails.org/routing.html – raidfive Nov 17 '10 at 04:38
  • Did you find a Solution on this I'm also interrested... but google didn't help me :( I use friendly_id that has generated a table slugs with the slug name, it's id and the controler name(sluggable_type) fields... but no documentation shows how to use that for routing purpose. Any help would be welcome. Regards. –  Feb 03 '12 at 12:24
  • I used the solution described in the first answer. Unfortunately, I do not know any other way to solve this problem. – Beer Brother Feb 11 '12 at 23:47

5 Answers5

49

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.

Fábio Batista
  • 25,002
  • 3
  • 56
  • 68
4

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
David Burrows
  • 5,217
  • 3
  • 30
  • 34
1

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

okliv
  • 3,909
  • 30
  • 47
1

I found this guide to rails 3 routing incredibly helpful:

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

Michael Durrant
  • 93,410
  • 97
  • 333
  • 497
0

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.

Matt
  • 5,328
  • 2
  • 30
  • 43
  • 1
    I agree with your position, but it absolutely impossible to convince the customer to agree on some kind of prefix in URLs. His SEO needs such URLs. So i need to find solution. – Beer Brother Nov 17 '10 at 09:35