RailsでURLに文字列を含めて日本語もオッケーでSEO的にもいい感じにする方法を考えてみた。

SEO的にはURLにキーワードを含めるのはいいことだと言うじゃないですか。  
>ドルフィン・シーエムエス「[URLに含めるキーワードの最適なSEOとは](http://www.dolphin-cms.net/blog/url-include-keyword)」

Tumblrのブログとかでも記事のタイトルをURLに入れてくれるし、これを絶賛運営中の「[THE TOURNAMENT](http://the-tournament.jp/about)」でもやりたい。
もちろん日本語でもオッケーな感じで。
  
というわけでいろいろやってみた結果のメモです。
近年まれに見る長期戦になってつかれた。。


# 基本方針
というわけで調べてみたけど、どうやらやり方もいくつかあったりするみたいなので
自分の状況とあわせてどんな感じにするか考えてみた。

## どんなURLにするか
今回やりたいのは、tournamentモデルのshow画面で、リソースの名前をURLに入れること。

やり方として考えられるのは、

    #リソース名がhogehogeの場合
    (0) http://the-tournament.jp/tournaments/58	 #=>デフォルト(ID)
    (1) http://the-tournament.jp/tournaments/58-hogehoge  #=>リソース名とIDを連結して使用
    (2) http://the-tournament.jp/tournaments/hogehoge  #=>IDをリソース名で置き換え
    (3) http://the-tournament.jp/tournaments/58/hogehoge  #=>IDの下の階層でリソース名を表示

とりあえずこんなとこですかね。

###①リソース名とID連結パターン
これは最初選択肢になかったけど、調べてると一番お手軽に実装できる方法としてけっこう人気みたい。
>inBoundio「[SEO for Ruby on Rails (Complete Guide)](http://www.inboundio.com/blog/seo-for-ruby-on-rails-complete-guide/)」

なんとmodelのメソッドをちょっとoverrideするだけで実装が完了してしまいます。

```ruby
# models/tournament.rb
class Tournament < ActiveRecord::Base
  def to_param
    "#{id}-#{title}".parameterize
  end
end
```

これだけ。すでに設置してるlink_toとかのリンクも勝手に置き換わって、かつこの連結したIDでアクセスするとRailsが勝手に読み取って今まで通りのtournament#showを表示してくれます。

簡単で副作用がないのがいいですね。
IDをつけるから名前を一意にする必要がないのも便利です。


###②IDをリソース名で置き換えパターン
次はちょっとがんばるときの王道。リソース名を完全にIDの代わりに使っちゃいます。
friendly_idって専用のgemを使うのがいいらしい。

>TreeHouseBlog「[Creating Vanity URLs in Rails](http://blog.teamtreehouse.com/creating-vanity-urls-in-rails)」

リソース名を単体でキーに使ってるので、このパターンやと
名前の重複とか名前が変わった時とかの対応をどうするか問題が発生します。

このgemを使うとそのへんをうまいこと処理してくれるっぽい。
(変更の履歴をとっといてリダイレクトしてくれたりとか)


###③IDの下の階層でリソース名を表示パターン
調べててもほとんど情報がなかったけど、元々やりたかったのはこれです。  
tumblrとかstackoverflowの記事リンクでもこれになってる。

    #stackoverflow：記事IDのあとにタイトル
    http://stackoverflow.com/questions/9169101/friendly-id-generate-slug-with-id/
    
    #tumblr：同じく記事IDのあとにタイトル
    http://blog.notsobad.jp/post/87809261001/rails

大手も使ってる方法やのになんで全然情報がないんだろう。
調べ方が悪かっただけかな。。



## しかし日本語の扱いがネックでして
というわけで3つくらい方法が見つかったけど、日本語の扱いが大きなネックになって
結局当初の想定どおり「③IDの下の階層でリソース名を表示」パターンでやることにしました。

「①リソース名とID連結」パターンは初めて知って簡単でよかったんやけど、日本語がうまく扱えずしょんぼり。（だれか解決法知ってたら教えてください）

「②IDをリソース名で置き換え」パターンも同じく日本語が空文字になったり大変そうで、かつgemを使いこなして一意制御とかやり切るのも面倒そうなのでパス。

「③IDの下の階層でリソース名を表示」パターンは、結局IDで一意判別しててるのでリソース名はあくまでおまけのオプション。
なので日本語の扱いとかも雑でいい気がしてて、いいんじゃないかなと。


## あとnestするのにあんまり長いURLもいやでして
あと選択基準はもう１個あって、nested_resourceしたときにURLが長くなって気持ち悪い問題です。

tournamentリソースはplayersとgamesというリソースをnestしてて、

    http://the-tournament.jp/tournaments/58/players/  
    http://the-tournament.jp/tournaments/58/games/

というパスが用意されてます。

ここで断念した２つの方法やと、このID部分が長いのに置き換わるので
URLがちょっと長すぎて気持ち悪いかなーとか(主観)。

長すぎるというか、nestしてるいろんなリソースにアクセスするときに
いちいちリソース名つけてまわらんでも…って感覚が近いですかね。

今回採用した方式やと逆にnestと階層かぶっちゃうからそっちまで変えるの大変やし、あくまでtournament#showのときだけprettyなURL、nestしたリソースとかにアクセスする際は  
もともとのIDだけ使う方式でいってみようかと。


## 最終的にこんな感じにしてみようと思います
というわけで、最終的にこんな感じのroutingにするのがゴールです(長かった…)。

リソース名が「ほげほげ」だとすると、

    #「正」となるURLとリダイレクト設定
    http://the-tournament.jp/tournaments/58/ほげほげ    #=> これが「正」。tournament#show
    http://the-tournament.jp/tournaments/58    #=> 上記URLにリダイレクト
    http://the-tournament.jp/tournaments/58/ちがう名前    #=> 上記URLにリダイレクト

    # nestされたリソースとか
    http://the-tournament.jp/tournaments/58/players  #=> players#index
    http://the-tournament.jp/tournaments/58/games  #=>  games#index
    http://the-tournament.jp/tournaments/58/new  #=> tournament#new
    http://the-tournament.jp/tournaments/58/edit  #=> tournamnet#edit
  
ふう。

最後のほうに載せてるように、nestリソースとかデフォルトのactionとかとの  
判別をどうするか問題が出てきますね。



# 実装
それではいよいよ実装していきますか。  
しかし方針さえ決まってれば実装はそんなに難しくなかったりします。

## routingをいじる
まずはroutingの設定。これが今回の肝ですな。

>akkunchoi.github.com「[Rails3 routes.rb まとめ](http://akkunchoi.github.io/rails3-routes.html)」

久しぶりにroutingいじったので設定の方法をいろいろ参考にさせてもらいました。

```ruby
# config/routes.rb
resources :tournaments do
  resources :players
  resources :games
end
match 'tournaments/:id/(:title)', to: 'tournaments#show', via: :get, as: :pretty_tournament
```

これだけ。上のほうはもともと設定してるやつなので、追加は実質最後の１行だけです。

まず

```ruby
'tournaments/:id/(:title)'
```

の部分は、idのあとに何か入ってたらparams[:title]で受け取るって感じ。
かつ()で囲ってるので、別に指定なくてもいいんですけどねという謙虚な気持ちを表現しています。(もちろん実際そうなる仕様です)

で to: で今までどおり#showのページを表示するようにさせて、
as: でこのパスに名前をつけときました。ここは趣味です。なくても大丈夫。

設定はこれだけなんやけど注意するのはこの１行を記述する場所で、
必ずresources: tournamentsより後に書くようにします。

routingは上から優先して適用されるので、

    http://the-tournament.jp/tournaments/58/players

とかのURLでアクセスが来た際、上のresources内でこのアドレスがキャッチされて
今までどおりのページに処理が渡ります。

逆にしちゃうと、playersとかにアクセスしてもそれがリソース名として処理されちゃうので
tournamnets#showページに行ってしまいます。。

nestリソースだけじゃなくてnew, editとかのリンクもこれで問題なく処理されます。

この時点で手動でURL叩いてみて、ちゃんと
名前付きのURLが機能してるのを確認しておきましょう。


## 予約語を設定しとく
しかしこのままだとtournamentの名前に
「players」とかつけたらどうなるんだ問題が出てきます。

まぁどうなるかというと「players」大会のtournament#showに行こうとしても
players#index（プレイヤー一覧画面）に行ってしまって困る、という感じですね。

これを防ぐにはそもそもそんな名前で大会を登録させないようにするしかないんじゃないかと。
登録されるとつらい名前問題です。

>Qiita「[登録されるとつらいユーザー名リスト](http://qiita.com/phimcall/items/4c559b70f70ea7f1953b)」

一旦ここまで網羅的にやらんでも大丈夫かなということで、最低限の制御だけ追加してみました。

```ruby
validates :title, exclusion: {in: %w(index new edit players games)}
```

これでこんな名前で登録しようとすると、「予約されてます」ってエラーメッセージが出るように。

いいですねいいですね。


## viewでリンクを修正する
routingが設定できたら次は実際にリンクを表示してるところのパスを修正していきます。
まぁこのあと旧URLからのリダイレクトを設定するんですが、最初からちゃんと飛ばすほうがいいですよね。

さっきroutingでas: pretty_tournamentって名づけておいたので、

```ruby
# 旧URL
- link_to tournament_path(@tournament)
    
# 新URL
- link_to pretty_tournament_path(@tournament, @tournaemnt.title)
```

こんな感じでちゃんとリンクが作成されるはず。渡す引数が１個増えてるので注意です。


## prettyなURLにリダイレクト
長かったけどそろそろ仕上げ。最後に旧URLにアクセスが合った場合、もしくは
リソース名に変な値が入ってた場合に正しいprettyなURLにリダイレクトするようにします。

config/routes.rbで定義できたらいいんやけど、残念ながらその時点では
なんていう名前をURLに含めるかが判断できないので、controllerに処理を追加します。

```ruby
# controllers/tournamens_controller.rb
def show
  redirect_to pretty_tournament_path(@tournament, @tournament.title), status: 301 unless params[:title]==@tournament.title
  ...
end
```

URLでidのあとにつけてる名前がparams[:title]として取得できるので、
それを正しいリソース名と比較して違うときだけリダイレクト発動。

statusオプションをつけてちゃんと301でリダイレクト。

エンコードとかの関係で日本語の比較がうまくいかんかったら無限ループしそうなので、
エラーが続くようならparams[:title]が空かどうかだけの判定にしてもいいかもしれない。。



というわけでいろいろがんばってみたけど、日本語の扱いとかリダイレクトの設定とかほんとにこんな感じでいいのか色々不安です。

おかしいところとかあればご指摘ください。