to_key と to_param で URL や HTML 中のリソースの id を別の文字列にしてしまう

More than 3 years have passed since last update.

ActiveModel のメソッドを使って、


  • URL に含まれるモデルの id を別の文字列にしたい

  • 作成済みのリソースの数がばれないように、HTML 中に生の id を表示したくない

みたいなときに何とかする方法の紹介。

今回は id の代わりに、id を Base64 エンコードしたものを URL や HTML 中に含めるようにしてみます。


準備

まずは適当なリソースを scaffold で作成。

bin/rails g scaffold posts title:string body:text

リソースを create した後で /posts/1/edit に移動し、HTML のソースを見るとこんな感じ。


to_key

ここで Post の to_key を上書きする。

to_key はリソースを一意に特定するキーを含んだ Array を返す。そいつが dom_id によって利用される。dom_id ってのによって、fomr_for などで埋め込まれる id が変えられている、らしい。。。

今回は to_key で Base64 エンコードした id を含む配列を返す。ちなみに ActiveRecord のデフォルトでは、to_key はリソースの id を含む Array を返す。


app/models/post.rb

class Post < ActiveRecord::Base

def to_key
[Base64.encode64(id.to_s)]
end
end

こうすると to_key の返り値が以下のように変わる。

# これが

Post.first.to_key
=> [1]

# こうなる
Post.first.to_key
=> ["MQ==\n"]

form の id に埋め込まれる Post の id も変わった!


to_param

しかしパスに含まれる id はまだ生の id のまま。こちらは to_param で変更することが可能。

to_param はリソースのユニークな URL を発行するために使われる。post_path(@post) とやると @post に対して to_param が呼び出されて、その結果が URL に含まれる。ちなみに ActiveRecord の場合、デフォルトでは id が返ってくる。

ということで以下のように変更。

class Post < ActiveRecord::Base

def to_key
[Base64.encode64(id.to_s)]
end

def to_param
Base64.encode64(id.to_s)
end
end

するとこんな感じになる。

# これが

app.edit_post_path(Post.first)
=> "/posts/1/edit"

# こうなる
app.edit_post_path(Post.first)
=> "/posts/MQ==%0A/edit"

URL と HTML のソースに含まれるパスに含まれる id もエンコードされた!


controller の更新

ただ、このままだと Update Post ボタンをおした時にうまく動かない。

エラー画面を見れば分かる通り、Post.find(params[:id]) しても params[:id] が 1 でなく MQ== なので当たり前。

find に渡す id を先に decode してやるとうまく動く。


app/controllers/posts_controller.rb

def set_post

@post = Post.find(Base64.decode64(params[:id]))
end

Post.find(params[:id])Post.find(Base64.decode64(params[:id])) としてやることで動くようになる。


params[:id] の :id も変える

ただ、params[:id] に入ってくるのが id をエンコードしたもの、というのが気持ち悪い。params[:encoded_id] のように params のキーを変えたい。そのため、config/routes.rb で resources の params オプションを使う。


config/routes.rb

Rails.application.routes.draw do

resources :posts, param :encoded_id
end

controller 側も忘れずに書き換え。


app/controllers/posts_controller.rb

def set_post

@post = Post.find(Base64.decode64(params[:encoded_id]))
end

はい、params[:encoded_id] に値が入るようになりました。(ちなみに初めての web console)


補足

実際には Base64 エンコードしただけだとあんまり意味ないかもしれませんが、暗号化するなり、ユーザやタグなどの id の代わりに名前を使って見るなりやると良いんじゃないでしょうか。

なお、to_key も to_param も ActiveModel のメソッドなので、ActiveRecord でなくても使えます。

dom_id なんかは、自分でリソースの id を HTML に埋め込むときにも使えそうですね。