LoginSignup
15
2

More than 1 year has passed since last update.

【Rails】で、結局 params[:id]って何なん

Last updated at Posted at 2022-12-13

この投稿は、
DMM WEBCAMP mentor Advent Calendar 2022
の投稿13日目のエントリーです。

12日目も 私で
【Rails】gemを使わずに 楽天Apiを動かしてみよう:後編【ハンズオン】

環境と前提

  • ruby 3.1.1
  • Rails 6.1.7

Railsでアプリを作る

準備

準備は、 rails new をやる準備が整っている前提で進めていきます。

bash
$ rails _6.1.7_ new routes_test

でアプリを作成します。

もし、この時点で、

can't find gem railties (= 6.1.7) with executable rails (Gem::GemNotFoundException)

といったエラーが出るようでしたら、

bash
$ gem install rails -v 6.1.7

を実行して、該当バージョンのRailsをインストールしておきましょう。

コマンドの結果は以下となります。

bash
 $ rails new routes_test
      create  
      create  README.md
      create  Rakefile
      create  .ruby-version
      create  config.ru
      create  .gitignore
      create  .gitattributes
      create  Gemfile
         run  git init from "."
Initialized empty Git repository in /xxxxxx/xxxxxx/xxxxxxx/xxxxxxx/rakuten_api/.git/
      create  package.json
      create  app
.
.
(省略)
.
.
├─ url-parse@1.5.10
├─ utils-merge@1.0.1
├─ uuid@3.4.0
├─ wbuf@1.7.3
├─ webpack-dev-middleware@3.7.3
├─ webpack-dev-server@3.11.3
├─ websocket-driver@0.7.4
├─ websocket-extensions@0.1.4
└─ ws@6.2.2
✨  Done in 6.53s.
Webpacker successfully installed 🎉 🍰
&

プロジェクトに移動します。

bash
$ cd routes_test

ここで、Rails6.1系とruby3.1系との組み合わせで必要となるGemがあります。

Gemfile
gem "net-smtp"
gem "net-imap"
gem "net-pop"

上記3点を追加して、

bash
$ bundle install

を実行しておきましょう。

これで準備は完了です。

モデル

今回はモデルが必要なコマンドを使う為、ざっくり scaffold で作ります。

bash
$ rails g scaffold User name
$ rails g scaffold Post title

これでMVCを作成し、

bash
% rails db:migrate
Running via Spring preloader in process 11163
== 20221213073921 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0134s
== 20221213073921 CreateUsers: migrated (0.0136s) =============================

== 20221213073934 CreatePosts: migrating ======================================
-- create_table(:posts)
   -> 0.0010s
== 20221213073934 CreatePosts: migrated (0.0010s) =============================

マイグレーションを通しておきましょう。

routes.rb

ルーティングでとりあえず、 resources を多発させていませんか?
基本的に、 「書けるのなら resources」 を使うのは正解です。実際にRailsもそれを推奨しています。

ただし、 resources をネストさせて、

  resources :users do
    resources :posts do
      resource :comments
    end
  end

こんなコードを大量に作っていませんか?
一見、階層化していて判断しやすいようですが、これはあかんやつです。

Railsガイドにも

リソースのネスティングは、ぜひとも1回にとどめて下さい。決して2回以上ネストするべきではありません。

と書かれています。

これがどのくらい面倒な話になるのかを今から検証してみます。

では、ルーティングを書いてみましょう。すでに scaffold で、自動で書かれてしまいますが、無視して・・・・

config/routes.rb
Rails.application.routes.draw do
  resources :users do
    resources :posts
  end
end

このようにネストさせて書き換えてみます。

早速ターミナルで、

rails routes -c users

と打ってみましょう。 -c オプションはコントローラー名で絞り込みをします。

bash
$ rails routes -c users
   Prefix Verb   URI Pattern               Controller#Action
    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit
     user GET    /users/:id(.:format)      users#show
          PATCH  /users/:id(.:format)      users#update
          PUT    /users/:id(.:format)      users#update
          DELETE /users/:id(.:format)      users#destroy

ここまでは何の問題もありません。

