Tanaka on Rails

行動・努力・俯瞰

ステータスコード

ステータスコードとは

そもそもRailsアプリケーションの仕組みは…
①HTTPサーバよりリクエストを受ける、リクエストはparamsで受け取った情報を元とする。
②リクエストをもとにrooterを経てcontrollerで該当のアクションを呼び出す。
③アクションよりmodelを使用しDBからレコードを引き出し、それをアクション内のインスタンス変数などに代入する。
④その情報をviewを元にHTMLを生成。レコードの情報が埋め込まれたHTMLが渡される。
⑤HTTPサーバを通って、HTMLがレスポンスとして送られる。
→すなわちどんな時でも、リクエストがあった時はレスポンスが返ってきて、ステータスコードが表示される!

ステータスコードはサーバーからのレスポンスの結果を表す、3桁の数字コードのこと。

ステータスコードの種類

100番台:情報レスポンス → 処理中の時
200番台:成功レスポンス → 成功した時
300番台:リダイレクションメッセージ → リダイレクトする時
400番台:クライアントエラーレスポンス → クライアント側でエラーが起こった時
500番台:サーバーエラーレスポンス → サーバ側でエラーが起こった時

ステータスコードの構造

プロトコルバージョンやステータスコードを記述した「ステータス行」
レスポンスするデータの情報などを記述した「ヘッダー」
ヘッダーによって定義される「本文」
から成り立つ。

関連するワード

head
headメソッドを使用することで、ヘッダだけで本文のないレスポンスをブラウザに送信できる。headメソッドには、HTTPステータスコードを示す多くのシンボルを引数として指定できる。
例えばcontrollerの条件分岐で失敗した時にhead :bad_requestとするとステータスコード400のヘッダだけのレスポンスが返ってくる。

参考URL

https://railsguides.jp/layouts_and_rendering.html#head%E3%81%A7%E3%83%98%E3%83%83%E3%83%80%E3%81%AE%E3%81%BF%E3%81%AE%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B
https://diveintocode.jp/blogs/Technology/ProcessFlow
https://www.itmanage.co.jp/column/http-www-request-response-statuscode/
https://digitalidentity.co.jp/blog/seo/seo-tech/howto-http-status-code.html
https://qiita.com/unsoluble_sugar/items/b080a16701946fcfce70
https://developer.mozilla.org/ja/docs/Web/HTTP/Status#successful_responses
https://developer.mozilla.org/ja/docs/Web/HTTP/Messages#http_responses
https://qiita.com/Knbass/items/d17910c53ffe64dff6a6

bundle exec と bin の違い

$ bin/$ bundle execの違いがよくわからないので調べてみた。

bundle execについて

Gemのパッケージ管理ツールBundlerによって使うことができる。
Gemfileに基づいて実行をするというコマンド。
たとえば$ bundle exec rspecRSpecを実行する際は、アプリケーション内のGemfileに設定してあるバージョンで実行できる。
$ rspecだとローカル環境にインストールされているGemのRSpecを使用することになるため、バージョンが異なったり、インストールされてない場合は実行されない。

binについて

ググってみたがあんまりピンとこなかったが、現場Rails P65 で以下のような記述があり非常にわかりやすかった。

アプリケーションのルートディレクトリ直下のbinディレクトリにあるrailsというスクリプトを呼び出しています。このスクリプトを利用すると、「bundle exec rails」として実行した時と同様に、Gemfileどおりのgemを利用できる環境上でrailsコマンドを実行することができます。
それに加えて、SpringというRailsの起動を効率的に行う機能も組み込まれます。このように、よく使うコマンドを包み込んで使いやすくするスクリプトを「binstub」といいます。

つまりbinディレクトリ以下に設定してあるスクリプト$ bin/railsのように使うことができ、$ bundle exec railsと同じように、そのアプリケーションのGemfileに設定してある環境でコマンドを実行することができるってこと。
また、$ bin/rspecをしてみるとSpring is runningと自動的に設定されることも確認できる。ここが$ bundle execとの違い。

