この投稿は、
DMM WEBCAMP mentor Advent Calendar 2022
の投稿13日目のエントリーです。
12日目も 私で
【Rails】gemを使わずに 楽天Apiを動かしてみよう:後編【ハンズオン】
環境と前提
- ruby 3.1.1
- Rails 6.1.7
Railsでアプリを作る
準備
準備は、 rails new
をやる準備が整っている前提で進めていきます。
$ rails _6.1.7_ new routes_test
でアプリを作成します。
もし、この時点で、
can't find gem railties (= 6.1.7) with executable rails (Gem::GemNotFoundException)
といったエラーが出るようでしたら、
$ gem install rails -v 6.1.7
を実行して、該当バージョンのRailsをインストールしておきましょう。
コマンドの結果は以下となります。
$ 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 🎉 🍰
&
プロジェクトに移動します。
$ cd routes_test
ここで、Rails6.1系とruby3.1系との組み合わせで必要となるGemがあります。
gem "net-smtp"
gem "net-imap"
gem "net-pop"
上記3点を追加して、
$ bundle install
を実行しておきましょう。
これで準備は完了です。
モデル
今回はモデルが必要なコマンドを使う為、ざっくり scaffold
で作ります。
$ rails g scaffold User name
$ rails g scaffold Post title
これでMVCを作成し、
% 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
で、自動で書かれてしまいますが、無視して・・・・
Rails.application.routes.draw do
resources :users do
resources :posts
end
end
このようにネストさせて書き換えてみます。
早速ターミナルで、
rails routes -c users
と打ってみましょう。 -c
オプションはコントローラー名で絞り込みをします。
$ 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ヘルパーからパラメーターを確認したいと思います。
コンソールを起動しましょう。
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
を作ることが出来ました。
また、この数字の部分はインスタンスで代用できますので、
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) %>
このコードの @user
の id
が 1
だとしたら、
<%= link_to "詳細", 'users/1' %>
という感じです。
ただし、Railsでは、 routes.rb
の書き換えでURLを切り換える事が出来ますので、 View
には、 ヘルパーを使った ~_path
(相対パス) ~_url
(絶対パス) と記述し、もし URLを変更する必要があるとすれば、routes.rb 側で出来る限り対応します。 すると変更があっても直す場所が少なくて済みます。
では、今度はパラメーターの方を見ていきます。
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
は取得できませんので、ここは仮に
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"}
このように変数に入れてしまいます。
それから、ブラケット記法で、
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
のルーティングは、
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箇所で行われており、見た目にすでにややこしいです。
早速コンソールで見てみましょう。
まずは、必要データーを作ります。
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
を作ります。
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
...
では、先程の要領で、パラメーターを表示させてみましょう。
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
は簡単に入れられます。
そうなると、ルーティングの案として、
Rails.application.routes.draw do
resources :users
resources :posts
end
でも十分良いですし、 どうしても :user_id
が欲しいんじゃーという場合、
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? %>
という条件式も入れておき、
<!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やり始めた頃は、あれれ?ルーティングネストしてないとパラメーター取れないよね?みたいな感じで自ら自爆していいた事もあったなぁ・・・。
ここまで読んでいただき、ありがとうございます。