34
votes

I have a controller named 'companies' and rather than the urls for each company being denoted with an :id I'd like to have the url use their :name such as: url/company/microsoft instead of url/company/3.

In my controller I assumed I would have

 def show
   @company = Company.find(params[:name])
 end

Since there won't be any other parameter in the url I was hoping rails would understand that :name referenced the :name column in my Company model. I assume the magic here would be in the route but am stuck at this point.

7
There is a gem frendly_id for thiszishe
you can use to_param method, read more about to_param method hererails_id
I suggest to dscher change good answer for this answer ; stackoverflow.com/questions/24226378/… can help other people ...Matrix

7 Answers

83
votes

Good answer with Rails 4.0+ :

resources :companies, param: :name

optionally you can use only: or except: list to specify routes

and if you want to construct a URL, you can override ActiveRecord::Base#to_param of a related model:

class Video < ApplicationRecord
  def to_param
    identifier
  end

  # or
  alias_method :to_param, :identifier
end

video = Video.find_by(identifier: "Roman-Holiday")
edit_videos_path(video) # => "/videos/Roman-Holiday"
16
votes

params

The bottom line is you're looking at the wrong solution - the params hash keys are rather irrelevant, you need to be able to use the data contained inside them more effectively.

Your routes will be constructed as:

#config/routes.rb
resources :controller #-> domain.com/controller/:id

This means if you request this route: domain.com/controller/your_resource, the params[:id] hash value will be your_resource (doesn't matter if it's called params[:name] or params[:id])

--

friendly_id

The reason you have several answers recommending friendly_id is because this overrides the find method of ActiveRecord, allowing you to use a slug in your query:

#app/models/model.rb
Class Model < ActiveRecord::Base
  extend FriendlyId
  friendly_id :name, use: [:slugged, :finders]
end

This allows you to do this:

#app/controllers/your_controller.rb
def show
    @model = Model.find params[:id] #-> this can be the "name" of your record, or "id"
end
12
votes

Honestly, I would just overwrite the to_param in the Model. This will allow company_path helpers to work correctly.

Note: I would create a separate slug column for complex name, but that's just me. This is the simple case.

class Company < ActiveRecord::Base
  def to_param
    name
  end
end

Then change my routes param for readability.

# The param option may only be in Rails 4+,
# if so just use params[:id] in the controller
resources :companies, param: :name

Finally in my Controller I need to look it up the right way.

class CompaniesController < ApplicationController
  def show
    # Rails 4.0+
    @company = Company.find_by(name: params[:name])
    # Rails < 4.0
    @company = Company.find_by_name(params[:name])
  end
end
7
votes

I recommend using the friendly_id for this purpose. Please be noted that there are differences between friendly_id 4 and 5. In friendly_id 4, you can use like this

 @company = Company.find(params[:id])

However, you won't be able to do that in friendly_id 5, you have to use:

 @company = Company.friendly.find(params[:id])

In case that you don't want to use the params[:id] but params[:name], you have to override the route in routes.rb. For example

 get '/companies/:name', to: "companies#show"

Hope these info would be helpful to you

5
votes

There's actually no magic to implement this, you have to either build it yourself by correctly implementing to_param at your model (not recommended) or using one of the gems available for this like:

I use friendly_id and it does the job nicely.

2
votes

Model.find(primary_key)
The default parameter here is primary_key id. If you want to use other columns, you should use Model.find_by_xxx so here it could be

def show
  @company = Company.find_by_name(params[:name])
end
1
votes

The :id parameter is whatever comes after the slash when the URL is requested, so a name attribute needs to be extracted from this by checking the :id parameter for non-numerical values with regular expressions and the match? method in the controller. If a non-numerical value is present, the instance can be assigned by the name attribute using the find_by_name() method that rails generated for the model (assuming that the model has an attribute called name)

That's how I figured out how to do it in my app with my Users resource. My users have a username attribute, and all I had to do was modify the UsersController to define my @user variable differently depending on the :id parameter:

private
  # allow routing by name in addition to id
  def get_user
    if params[:id].match?(/\A\d+\Z/)
      # if passed a number, use :id
      @user = User.find(params[:id])
    else
      # if passed a name, use :username
      @user = User.find_by_username(params[:id])
    end
  end

This gives me the option to use either id or username when I create a link to a particular user, or type it into the browser's address bar.

Then the only other (optional) thing to do is to change all the links in the views so that they point to the URL with the name instead of the URL with the id.

For example, within the link_to() method call in my navigation bar I changed

... user_path(current_user) ...

to

... user_path(current_user.username) ...

In your example, you might have a company view with the following link:

<%= link_to @company.name, company_path(@company.name) %>

Which, if the current company is Microsoft, would display "Microsoft" and link to "companies/Microsoft", even though the URL "companies/1" would still be valid and display the same thing as "companies/Microsoft"