ちなみにrailsコマンドについてはbinをつける必要はないらしい。(つけなくても一緒の挙動らしい)

参考

現場Rails
https://railsguides.jp/initialization.html
https://qiita.com/d0ne1s/items/fa2dafcee02e963fe997
https://github.com/rbenv/rbenv/wiki/Understanding-binstubs
https://techracho.bpsinc.jp/hachi8833/2016_08_24/25037
https://qiita.com/jnchito/items/c5a0848144203dce6e26

環境構築系学び

学び

シェルをbashに変更
echo $SHELLで現在のシェル確認
chsh -s /bin/bashで切り替え
これだとrbenvとnodenvのパスが通ってないので、vi ~/.bashrcをして以下のように記述

export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init - bash)"
export PATH="$HOME/.nodenv/bin:$PATH"
eval "$(nodenv init - bash)"

これでsource ~/.bashrcすれば、rbenvとnodenvを使ってバージョンが変更できるようになる
Rubyの場合はrbenv local ○○
Nodeの場合はnodenv local ○○ で変更可能

Redis
「REmote DIctionary Server」のこと
NoSQL
インメモリなので高速

bundle install --path vendor/bundle
vendor/bundle以下にgemがインストールされる
gemをアプリケーションごとに管理できるため、バージョン違いによって起こる問題を解決できる
.gitignoreに設定していないとgitに大量に変更履歴が残ってしまうため注意

webpack
JavaScript」「CSS」「画像やフォント」といった静的アセットを管理できる
より新しいJavaScriptツールやNPMパッケージとの統合に優れており、より多くのものを統合できる

参考URL

Macのターミナル(シェル)でbashやzsh を切り替える方法 | Hirooooo’s Labo

Macのzshでrbenvを使う - Qiita

Redisとは?RailsにRedisを導入 - Qiita

Webpacker の概要 - Railsガイド

ブログの指針変更!

ブログを作成したはいいものの、全然更新していませんでした。😭
理由としては、前回書いた記事のように一つの記事を書くのに時間がかかりすぎてしまうため、面倒臭いなって思ったからです。
ただせっかくブログを作ったので、指針を変更してまた利用を再開しようと思います!

新しい指針

  • 前日に新しく学んだことを毎朝10〜20分かけて振り返りまとめていきます。(できるだけ毎日続けたい😅)
  • たまに単元ごとにまとめものも投稿します。

単純なアソシエーションと第3のモデルを利用したアソシエーション

何をまとめたいか

本投稿は単純なアソシエーションとブックマーク機能やお気に入り機能などを作れるような第3のモデルを利用したアソシエーションについてまとめていきます。
まだまだ知識浅薄であるため間違いがあったらご教示いただけると幸いです。

前提

猫型ロボット(nekogata_robot)は道具(item)を持っています。
また猫型ロボットは道具の保管方法としてポケット(pocket)を使用している時もあります。

それに伴い、モデルはnekogata_robotitempocketにて考えていきます。それぞれ
$ rails g model Nekogata_robot name:string
$ rails g model Item nekogata_robot:references name:string#1
$ rails g model pocket nekogata_robot:references item:references#2
でモデルを作っておきます#1と#2は後から触れていきますがアソシエーションにおいて非常に重要です。

キホン!

様々な関連付けがありますが、まずはキホン的なものから。

猫型ロボット(nekogata_robot)は道具(item)を持っているためアソシエーションが成り立ちます。
nekogata_robotモデルとitemモデルを使用してアソシエーションを行なっていきます。

まず外部キー制約を考慮してitemモデルを作っていきます。
(既にnekogata_robotモデルは作成されている前提)
$ rails g model Item nekogata_robot:referencesを行うと…

class CreateItems < ActiveRecord::Migration[6.1]
  def change
    create_table :items do |t|
      t.references :nekogata_robot, null: false, foreign_key: true
      t.string :name

      t.timestamps
    end
  end
end

