読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

Railsでfriendly_idを使って検索エンジンにわかりやすいURLを作成する

SEO Rails中級 Rails Model

f:id:nipe880324:20150221111817j:plain:w480
Photo by Flickr: marcosHB's Photostream

friendly_idは次のようにURLを人間や検索エンジンにわかりやすいようにするgemです。

# firendly_idを使っていない場合
http://example.com/states/4323454

# firendly_idを使った場合
http://example.com/states/washington

また、URL文字列(washingtonの部分)の履歴や他言語対応などの機能もあります。



動作確認

  • Rails 4.2.0
  • Ruby 2.1.0
  • friendly_id 5.1.0

目次

  1. friendly_idのインストール
  2. friendly_idの基本的な使い方
  3. slugを追加する
  4. 404エラーを避けるためにslugの履歴を取る


1. friendly_idのインストール

Gemfileに追加します。

# Gemfile
gem 'friendly_id'

Bundlerを実行します。

bundle install


2. friendly_idの基本的な使い方

FrendlyIdの設定ファイルとfriendly_id_slugsテーブルを作成します。

bin/rails g friendly_id
  create  db/migrate/20150220082838_create_friendly_id_slugs.rb
  create  config/initializers/friendly_id.rb

bin/rake db:migrate

friendly_id_slugsテーブルはslugカラム(URL上のIDの代わりに表示する文字列を保持する)のバージョニングを行うテーブルです。slugの履歴を取らない場合は必要ありません。


friendly_idを使うためにデモとしてUserをScaffoldで作成します。

bin/rails g scaffold User name:string:uniq
bin/rake db:migrate


作成されたUserモデルにFriendlyIDを追加します。

# app/models/user.rb
class User < ActiveRecord::Base
  validates :name, presence: true, uniqueness: true

  include FriendlyId # extend FriendlyId でもよい(違いはない)
  friendly_id :name  # idカラムの代わりにnameカラムをURLのIDとする
end


こうすることで、Userモデルの検索や作成時などに、idパラメーターに「name属性の値」が設定されてサーバー側に送られてきます。

{ "controller"=>"users", "action"=>"show", "id"=>"tom-tick" }


そのため、Usersコントローラーのユーザーを取得箇所では、friendlyメソッドを使いUserを取得できるようにします。

# app/controllers/users_controller.rb
def set_user
  # @user = User.find(params[:id])
  @user = User.friendly.find(params[:id])
end


ちなみにfriendlyメソッドは次のように動作します。

User.friendly.find('tom-tick') #=> friendly_idで設定したカラムで検索できる(ここではnameカラム)
User.friendly.find(3)          #=> IDで検索ができる
User.find(23)                  #=> friendlyメソッドを使わなくてもIDで検索できる
User.find('tom-tick')          #=> X friendlyメソッドを使わないとnameカラムで検索できない


では、画面で確認してみましょう。
IDの代わりに、Userモデルのnameカラムの値がURLとして使われています。
f:id:nipe880324:20150221120546j:plain:w320

日本語も作ることができます。
f:id:nipe880324:20150221120552j:plain:w320




3. slugを追加する

現在はUserモデルのnameカラムをURLとして表示していますが、ユーザー名にスペースが入ったりするとURLが見づらいものになるのでslugカラム(URL上のIDの代わりに表示する文字列)を追加します。

bin/rails g migration add_slug_to_users slug:index:uniq
  invoke  active_record
  create    db/migrate/20150220083718_add_slug_to_users.rb

bin/rake db:migrate


slugカラムを使うようにするために、Userモデルにuse: :sluggedを追加します。

# app/models/user.rb
friendly_id :name, use: :slugged


既に存在するUserレコードのslugカラムはnilなので値を設定します。
保存をするとnilの場合、FriendlyIdが自動的にslugを生成してくれます。

bin/rails c

# slugを確認する
User.all.pluck(:id, :name, :slug)
#=> [[1, "Tom Tick", nil], [2, "田中 太郎", nil]]

# FriendlyIdが自動的にslugを作成し、保存する
User.find_each(&:save)

# slugが生成されたことを確認する
User.all.pluck(:id, :name, :slug)
=> [[1, "Tom Tick", "tom-tick"], [2, "田中 太郎", "fcf1c7ee-6d17-4b43-a67a-b6d67adfdb48"]]


上記で見てわかると思うのですが、日本語は適切に入らないので、FriendlyIdのnormalize_frindly_idメソッドをオーバーライドします。
日本語の場合、parameterizeメソッドは空文字を返すので次のようにしています。

# app/models/user.rb
def normalize_friendly_id(value)
  value.to_s.parameterize.present? ? value.to_s.parameterize : value
end


では、うまく入らなかったUserレコードのslugを更新します。

bin/rails c

japanese_user = User.last
japanese_user.slug = nil
japanese_user.save


では、画面を確認してみましょう。
英語の場合は、大文字は小文字になり、スペースはハイフンになっています。
f:id:nipe880324:20150221121203j:plain:w320

残念ながら日本語などの場合、そのままの値をslugに設定しているのでslugを導入する前とURLは変わりません。
f:id:nipe880324:20150221120552j:plain:w320



上記で、slugを変更するために、nilを設定て、保存することで slugの変更を行いました。
slugを変更したいと思うたびに、nilを設定するのはめんどうなので、slugを更新するかどうかを確認するshould_generate_new_friendly_id?メソッドをオーバーライドします。
nameカラムをfriendly_Idとして利用しているので、name属性が変更された場合にtrueを返す(slugを変更する)ようにします。

# app/models/user.rb
def should_generate_new_friendly_id?
  name_changed?
end


では、名前を更新してみて、URLが変更されることを確認します。
URLが「tom-tick」の名前を更新すると、
f:id:nipe880324:20150221121548j:plain:w320

URLが「tom-tick2」に更新されてました。
f:id:nipe880324:20150221121601j:plain:w320




4. 404エラーを避けるためにslugの履歴を取る

上記で、name属性を変更することで、URLを変更することができました。
しかし、もし前のURL(例:/users/tom-tick)でアクセスした場合、404エラー Not Found (内部的にはActiveRecord::RecordNotFound Exception)が発生してしまいます。
そのため、以前のslugも保持するようにして、前のURLでアクセスされたときもページを表示できるようにします。

FriendlyIdでは次のようにhistoryを追加するだけです。

# app/models/user.rb
friendly_id :name, use: [:slugged, :history]

こうすることで、本記事の最初で作成したfriendly_id_slugsテーブルに自動的にslugの履歴が保持されることで、前のURLからでも同じUserモデルにアクセスできるようになります。


slugを変更しても、前のslugでもページにアクセスできるか確認します。
まず、名前を「Tom Tick2」から「Tom Tick3」に変更します。
f:id:nipe880324:20150221122447j:plain:w320

すると、slugは「tom-tick3」に変更されます。
f:id:nipe880324:20150221122601j:plain:w320

slugが変更された後でも、「/users/tom-tick2」でもアクセスできます。
f:id:nipe880324:20150221122613j:plain:w320



現在のslugにリダイレクトさせるようにするために、ステータスコード 301 Moved Permanently(恒久的に移動した)を返しリダイレクトするようにします。

# app/controllers/users_controller.rb
def show
  if request.path != user_path(@user)
    return redirect_to @user, :status => :moved_permanently
  end
end


friendly_id_slugsテーブルはslugの変更に応じて増えていくので、適切にデータの削除が必要です。削除することでアクセスができなくなるので、タイミングは難しいところです。


以上です。