では、ここに出てきたURLヘルパーからパラメーターを確認したいと思います。

コンソールを起動しましょう。

rails console
 routes_test (main #%)% rails c                    
Running via Spring preloader in process 11558
Loading development environment (Rails 6.1.7)
irb(main):001:0> app.users_path
=> "/users"
irb(main):002:0> app.new_user_path
=> "/users/new"
irb(main):003:0> app.edit_user_path(1)
=> "/users/1/edit"
irb(main):004:0> app.user_path(1)
=> "/users/1"

このような感じで、 URLヘルパーから URL を作ることが出来ました。

また、この数字の部分はインスタンスで代用できますので、

rails console
irb(main):005:0> user = User.create(name: "taro")
   (1.1ms)  SELECT sqlite_version(*)
  TRANSACTION (0.0ms)  begin transaction                               
  User Create (0.8ms)  INSERT INTO "users" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "taro"], ["created_at", "2022-12-13 07:48:43.838522"], ["updated_at", "2022-12-13 07:48:43.838522"]]       
  TRANSACTION (0.6ms)  commit transaction                              
=>                                                                     
#<User:0x00000001075ca938                                              
...                                                                    
irb(main):006:0> app.user_path(user)
=> "/users/1"

このように書く事も出来ます。

また、ここまでで、 Link_to の引数が、実は文字列のURLだという事も分かります。

<%= link_to "詳細", user_path(@user) %>

このコードの @userid1 だとしたら、

<%= link_to "詳細", 'users/1' %>

という感じです。

ただし、Railsでは、 routes.rb の書き換えでURLを切り換える事が出来ますので、 View には、 ヘルパーを使った ~_path(相対パス) ~_url(絶対パス) と記述し、もし URLを変更する必要があるとすれば、routes.rb 側で出来る限り対応します。 すると変更があっても直す場所が少なくて済みます。

では、今度はパラメーターの方を見ていきます。

rails console
Loading development environment (Rails 6.1.7)
irb(main):001:0> Rails.application.routes.recognize_path('users/1')
=> {:controller=>"users", :action=>"show", :id=>"1"}

URLからパスのパラメーターが取得できます。

これは、各アクションに渡されるパラメーターとなっていますので、

params[:controller]
params[:action]
params[:id]

で取得出来ます。

コンソールでは直接 params は取得できませんので、ここは仮に

rails
irb(main):004:0> params = Rails.application.routes.recognize_path(app.user_path(user))
=> {:controller=>"users", :action=>"show", :id=>"1"}
irb(main):005:0> params
=> {:controller=>"users", :action=>"show", :id=>"1"}

このように変数に入れてしまいます。
それから、ブラケット記法で、

rails
irb(main):006:0> params[:controller]
=> "users"
irb(main):007:0> params[:action]
=> "show"
irb(main):008:0> params[:id]
=> "1"

取得できます。

このURLをパラメーター化させたものが、いつも使っている params[:id] の正体ということです。

ネストしたルーティング

いよいよ次が本命となりますが、 posts_controlleのルーティングは、