というマイグレーションファイルができます。これで$ rails db:migrateをすると外部キーnekogata_robot_idがカラムとして追加されます。(もちろんnameも追加されます)
これによってnekogata_robotモデルのid(主キー)とitemモデルのnekogata_robot_id(外部キー)によってデータベースにおける関連付けがなされます。

次にアソシエーションの関係をコードに書いていきます。
「一匹の猫型ロボットがたくさんの道具を持っている」
つまりnekogata_robotモデルに対してitemモデルは「1対多」の関係。
models/nekogata_robot.rb

has_many :items

models/item.rb

belongs_to :nekogata_robot, dependent: :destroy

猫型ロボットが壊れてしまったら道具の保有者がいなくなるので、そうした際は保有していた道具ごと削除する必要があります。
そのような場合にdependent: :destroyは親モデルに連動して削除されるという機能をもたらします。

こうしてとりあえずのアソシエーションが完成したわけですが、実際に使用する際には
nekogata_robot.items.name(猫型ロボットの持っている道具の名前を探す)
items.nekogata_robot.name(道具を持っている猫型ロボットの名前を探す)
といった呼び出し方ができます。

以上がアソシエーションの基本です。

第3のモデルを利用したアソシエーション

ただこの猫型ロボットが持っている道具は単純に家に保管しているかもしれないですが、持ち運びをする場合にはポケットに保管していつでも道具を使えるようにしているかもしれないですよね?
また、猫型ロボットは一匹だけではなく、他にもたくさんの種類がいるかもしれないですよね?

すなわち以下の図のような関係をどのようにアソシエーションするかということです。
Image from Gyazo

この「多対多」の考え方についてもアソシエーションによって関連づけをすることができます。
「猫型ロボットはポケットに道具を持っている」
「道具は猫型ロボットのポケットに収納されている」
すなわちnekogata_robotモデルとitemモデルの間には「pocketモデル」があるのでこれをhas_many :throughを使って関連づけをしていきます。
(この章の表題の第3のモデルとはpocketモデルのことだったのです!)

pocketモデルを作成するためには…
$ rails g model Pocket nekogata_robot:references item:references を行います。

class CreatePockets < ActiveRecord::Migration[6.1]
  def change
    create_table :pockets do |t|
      t.references :nekogata_robot, null: false, foreign_key: true
      t.references :item, null: false, foreign_key: true

      t.timestamps
    end
  end
end

というマイグレーションファイルができるので、例の如く$ rails db:migrateをします。
するとnekogata_robot_idカラムとitem_idカラムが作成されます。

このpocketモデル内のnekogata_robot_idにnekogata_robotモデルのid(猫型ロボットを特定するid)、item_idにitemモデルのid(道具を特定するid)を入れた状態でcreateなどをすることで保存していくと、データベースのpocketモデルにどの猫型ロボットがどの道具をポケットに収納しているのかというデータが保管されます。

同様に第3のモデルを考慮した際のコードも追加していきます。それぞれ
models/nekogata_robot.rb

has_many :pockets, dependent: :destroy
has_many :pocket_items_boards, through: :pockets, source: :item

models/item.rb

has_many :pockets, dependent: :destroy
has_many :pocket_items_boards, through: :pockets, source: :item

models/pocket.rb

belongs_to :nekogata_robot  
belongs_to :items  

となります。上2つは全く同じコードです。

has_many :pockets, dependent: :destroy
当然アソシエーションを作るためにこの記述は必要となり、猫型ロボットか道具のいずれかが消えてしまえばポケットは存在する意味がないので消すべきです。
ただこの記述だけだと、それぞれがpocketメソッドとアソシエーションを結んでいるが、第3のモデルを使ったアソシエーションは依然使うことができない状態です。

そして次の記述が今回のポイント!

has_many :pocket_items_boards, through: :pockets, source: :item

2つ重要な要素があるのでそれぞれ確認をしていきます。

