Question

I'm using the Friendly_Id 5 gem in my Rails 4 app to make the URL's "friendlier." I can currently generate URLs like /article/my-article-title. On many blog's today, you also see the date or the id in the path. How would I generate URLs like:

 /articles/23/my-article-title 

where 23 is the article id. It seems like the slug would actually have to be 23/my-article-title since slugs have to be unique in the DB. Or how would I generate a date based slug like?

 /articles/2014/01/22/my-article-title 
Was it helpful?

Solution 2

I wouldn't put date of the article in the URL, as some people are thrown off by content that isn't created, like, within a week from now, so that might result in a little less traffic for you.

However, I would suggest to put ID next to the title. I'm not sure how to do this with friendly_id, but in my opinion there's a better way. I myself switched from friendly_id slugs to this method.

Simply define to_param in your model:

def to_param
   "#{id}-#{title.parameterize}"
end

Rails will be automatically able to find the id using Model.find(params[:id])

OTHER TIPS

I ended up doing the following.

In my routes.rb file:

resources :articles, except: [:index, :show]
constraints section: /section|anothersection|somethingelse/ do
  constraints year: /\d{4}/ do
    constraints month: /\d{1,2}/ do
      resources :articles, only: :show, path: '/:section/:year/:month', as: :my_articles
      get ':section/:year/:month', to: 'articles#by_month', as: :month
    end
    get ':section/:year', to: 'articles#by_year', as: :year
  end
  get ':section', to: 'articles#by_section', as: :section
end

Someone suggested I do this differently, but I ended up just using my version anyway since this allows me to add constraints directly to the routes. It's also visually clearer to see what my intentions are with this routes nesting.

In my article.rb model file I have something like:

class Article < ActiveRecord::Base
    def slugify
        to_remove = %w(a an as at before but by for from is in into like of off on onto per since than the this that to up via with)
        title.downcase.split.reject {|word| to_remove.include?(word)}.collect {|word| word.gsub(/[:,\-\.’']/,'')}.join(' ').parameterize
    end

    def to_param
        slug
    end
end

I made the slugify method, which behaves like Drupal's default slug generator. This is what gets saved to the database inside the slug column. The to_param method then calls this field to generate the article slug.

As for my database schema.rb file, the important part literally boils down to creating the slug column and indexing it:

create_table "articles", force: true do |t|
  t.string   "title"
  t.text     "body"
  t.datetime "created_at"
  t.datetime "updated_at"
  t.integer  "user_id"
  t.string   "section"
  t.string   "slug"
end

add_index "articles", ["slug"], name: "index_articles_on_slug", unique: true

And my controller looks like:

class ArticlesController < ApplicationController
  before_action :set_section, only: [:by_month, :by_year, :by_section]

  #I'm using the by_star gem to make the by_year and by_month part easy!
  def by_month
    @articles = Article.by_month(params[:month]).by_year(params[:year]).by_section(@section)
  end

  def by_year
    @articles = Article.by_year(params[:year]).by_section(@section)
  end

  def by_section
    @articles = Article.by_section(@section)
  end

  private
  def set_section
    @section = params[:section]
  end
end

The result

Doing as I've explained avoids having to see unnecessary numbers in the slug. It also makes it possible to access articles by section, by year, by month and, obviously, show the article itself. You may want to handle everything via a single route as explained in the link, but that's up to you.

Hope this helps!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top