Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Rails Controller Fundamentals

120 views

Published on

鳥取Ruby会とっとるびー勉強会資料.

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Rails Controller Fundamentals

  1. 1. Rails Controller Fundamentals — とっとるびー第31回 2018-09-24 — https://tottoruby.connpass.com/event/100646/ — @hamajyotan (SAKAGUCHI Takashi)
  2. 2. さきにお伝え — ちょっとした Rails コードを動かしたりします. 以下 の状態が手元にあると, 一緒に試したりできます. — Ruby 2.5.1 , Rails 5.2.1 で実施しますが, そこまで 厳密に合わせる必要はないです. — gem sqlite3. (rails new で database を指定しない ので既定で使っているだけであり, 行間読んで別 DB を指定できればそれで OK です)
  3. 3. 自己紹介 — SAKAGUCHI Takashi — Office UMMM LLC — Ruby とか Rails とかがちょっとできるおじさんです
  4. 4. 今日話すこと — scaffold よくみてみようという件 — resource routing にこだわろうという件 — template inheritance 便利だよという件
  5. 5. scaffold よくみてみようという件
  6. 6. 簡単なアプリを作りつつ $ ruby -v ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux] $ rails -v Rails 5.2.1 $ rails new example --skip-active-storage 出力省略... $ cd example/ $ ./bin/rails s ➡ http://localhost:3000
  7. 7. 簡単なアプリを作りつつ $ ./bin/rails scaffold message title body:text published:boolean $ ./bin/rails db:migrate ➡ http://localhost:3000/ messages
  8. 8. resource(s) なルーティング config/routes.rb Rails.application.routes.draw do resources :messages end $ rails routes Prefix Verb URI Pattern Controller#Action messages GET /messages(.:format) messages#index POST /messages(.:format) messages#create new_message GET /messages/new(.:format) messages#new edit_message GET /messages/:id/edit(.:format) messages#edit message GET /messages/:id(.:format) messages#show PATCH /messages/:id(.:format) messages#update PUT /messages/:id(.:format) messages#update DELETE /messages/:id(.:format) messages#destroy
  9. 9. アクションとその意味 Action 意味 index 一覧表示(画面) create 新規作成 new 新規作成(画面) edit 編集(画面) show 詳細表示(画面) update 更新 destroy 削除
  10. 10. html 都合の new/edit はこのさい置いとく Resource HTTP Method Action 意味 /messages GET index 一覧表示(画面) /messages POST create 新規作成 /message/:id GET show 詳細表示(画面) /message/:id PATCH/PUT update 更新 /message/:id DELETE destroy 削除
  11. 11. HTTP のメソッド HTTP Method /messages /message/:id GET index show PUT/PATCH - update DELETE - destroy POST create -
  12. 12. HTTP のメソッド HTTP Method 意図など 安全 冪等 GET リソースの取得 ! ! PUT/PATCH リソースの配置 !❓ DELETE リソースの削除 !❓ POST リソースの生成
  13. 13. GET — リソースの取得. 常に安全で冪等. — scaffold の実装だと, コレクションへの GET は常に 200 でメンバーへの GET はない場合 404 を返す. def index @messages = Message.all end def show end
  14. 14. PUT/PATCH — リソースの配置. — scaffold の実装の場合存在しない場合は 404 を返す が, 定義からすれば ID を明示した merge (insert or update) とした方が適当かも. def update respond_to do |format| if @message.update(message_params) format.html { redirect_to @message, notice: 'Message was successfully updated.' } format.json { render :show, status: :ok, location: @message } else format.html { render :edit } format.json { render json: @message.errors, status: :unprocessable_entity } end end end
  15. 15. DELETE — リソースの削除. — scaffold の実装の場合存在しない場合は 404 を返す が, 定義からすれば存在しない場合は何もしないのが 適当かも. def destroy @message.destroy respond_to do |format| format.html { redirect_to messages_url, notice: 'Message was successfully destroyed.' } format.json { head :no_content } end end
  16. 16. POST — リソースの生成 — 「生成」としているのはコレクションリソースに対 する操作であるため. def create @message = Message.new(message_params) respond_to do |format| if @message.save format.html { redirect_to @message, notice: 'Message was successfully created.' } format.json { render :show, status: :created, location: @message } else format.html { render :new } format.json { render json: @message.errors, status: :unprocessable_entity } end end end
  17. 17. 60行すこし class MessagesController < ApplicationController before_action :set_message, only: [:show, :edit, :update, :destroy] def index @messages = Message.all end def show; end def new @message = Message.new end def edit; end def create @message = Message.new(message_params) respond_to do |format| if @message.save format.html { redirect_to @message, notice: 'Message was successfully created.' } format.json { render :show, status: :created, location: @message } else format.html { render :new } format.json { render json: @message.errors, status: :unprocessable_entity } end end end def update respond_to do |format| if @message.update(message_params) format.html { redirect_to @message, notice: 'Message was successfully updated.' } format.json { render :show, status: :ok, location: @message } else format.html { render :edit } format.json { render json: @message.errors, status: :unprocessable_entity } end end end def destroy @message.destroy respond_to do |format| format.html { redirect_to messages_url, notice: 'Message was successfully destroyed.' } format.json { head :no_content } end end private def set_message @message = Message.find(params[:id]) end def message_params params.require(:message).permit(:title, :body, :published) end end
  18. 18. resource routing にこだわろうという件
  19. 19. メッセージにお気に入りをつけたい — って要件を考えてみる. — 特定のメッセージに対してお気に入りにしたりそれ を外したりできる — 今回は「誰が」お気に入りをつけるのか? という情報 はサボる — 本質でないので, 簡単に favorited:boolean の フラグを Message モデルにつけるだけにしてる.
  20. 20. favorited フラグを用意する $ ./bin/rails g migration AddFavoritedToMessages favorited:boolean $ ./bin/rails db:migrate
  21. 21. どのようなアクションを準備すべきか? resources :messages do post :add_favorite, on: :member post :remove_favorite, on: :member end $ ./bin/rails routes Prefix Verb URI Pattern Controller#Action add_favorite_message POST /messages/:id/add_favorite(.:format) messages#add_favorite remove_favorite_message POST /messages/:id/remove_favorite(.:format) messages#remove_favorite ... class MessagesController < ApplicationController def add_favorite end def remove_favorite end ... ➡ (´・ω・`)…
  22. 22. どのようなアクションを準備すべきか? resources :messages do put :favorite, on: :member delete :favorite, on: :member end $ ./bin/rails routes Prefix Verb URI Pattern Controller#Action favorite_message PUT /messages/:id/favorite(.:format) messages#favorite DELETE /messages/:id/favorite(.:format) messages#favorite ... class MessagesController < ApplicationController def favorite # favorite & unfavorite ? end ... ➡ (´・ω・`)……
  23. 23. どのようなアクションを準備すべきか? resources :messages do put :favorite, on: :member, action: 'add_favorite' delete :favorite, on: :member, action: 'remove_favorite' end $ ./bin/rails routes Prefix Verb URI Pattern Controller#Action favorite_message PUT /messages/:id/favorite(.:format) messages#add_favorite DELETE /messages/:id/favorite(.:format) messages#remove_favorite ... class MessagesController < ApplicationController def add_favorite end def remote_favorite end ... ➡ (´・ω・`)………
  24. 24. ルーティングの設計ガイドライン — resource(s) で提供されるアクション以外を避ける — index, show, new, create, edit, update, destroy のみ — config/routes.rb で get とか post とかをな るべく使わない.
  25. 25. ルーティングの設計ガイドライン — 実現することから名詞を探し, 積極的にサブリソース としてデザインする — /messages/:message_id/favorite
  26. 26. ルーティングの設計ガイドライン — 今回の例では /messages/:message_id/ favorite に対して PUT/DELETE で実現 — DELETE だからと言って, 実際に DB 上でデータを 削除する必要はない. — URI のリソースに対し, DB での永続化方法は全 く別の話. — DB構成変えるたびに URI構成変えないでしょ?
  27. 27. 再考 - ルーティング resources :messages do resource :favorite, only: %i[update destroy], module: 'messages' end $ ./bin/rails routes Prefix Verb URI Pattern Controller#Action message_favorite PATCH /messages/:message_id/favorite(.:format) messages/favorites#update PUT /messages/:message_id/favorite(.:format) messages/favorites#update DELETE /messages/:message_id/favorite(.:format) messages/favorites#destroy ...
  28. 28. 再考 - コントローラ # app/controllers/messages/favorites_controller.rb class < Messages::FavoritesController before_action :set_message def update respond_to do |format| if @message.update(favorited: true) format.html { redirect_to @message, notice: 'Favorited.' } else format.html { redirect_to @message, alert: 'Favorite failed.' } end end end def destroy respond_to do |format| if @message.update(favorited: false) format.html { redirect_to @message, notice: 'Unfavorited.' } else format.html { redirect_to @message, alert: 'Unfavorite failed.' } end end end private def set_message @message = Message.find(params[:message_id]) end end # (json 省略.)
  29. 29. template inheritance 便利だよという件
  30. 30. template inheritance #とは — ビューはどのディレクトリ/ファイルを探索する? — app/views/コントローラ名/アクション名.拡張子 みたいなところ? — ➡ だいたいあってるけどもうひといき — ➡ ❓
  31. 31. template inheritance #とは — ビューはどのディレクトリ/ファイルを探索する? — app/views/コントローラ名/アクション名.拡張子 みたいなところ? — ➡ だいたいあってるけどもうひといき — ➡ コントローラの継承ツリーがヒントになっている
  32. 32. view template の探索順序 — コントローラの継承ツリー — MessagesController — < ApplicationController — ➡ 以下の順に探索される 1. app/views/messages/* 2. app/views/application/*
  33. 33. view template の探索順序 — 例えば Messages のサブクラスの Foos を作ったら — FoosController < MessagesController — ➡ 以下の順に探索される 1. app/views/foos/* 2. app/views/messages/* 3. app/views/application/*
  34. 34. ちょっとためそう $ mkdir app/views/application $ mv app/views/messages/index.html.erb app/views/application/ ➡ 場所移しても動く. $ cp app/views/application/index.html.erb app/views/messages/index.html.erb $ echo '<h2>ほげ</h2>' >> app/views/messages/index.html.erb ➡ messages/ が優先.
  35. 35. みんな ApplicationController を継承してるということは — app/views/application/ は共通ビューテンプレ ート置き場に使える. — しかも, 場合によっては各々のコントローラで上書き できる. — これ知らずに app/views/shared/* とか作るより Rails way かと.
  36. 36. たとえばこんなルーティング — /messages - メッセージ一覧 — これに対して、 /messages/draft という URL を準 備する — 基本的には /messages と同じ. — ただし, published が false のデータでフィルタ された状態とする.
  37. 37. /messages/draft をつくる - 1/4 app/controllers/messages_controller.rb def index - @messages = Message.all + @messages = message_scope end private + def message_scope + Message.all + end + def set_message
  38. 38. /messages/draft をつくる - 2/4 app/controllers/messages/ drafts_controller.rb class Messages::DraftsController < MessagesController private def message_scope Message.where(published: false) end end
  39. 39. /messages/draft をつくる - 3/4 config/routes.rb Rails.application.routes.draw do + namespace :messages do + resources :drafts, only: %i[index] + end resources :messages end
  40. 40. /messages/draft をつくる - 4/4 config/routes.rb Rails.application.routes.draw do namespace :messages do - resources :drafts, only: %i[index] + resources :drafts, only: %i[index], path: 'draft' end resources :messages end ➡ 以上 ❗ ビューの修正などはなし
  41. 41. まとめ — scaffold の吐くコントローラは良いコード. — routes にはなるべく resource(s) だけを記述. — URIでデザインするリソースと DB永続化は別の話. — template inheritance をうまく使おう. — 共通のテンプレートは app/views/application/ ディレクトリ

×