bash
 routes_test (main #%)% rails routes -c posts
        Prefix Verb   URI Pattern                              Controller#Action
    user_posts GET    /users/:user_id/posts(.:format)          posts#index
               POST   /users/:user_id/posts(.:format)          posts#create
 new_user_post GET    /users/:user_id/posts/new(.:format)      posts#new
edit_user_post GET    /users/:user_id/posts/:id/edit(.:format) posts#edit
     user_post GET    /users/:user_id/posts/:id(.:format)      posts#show
               PATCH  /users/:user_id/posts/:id(.:format)      posts#update
               PUT    /users/:user_id/posts/:id(.:format)      posts#update
               DELETE /users/:user_id/posts/:id(.:format)      posts#destroy

このようになっております。 :user_id :id と idの記録が2箇所で行われており、見た目にすでにややこしいです。

早速コンソールで見てみましょう。
まずは、必要データーを作ります。

rails console
irb(main):001:0> Post.create!(id: 3, title: "hello")
   (0.9ms)  SELECT sqlite_version(*)
  TRANSACTION (0.1ms)  begin transaction                        
  Post Create (1.2ms)  INSERT INTO "posts" ("id", "title", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["id", 3], ["title", "hello"], ["created_at", "2022-12-13 08:10:11.350372"], ["updated_at", "2022-12-13 08:10:11.350372"]]
  TRANSACTION (0.5ms)  commit transaction                       
=>                                                              
#<Post:0x00000001074cf6f0                                       
 id: 3,                                                         
 title: "hello",                                                
 created_at: Tue, 13 Dec 2022 08:10:11.350372000 UTC +00:00,    
 updated_at: Tue, 13 Dec 2022 08:10:11.350372000 UTC +00:00>    

一旦 id: 3 の投稿を作ります。

そして、変数 user post を作ります。

rails console
irb(main):002:0> user = User.first
  User Load (1.2ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=>                                                                     
#<User:0x0000000107febf20                                              
...                                                                    
irb(main):003:0> post = Post.first
  Post Load (0.3ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT ?  [["LIMIT", 1]]
=>                                                                     
#<Post:0x0000000108158188                                              
...                     

では、先程の要領で、パラメーターを表示させてみましょう。

rails console
irb(main):004:0> Rails.application.routes.recognize_path(app.user_posts_path(user))
=> {:controller=>"posts", :action=>"index", :user_id=>"1"}
irb(main):005:0> Rails.application.routes.recognize_path(app.new_user_post_path(user))
=> {:controller=>"posts", :action=>"new", :user_id=>"1"}
irb(main):006:0> Rails.application.routes.recognize_path(app.edit_user_post_path(user, post))
=> {:controller=>"posts", :action=>"edit", :user_id=>"1", :id=>"3"}
irb(main):007:0> Rails.application.routes.recognize_path(app.user_post_path(user, post))
=> {:controller=>"posts", :action=>"show", :user_id=>"1", :id=>"3"}

ここまで書くと、 params[:user_id] は必要ないものがある事に気がつくと思います。

そもそも show edit destroy などは、 そのレコードの id があれば特定出来るので、 :user_id で特定する必要がないのです。

特にログイン後、 current_user が使える場合、 Userに紐付いたレコードの id は簡単に入れられます。

そうなると、ルーティングの案として、

config/routes.rb
Rails.application.routes.draw do
  resources :users 
  resources :posts
end

でも十分良いですし、 どうしても :user_idが欲しいんじゃーという場合、

config/routes.rb
Rails.application.routes.draw do
  resources :users do
    resources :posts, only: [:new, :create]
  end
  resources :posts, only: [:show, :edit, :update, :destroy]
end

こんなルーティングも考えられます。

$ rails routes -c posts
       Prefix Verb   URI Pattern                         Controller#Action
   user_posts POST   /users/:user_id/posts(.:format)     posts#create
new_user_post GET    /users/:user_id/posts/new(.:format) posts#new
    edit_post GET    /posts/:id/edit(.:format)           posts#edit
         post GET    /posts/:id(.:format)                posts#show
              PATCH  /posts/:id(.:format)                posts#update
              PUT    /posts/:id(.:format)                posts#update
              DELETE /posts/:id(.:format)                posts#destroy

元々、Railsは DBの id を一意のキーとして扱うので、複合的に id を管理する必要はあまりないようです。

小ネタ

debug

<%= debug params %> というコードを View に仕込んでおくと、パラメーターで悩む事が減ります。

ただし、Develop環境のみにしたいので、

<%= debug params if Rails.env.development? %>

という条件式も入れておき、

app/views/layouts/applicaion.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>RoutesTest</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %>
    <%= debug params if Rails.env.development? %> ←この辺
  </body>
</html>

に書いておきましょう。すると、開発環境のみで、

View側でパラメーターの確認が出来ます。

結論

params[:id] は URLを構成している情報の一つ。
params は 次のリクエストに値を持ち越すための仕組みなので、 params を知ると世界が平和に。

終わりに

Railsやり始めた頃は、あれれ?ルーティングネストしてないとパラメーター取れないよね?みたいな感じで自ら自爆していいた事もあったなぁ・・・。

ここまで読んでいただき、ありがとうございます。

15
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
2