Rails初級者が一ヶ月研修を受けて得た学びをまとめた~Rails~

この記事は「[Rails初級者が一ヶ月研修を受けて得た学びをまとめた - Qiita](https://qiita.com/Nifu-Uchi/items/f676d9f082e8c02bd88d)」のRailsに関する項目を切り出した分割記事です
概要などについては親記事を参照してください。

# Rubyの文法など基本的なこと
### 配列の種類
- `%i`：シンボル配列。メソッド名、変数名などの配列を作るときに

### Hashは省略記法が使える
- Ruby3.1から対応
- [Hashの値の省略記法 - NaCl非公式ブログ](https://nacl-ltd.github.io/2021/12/20/hash-value-omission.html)
- `{ x: x, y:y }` -> `{ x:, y: }`

## pp pp puts printの使い分け
- [【ruby】p pp puts print　違い。 - Qiita](https://qiita.com/nyaoo/items/f5d84581d3a301a9c22f)

### enumの挙動に注意
- key,valueどちらを渡してもsaveは成功する
- [Rails5でenum定義したカラムの元の値を取得 - Qiita](https://qiita.com/yusuke-matsuda/items/df05c8165e2f084023b0)
- DBにはvalueで保存される
  > ActiveRecord上ではenumのkey名として保持して、DBに保存する時に数値で変換して保存してくれます

- `Task.status`が`{ yet: 0, ongoing: 1, complete: 2 }`のようになっていた場合、`Task.ongoing`で`Task.where(status: 'ongoing')`と同じことが実行さる。
- このようなカラムに`numericality: { only_integer: true }`などのvalidationをつけるとkey名でsaveできなくなるので注意
- 付与するvalidationも`presence: true`程度で良い
  > なので独自に追加するのは presence: true 程度で良いかと思います。
記事とかだと inclusionを指定しているようなものもありますが、上記通りRails側で指定したenumのkey / value以外をしてしようとするとエラー発生してくれるのでinclusionも必要ありません。
- この仕様がわかっておらず騒いでいる初心者のアホ→[railsのenum型で沼](https://zenn.dev/nifchi/scraps/5051c160716f9c)
- enumについてはここが参考になった [【Rails】５ステップでイケてる enum を作る（翻訳） - Qiita](https://qiita.com/punkshiraishi/items/799bef63607e03262644)

# Rails実装に関すること

### Gemfile内でのgroup指定
- rubocopなど本番環境で利用しないものは適切にgroup指定をする
- ref:[https://bundler.io/guides/groups.html](https://bundler.io/guides/groups.html)

### 現在アプリ側で利用されているDB設定を確認する
- envを利用して参照するDBをDockerコンテナ内とローカルDBとを切り替えたりしていた
- その際「この環境変数とconfigちゃんと有効になっている？」と気になったときに使った
- `Rails.application.config.database_configuration` で参照できる
- ref: [Rails ActiveRecord 現在のDB設定を確認する方法 | ふらっと考える](https://omohikane.com/rails_get_current_db_config/)


## ユーザビリティー・インターフェイス
### errorメッセージが日本語で出るようにi18nを適応させる
- ユーザーフレンドリーなのでやっておこう
### enumはi18nとgemで日本語化できる
- 例えばタスクの優先度をこのようなenumで定義していた場合
  ```rb
    class Task < ApplicationRecord
      ....

      enum priority: {
        low: 0,
        middle: 1,
        high: 2
      }
  ```
- フロントで表示する際は`低、中、高`としたい
- その時は`enum_help`で定義できる
- 便利なheler_methodも用意されている
- 間違っても`enum priority: { 低:0, 中:1, 高:2 }` のような定義をmodelでしないこと
- refs
  - [zmbacker/enum_help: Help ActiveRecord::Enum feature to work fine with I18n and simple_form.](https://github.com/zmbacker/enum_help)
  - [【Rails】enumをI18n対応させるenum_helpが便利すぎた - ひよっこエンジニアの雑多な日記](https://kimuraysp.hatenablog.com/entry/2016/05/19/233144)


### created_at/updated_atのRailsが自動付与カラムはユーザー向けには使わない
- 手動データ修正した際にずれる可能性が高い。
- 日付けソートなどの機能を提供したい場合は専用の別カラムを用意した方が良い。

### APIのパラメータは使い手に理解しやすく
- Rails側でしかわからないことは含ませない＝内部実装は隠す
- 理解しやすいインターフェイスに
- これま同じメンバーが実装するフロントでコールするAPIしか設計してこなかったため、この辺りの意識が足りていなかった
  - スマホアプリ開発チーム側で使われるAPIを設計していくことになるのでしっかり意識する必要がある


## メソッド
### controller内で利用する定数はprivate_constantつけとくとよい
- あと、定数はアッパーケースで
- 例えば`tasks_controller`内のメソッドで利用する定数の場合このように書いておくと良い
  ```rb
  class TasksController < ApplicationController
    ...

    SORTABLE_COLUMN = {
      name: 'task_name',
      detail: 'detail',
      limit: 'date_limit',
      status: 'status',
      priority: 'priority'
    }.freeze

    private_constant :SORTABLE_COLUMN

  ```
- 他殻も参照できてしまうのでできるだけスコープは狭く
- 定数定義の仕方は色々あるみたい
  - ref: [Ruby on Railsで定数の指定 - Qiita](https://qiita.com/srockstyle/items/daed31a78c343e607822)

### methodの説明などのコメントはYARD記法で
- YARDはmethodの引数、戻り値などを記載できるDoc記法
- IDEでの型推論に役立つのでどうせ書くならYARDで書こう
- ref: [Rails/Rubyドキュメントをキレイに生成するYARD、早見表付き！　 | 酒と涙とRubyとRailsと](https://morizyun.github.io/blog/yard-rails-ruby-gem-document-html/)

### method名に`set`や`get`,`has`は使わないようにする
- factory_botに怒られる

### 動的にwhereをつけたい時は`compact`を使うと良い
- 例えばクエリパラメータに`status=low`が含まれている場合だけ`Task.where(status: params[:status])`を付与したい…
	```rb
	# bad
	@tasks = params[:status] ? @tasks.where(status: params[:status]) : @tasks

	#good
	@tasks = @tasks.where({ status: params[:status] }.compact)
	```
- このようにhashで渡してやれば`compact`が省略してくれ、where句自体が実行されない
- なお実際は`@tasks.where({ status: Task.statuses[params[:status]] }.compact)` として不正なリクエストパラメーターをクエリに渡さないようにしている

### errorレンダリング関数名はHTTPステータスのシンボルで定義すると良い
- 例えば503エラーページをrenderさせる場合
  - bad: 
    ```rb
    def render_503
        ...
    end
    ```
  - good: 
    ```rb
    def render_service_unavailable
        ...
    end
    ```
- ref:  [レイアウトとレンダリング - Railsガイド](https://railsguides.jp/layouts_and_rendering.html#status%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3)

### controllerでのメソッド呼び出しの仕方
- actionメソッドで利用するTaskのインスタンスの呼び出し方についてこのような定義をしていた
	- それぞれのcotrollerメソッドの前に`before_action`で`@task`にインスタンスを格納し、それをメソッド内で参照していた
	```rb
	  before_action: :task
	  ....

	  def update
	    @task = 必要な処理
	    @task.update
	  end

	  def destroy
	    @task.destroy
	  end

	  def task
	    @task = Task.find[params[id]]
	  end

	```


- PRで提案された記法がこう
	```rb
	# before_action: :task


	def update
	  task.update
	  ....
	end

	def destroy
	  task.destroy
	end

	private 

	def task
	  @task ||= Task.find(params[:id])
	end

	```
### point
  - `||=` を使い、**同一メソッド内で呼び出された場合は、DBへの再参照が起きなようにしている**
    - ref: [演算子式 (Ruby 3.1 リファレンスマニュアル) -自己代入](https://docs.ruby-lang.org/ja/latest/doc/spec=2foperator.html#selfassign)
    - すでに値が定義済みなら再代入しない
- privateメソッドでタスクインスタンスを定義し、メソッドを呼び出して関数内でも利用する。（railsっぽい書き方らしい）
  - `before_action`でセットされたインスタンス変数を使うとactionコードだけ見たときにわかりづらいから
- **Q.**
	>   getterメソッドで対応し、インスタンスに直接アクセスしない様にするのは何かアーキテクチャ的理由があるのでしょうか？

- **A.**
  > クラス内部でインスタンス変数を参照するのは全然構わないと思います！（人によっては getter メソッドに統一する人もいる）
  今回のでいうと、各アクションでは before_action で事前にセットされたインスタンス変数にアクセスしていますが、アクションのコードだけ見るとその前提が分かりづらいので、分かりやすい形にしておきたいって感じですかねー
  こういう時はこういう風に書くというのが大体決まっているので、そこから外れないように書くと他の人にも読みやすいコードになるかと思います！（こういう風というケースは、他の人のコードを読んで学習するのが一番👍）


### redirect_toは引数の指定の仕方に注意
- [redirect_toでaction指定して、かつnoticeも設定しようとしたら、引数にハッシュを渡す云々で詰まった - Qiita](https://qiita.com/tsubasa_upset/items/dd4a11a90bf156557de6)


### redirect_toは3xx以外のstatusを渡すと正常に動かない
- redirect_toはリダイレクト先とHTTPstatusを渡すことができる
- 「ここに任意のstatusを与えて使えますよ〜」と書いてある記事は多いが、実際は3xx以外渡すと動かなかった
- 例えば201を渡すとこんな感じ
  ```bash
    ↳ app/controllers/tasks_controller.rb:41:in `create'
    Redirected to http://localhost:3000/tasks/new
    Completed 201 Created in 76ms (ActiveRecord: 48.7ms | Allocations: 12669)
    ## ↑ここでredirect処理自体は終わっている

    ## ↓ここからは?
    Started GET "/tasks" for ::1 at 2022-08-25 17:51:19 +0900
    Processing by TasksController#index as HTML
  ```

- `http://localhost:3000/tasks/new` にリダイレクトされる処理が完了してしまっているが、GETが走っていない。
- その代わり、直後に謎の力で`root_path`へのGETメソッドが走っている
- specで同様のことをしてもこの挙動は起きないため、railsの何らかのおせっかいで`root_path`に飛ばしてくれている？
- `redirect_to`の公式にも`3xx以外だと動かないよ！`としか書かれていない [rails/redirecting.rb  rails/rails](https://github.com/rails/rails/blob/984c3ef2775781d47efa9f541ce570daa2434a80/actionpack/lib/action_controller/metal/redirecting.rb#L40-L42)

## erb
### deleteメソッドは`link_to`ではなく`button_to`で
- Rails7からJS仕様が変更になり`link_to`で`DELETE`メソッドを使うと`GET`になってしまう
- [devise - Rails 7 - link_to with method :delete still performs GET request - Stack Overflow](https://stackoverflow.com/questions/70474422/rails-7-link-to-with-method-delete-still-performs-get-request)
- [Rails7でmethod: :deleteの扱い - KATSUSHI-OUGI.COM](https://katsushi-ougi.com/rails7-method-delete)
- `button_to`を使いましょう

### `<% %>`と`<%= %>`は別物
- これは完全に自分がアホ
- `<% %>`　-> 結果がレンダリングされない
- `<%= %>`　-> 結果がレンダリングされる
- [Action View の概要 3.1.1 ERB　- Railsガイド](https://railsguides.jp/action_view_overview.html#erb)

### erbLintを入れてerbの整形を
- [Shopify/erb-lint: Lint your ERB or HTML files](https://github.com/Shopify/erb-lint)

### Deviseで使われている認証フレームワークWarden
- [Wardenの使い方 まとめ - 猫Rails](https://nekorails.hatenablog.com/entry/2018/10/27/172832)
- 直接触ってはいないが、Deviseで実装したサインイン機能をrspecで呼び出す際に勉強した気がする。
- 深追いはしない
### migrationはbulkupdateで行うと良い
- rubocopの `Rails/BulkChangeTable: You can use change_table :tasks, bulk: true to combine alter queries.`でも指摘される
- 先輩も
  > migrationファイルで実行されるSQLは1トランザクションで完結するべきと考えていて、それを半強制するこのルールは好きです。

  とおっしゃっており、ベターっぽい
  ```rb
    # bad
    def up 
      change_column ....
      change_column ....
    end

    # good
    def up 
      change_table(:tasks, bulk: true) do |t|
        t.index ...
        t.chage_default ...
      end
    end
  ```
- 使い方はこちらを参照：[change_table | Railsドキュメント](https://railsdoc.com/page/change_table)