Rails routing with slugs
Rails's default routing scheme is alright. /posts/1, /posts/2, etc. But it's not very good for SEO and it's doesn't look great to users, either. Let's change Rails's config to use a slug that we can define ourselves.Obviously this can be used for any model. We're just using posts here because it's a common use-case for Rails. I mean just look at this site.Add the slug column to our tableFirst we need to generate the migration to add the column to the table, which Rails makes very easy:$ rails g migration add_slug_to_posts slug:stringDon't migrate it just yet, though. We'll be setting Rails up to search for posts by slug (rather than by id), so we'll also want to add an index to the slug column. For performance.class AddSlugToPosts < ActiveRecord::Migration[5.0] def change add_column :posts, :slug, :string add_index :posts, :slug, unique: true end end Now it's time to migrate.$ rails db:migrateAdd slugs to existing posts (optional)It may happen that you've already got posts on your site, all of which now have a nil slug field. Which will create problems. Let's fire up the rails console and add some post hoc slugs.$ rails c Once in the console, we'll get all of your posts, iterate over them, and update the slug. In this case we're basing the post's slug off its title (since it presumably has a title).We're using Rails's parameterize function, which removes non-url-safe characters and adds hyphens where spaces exist. Viz. 'Sample Post No. 1' will become 'sample-post-no-1'Post.find_each do |post| post.update_attributes(slug: post.title.parameterize) end You can swap out the hyphen for something else by passing a hash with the separator key to parameterize. For example: 'Sample Post No. 1'.parameterize(separator: '_') > => "sample_post_no_1"Update the routesWe have to make sure to let Rails know that our routes are going to use the slug rather than the id. Rails, of course, has a simple way of dealing with this:# routes.rb resources :posts, param: :slug Update your modelThe model also has to know that we're using a slug instead of id as well. It's a good place to put a couple of methods for making sure that our slugs are all going to the right place in the right shape.We'll add some validation to make sure that our slugs are unique—no sense in having two posts with the same URL.We're also going to do a little bit of Ruby's famous monkeypatching to change the built-in to_parammethod to use our slug when building paths, rather than the default id.And finally, we're going to monkeypatch the find method to find by slug instead of by id. That way we can run calls like Post.find('sample-post-no-1').# post.rb validates :slug, uniqueness: true, presence: true def to_param slug end def self.find(input) find_by_slug(input) end Update your controllerNow that our find method is finding by slug, our controller is going to run into a problem. You're probably finding by params[:id], which introduces two problems.find is expecting a slug, so it's going to choke unless your slug is 1 or 55 or something, but—You're no longer passing in a params[:id], since in our routes.rb we configured the route to use slug as the param. So params[:id] will always be nil.The solution: just change the params[:id] to params[:slug]. So:
Discussion in the ATmosphere