【Rails】primary_key(主キー)をランダムな文字列にする
はじめに
お疲れ様です。
おおくまです。
今回は、「【Rails】primary_key(主キー)をランダムな文字列にする」ということで、Ruby on Railsにおいて、primary_keyをランダムな文字列にする方法についてまとめてみました。
少しでも皆様の参考になりますと幸いです。
対象読者
注意点
環境
primary_key(主キー)とは
primary_keyとは、データベースのテーブルにおいて、各レコードを一意に識別するためのキーのことです。
そのため、primary_keyは、重複する値を持つことができません。
Ruby on Railsでは、特に指定しない場合、idカラムがprimary_keyとして設定され、1、2、3、4、5、・・・というように、順にidが振られていきます。
なぜprimary_key(主キー)をランダムな文字列にするのか
Ruby on Railsでは、/posts/:id/edit
のようなURLを生成する際に、idカラムの値を使用します。
先ほども述べた通り、idカラムは通常、1、2、3、4、5、・・・というように、順にidが振られていきますので、パスが推測されやすくなります。
def edit
@post = current_user.posts.find(params[:id])
end
def edit
@post = Post.find(params[:id])
end
1つ目の例のように、current_user起点で、Postテーブルからレコードを取得する場合は、アドレスバーのidの値を打ち変えたとしても、他のユーザーの投稿を編集することはできません。
しかし、万が一、2つ目の例のように、Postテーブルから直接レコードを取得するようにコードを書いてしまうと、アドレスバーのidの値を打ち変えることで、他のユーザーの投稿を編集することができてしまいます。
開発を進める中で、アドレスバーのidを打ち変えたとしても、他のユーザーの投稿を編集することができないようにするために、1つ目の例のような対策をすることも重要ですが、そもそも推測されにくいようなパスを生成することも重要です。
そこで、primary_keyをランダムな文字列にすることで、パスを推測されにくくすることができます。
primary_key(主キー)をランダムな文字列にする方法
環境構築が済んでいる段階からスタートします。
ランダムな文字列を生成するモジュールを定義する
まずは、app/models/concerns
ディレクトリにid_generate_module.rb
というランダムな文字列を生成するためのモジュールを作成します。
module IdGenerateModule
extend ActiveSupport::Concern
included do
before_create :generate, if: -> { has_attribute?(:id) }
end
def generate
self.id ||= loop do
id = SecureRandom.hex(5)
break id unless self.class.exists?(id: id)
end
end
end
id = SecureRandom.hex(5)
の部分で、ランダムな文字列を生成しています。
括弧内の数字を変更することで、生成される文字列の長さを変更することができます。
今回は、10文字のランダムな文字列を生成するようにしています。
次に、app/models/application_record.rb
にIdGenerateModule
をインクルードします。
class ApplicationRecord < ActiveRecord::Base
+ include IdGenerateModule
primary_abstract_class
end
これで、primary_keyをランダムな文字列にする準備が整いました。
Postテーブルを作成する
では、実際にテーブルを作成してみます。
docker compose run app rails g scaffold Post title:string content:text
次に、マイグレーションファイルを編集します。
class CreatePosts < ActiveRecord::Migration[7.0]
def change
+ create_table :posts, id: :string, limit: 10 do |t|
- create_table :posts do |t|
t.string :title
t.text :content
t.timestamps
end
end
end
このように編集することで、Postテーブルのidカラムの型をstringに変更することができます。
次に、マイグレーションを実行します。
docker compose run app rails db:migrate
db/schema.rb
を確認してみると、Postテーブルのidカラムの型がstringになっています。
ActiveRecord::Schema[7.0].define(version: 2024_09_14_035018) do
create_table "posts", id: { type: :string, limit: 10 }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "title"
t.text "content"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
Postテーブルにレコードを作成する
では、実際にレコードを作成してみます。
このようにルーティングを定義し、 http://localhost:3000/ にアクセスします。
Rails.application.routes.draw do
root 'posts#index'
resources :posts
end
ここから、New Postをクリックし、新規投稿を作成します。
無事にレコードが作成されました。
では、コンソールでidを確認してみます。
docker compose run app rails c
irb(main):001> Post.first
Post Load (0.7ms) SELECT `posts`.* FROM `posts` ORDER BY `posts`.`id` ASC LIMIT 1
=>
#<Post:0x0000ffff66c0d7b0
id: "6494ac5f80",
title: "タイトル",
content: "本文",
created_at: Sat, 14 Sep 2024 05:01:39.153767000 UTC +00:00,
updated_at: Sat, 14 Sep 2024 05:01:39.153767000 UTC +00:00>
無事にidがランダムな文字列になっていることが確認できました。
また、詳細ページ、編集ページのURLも、idの部分が同じようにランダムな文字列になっていました。
foreign_key(外部キー)をランダムな文字列にする
次に、Postテーブルと1対多の関係にあるCommentテーブルにも同様の設定を適用してみます。
ここでのポイントは、Commentテーブルのforeign_keyであるpost_idカラムの型をstringに変更することです。
Commentテーブルを作成する
まずは、Commentテーブルを作成します。
docker compose run app rails g scaffold Comment post:references content:text
次に、マイグレーションファイルを編集します。
class CreateComments < ActiveRecord::Migration[7.0]
def change
+ create_table :comments, id: :string, limit: 10 do |t|
- create_table :comments do |t|
+ t.references :post, type: :string, null: false, foreign_key: true
- t.references :post, null: false, foreign_key: true
t.text :content
t.timestamps
end
end
end
このように編集することで、Commentテーブルのidカラム、post_idカラムの型をstringに変更することができます。
次に、マイグレーションを実行します。
docker compose run app rails db:migrate
db/schema.rb
を確認してみると、Commentテーブルのidカラム、post_idカラムの型がstringになっています。
ActiveRecord::Schema[7.0].define(version: 2024_09_14_052451) do
create_table "comments", id: { type: :string, limit: 10 }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "post_id", null: false
t.text "content"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["post_id"], name: "index_comments_on_post_id"
end
create_table "posts", id: { type: :string, limit: 10 }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "title"
t.text "content"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_foreign_key "comments", "posts"
end
Commentテーブルにレコードを作成する
では、実際にレコードを作成してみます。
このようにルーティングを定義します。
Rails.application.routes.draw do
root 'posts#index'
resources :posts do
resources :comments, only: [:create]
end
end
次に、app/models/post.rb
を編集します。
class Post < ApplicationRecord
has_many :comments, dependent: :destroy
end
次にapp/controllers/comments_controller.rb
を編集します。
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
if @comment.save
redirect_to @post, notice: 'Comment was successfully created.'
else
render :new
end
end
private
def set_post
@post = Post.find(params[:post_id])
end
def comment_params
params.require(:comment).permit(:content)
end
end
最後に、app/views/posts/show.html.erb
を編集します。
<p style="color: green"><%= notice %></p>
<%= render @post %>
<div>
<%= link_to "Edit this post", edit_post_path(@post) %> |
<%= link_to "Back to posts", posts_path %>
<%= button_to "Destroy this post", @post, method: :delete %>
</div>
<hr>
<p>Comments</p>
<% @post.comments.each do |comment| %>
<div>
<strong>Comment:</strong>
<p><%= comment.content %></p>
</div>
<% end %>
<p>Add a new comment</p>
<%= form_with(model: [@post, Comment.new], local: true) do |form| %>
<div>
<%= form.label :content, style: "display: block" %>
<%= form.text_area :content %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
先ほど作成したPostレコードの詳細ページにアクセスし、Comenntレコードを作成してみます。
無事にレコードが作成されました。
では、コンソールでidを確認してみます。
docker compose run app rails c
irb(main):001> Comment.first
Comment Load (0.6ms) SELECT `comments`.* FROM `comments` ORDER BY `comments`.`id` ASC LIMIT 1
=>
#<Comment:0x0000ffff8e274a78
id: "3d6cb29b00",
post_id: "6494ac5f80",
content: "コメント",
created_at: Sat, 14 Sep 2024 06:03:32.050693000 UTC +00:00,
updated_at: Sat, 14 Sep 2024 06:03:32.050693000 UTC +00:00>
無事にid、post_idがランダムな文字列になっていることが確認できました。
このように、foreign_keyも、primary_keyと同じようにstring型に変更することができます。
注意点
一覧画面などで、レコードが作成された順に表示したい場合、idカラムを使用して並び順を指定することがあると思います。
class PostsController < ApplicationController
def index
@posts = Post.all.order(id: :asc)
end
end
しかし、idカラムをランダムな文字列に変更すると、idカラムを使用して並び替えても、レコードが作成された順に表示されなくなります。
この場合は、created_atカラムを使用して並び替えるようにします。
class PostsController < ApplicationController
def index
@posts = Post.all.order(created_at: :asc)
end
end
まとめ
Ruby on Railsで、primary_keyやforeign_keyをランダムな文字列にする方法についてまとめてみました。
少しでも皆様の参考になりましたら幸いです。
最後までお読みいただき、ありがとうございました。
参考文献
Discussion