through: :pockets
第3のモデルであるpocketsモデルを通じてそれぞれアソシエーションを組む際にはこの記述が必要不可欠です。この記述によって以下のようなコードでポケットを絡めたデータの呼び出しが可能になります。
nekogata_robot.pocket_items.name(猫型ロボットがポケットに収納している道具の名前を探す)

ただこれだけでは物足りないと思います。安心してください、まだ特殊能力が付与されてます!

それがcollection << (object, …)というメソッド!
Railsガイドには以下のような説明があります。

collection<<メソッドは、1つ以上のオブジェクトをコレクションに追加します。このとき、追加されるオブジェクトの外部キーは、呼び出し側モデルの主キーに設定されます。

Railsガイドだけ見てもわかりにくいので実際に使ってみるとわかりやすいです。

models/nekogata_robot.rb

def pocket_create(item)
  pocket_items << item
end

このようなメソッドを作成し、コントローラでnekogata_robotitemをそれぞれ定義してあげた上で…

nekogata_robot.pocket_items(item)

のようなコードを使えば、レシーバのnekogata_robotよりnekogata_robot_idが、引数のitemよりitem_idがそれぞれ値が入力されてpocketモデルに新しいデータが作り出されます。これは

pocekt.create(nekogata_robot: params[:nekogata_robot], item: params[:item])

を行っていることと等しいため、コードの省略に貢献するでしょう!

has_many :pocket_items, ~ source: :items
sourceは基本的にアソシエーションを行う際に使います。
具体的にいうとsourceでは実際にthroughで指定したモデルを通じて関連づけをされる小モデルが指定されます。
なのでここではpocketモデルを通じて関連づけをされる子モデルitemモデルが入ります。なお複数形なので注意しましょう。

そして結論なのですが、has_many :pocket_itemsではsource: :itemsで指定したitemsの代わりに、実際に使用する際にはpocket_itemsを使うようにするという言い換えを定義しています。

なぜこんなことする必要があるのでしょうか?
それはこの言い換えを行わないとitemsが重複するからです。

キホン!でも説明したように、既にnekogata_robotと単純なアソシエーション関係にあるため…
nekogata_robot.items.name(猫型ロボットの持っている道具の名前を探す)
のような使い方をされていました。

そして今またここでitemsを定義してしまうと、itemsは単純なアソシエーションによるものなのか?はたまた第三者のモデルを使ったアソシエーションによるものなのか?
2つの意味を持ってしまうため訳が分からなくなります。

そういった事態を避けるためにこのsourceを使用しています。なのでthroughを使ったアソシエーションではsourceは非常に重要な意味を持つのです。

結論

単純なアソシエーションなら
has_many :〇〇belongs_to :〇〇

第3のモデルを使う場合は単純なアソシエーションに加えて
has_many :〇〇, through: :〇〇, source: :〇〇 を書いてあげればOK!

なお今回の事例ではド〇えもんのポケットを例にして記入したため、22世紀まではこの機能を実装することはまずあり得ないと思います。
実際にはブックマーク機能やお気に入り機能を作る際に非常に役に立つっぽいです!

参考

https://qiita.com/imotan/items/036ceffb79e294d8a063
https://qiita.com/tomoharutt/items/e548186c763079327ed1#through
https://railsguides.jp/association_basics.html#has-many%E3%81%A7%E8%BF%BD%E5%8A%A0%E3%81%95%E3%82%8C%E3%82%8B%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89-collection

自己紹介・ブログの指針

自己紹介


2021年5月末をもって以前在籍していた会社を退職し、現在フルコミットでプログラミング学習に専念しています!

ブログの指針


学習を進めていく中で気になったこと、つまづいたこと、よくよく考えてみたら意味が分かっていないことなどを深く追求し、簡潔にまとめていきたいと思っています。
まとめていくことでアウトプットをしていき、知識の拡充に努めていきます!

※追記
ブログの指針変更! - Tanaka on Rails
で指針を変更しました!

ちなみに…


普段の学習の記録をTwitterで発信しています!
よかったらフォローして監視をしてください😁↓

twitter.com