【Rails】脱初心者っぽい書き方【ポートフォリオ制作】

## はじめに
初心者、実務未経験者には何が足りないのかという視点で書いてみました
**初心者レベルを脱したい人**、**他の駆け出しエンジニアと差別化したい人**には役に立つ内容かと思います

いろいろ書きますが、一気に全部理解しようとはせず**使えそうなものから試してみてください**🔥

* **対象者**<br>
  * プログラミング学習を始めてポートフォリオアプリを制作している人<br>
  * 基礎的な内容の理解ができており、問題なく動くアプリを作れる人<br>
（ちゃんと動くアプリを作れずエラー解決に四苦八苦する人は対象外）
* **内容**<br>
  * 主にサーバーサイド（rubyファイル）

初めてポートフォリオアプリを作成すると、**とりあえず期待通りに動くこと**が目標になるため、実際の現場での作法をいろいろ無視してしまうケースが多くあるかと思います。

まずは動くコードを書くことが最重要ではありますが、**それだけだと現場では通用しないのも事実です**:warning:

現場で教えてもらってできるようになれば問題ない気もしますが、余裕のある人は先に知っておくと良いかと思います。

さらに、**知っていることと使えることは全く別もの**なので、ポートフォリオアプリで**１度は練習しておくと良いです**




## 用意されたリソースだけを使ったルーティング
重要度：　★★★★★

ルーティングは基本的に7つのアクションのみを使うようにします:seven:
（index new create edit update show destroy)

Railsの生みの親DHHも推奨しています（以下参考）
https://postd.cc/how-dhh-organizes-his-rails-controllers/

例えば投稿（post）の公開ステータスを更新したい場合、
 

✕ :  postsコントローラにupdate_statusアクションを追加
○ : post_statuses_controllerを新たに作り、updateアクションでステータス更新

```ruby
# これはあまりよろしくない
resources :posts do
  collection do
    put :update_status
  end
end

# こうするとupdateのリソースが使えて、処理がわかりやすい
resources :posts do
  scope module: :posts do
    resource :post_statuses, only: :update
  end
end
```

メソッドが１つしかないコントローラがたくさんできちゃうよ。。。😿 なんて思った人もいるかと思います
大丈夫です。そのほうがわかりやすいので気にせずコントローラ作っちゃってください！


## if文をスッキリ書く
重要度：　★★★★★

if文は階層構造になりやすく、深くなればなるほどわかりにくくなります。
動けば良いという意識ではなく、読みやすいことを心がけるといい感じ👍

工夫はいろいろあるので、まずは以下の内容で解決できないか考えてみましょう！

#### 早期リターン

```ruby
# ↓これはイケてない
if @post.save
  redirect_to posts_path
else
  render :new
end

# ↓このように書くとスッキリ
return redirect_to post_path if @post.save

render :new
```

#### 三項演算子

```ruby
# ↓これはイケてない
if case_a
  '条件はAです'
else
  '条件はAではありません'
end

# ↓このように書くとスッキリ
case_a ? '条件はAです' : '条件はAではありません'
```
ただし三項演算子は複雑な条件分岐や、処理を書こうとすると可読性が下がるので注意が必要です:warning:
参考
https://qiita.com/lasershow/items/160c854e4256ba596ec5

#### 変数を条件ごとに設定

```ruby
# ↓は少しイケてない
if case_a
  message = '条件はAです'
else
  message = '条件はAではありません'
end

# ↓こうしたほうがDRYになる
message = if case_a
            '条件はAです'
          else
            '条件はAではありません'
          end
```


## アソシエーションが組まれたレコードの保存
重要度：　★★★★★

アソシエーションを使えば、関連するレコードの取得だけではなく、インスタンスを作成することも可能です。

例えばPostモデルのレコードとCommentモデルのレコードを同時に作成、更新するときに、片方のレコードの処理と同時にもう片方の処理もやってくれるようにします（片方だけ処理に失敗して、データ不整合になることを避ける）

```ruby
# post has_many :comments
# comment belongs_to :post

# ↓は良くない
def create
  post = Post.new(post_params)
  comment = Comment.new(comment_params)
  if post.save && comment.save # 片方だけ保存される可能性もある
    # return処理
  end

  render :new
end

# ↓のように書く
def create
  post = Post.new(post_params)
  comment = post.comments.build(comment_params)
  if post.save # postが保存されるとcommentも同時に保存される
    # return処理
  end

  render :new
end
```
1対1、1対多などアソシエーションの種類で使うメソッドが違うので、詳しくは以下の記事を参考にしてください
https://techtechmedia.com/build-method-rails/


## transaction（トランザクション）
重要度：　★★★★★

データを一度にたくさん作成または更新するときに使ったりします
（例えば、全てのユーザーに対してポイントを追加するなどの処理です）

**目的はデータの不整合を無くすためです**

ポイント追加の例でいくと、全ユーザーレコードを取得してeachでupdateアクションを適用するかと思いますが、**何らかの不具合でエラーが出るとポイントが正常に付与されたユーザーと付与されないユーザーが出てきてしまいます**
これではあとからどこで失敗したかをたどって、ポイント付与されなかったユーザーを探して更新処理を手作業でやるしかありません😨

これを防ぐには失敗したらそれまでのデータ更新をなかったことにすればOK（ロールバックと言います）で、**これを実現するのがtransactionです**

つまり

* **全部のデータ作成（または更新）が成功**
* **１件も作成（または更新）されない**

のどちらかの結果にしたいときに使えます

```ruby
def update
  ActiveRecord::Base.transaction do
    User.all.each do |user|
      user.update!(point: user.point + 100)
    end
  end
  # 成功した場合の処理はtransactionの外に書く
  # 失敗したらエラーが出る（エラーページが表示される）
  # エラーを出したくない場合はrescueを使う（ただし使い時に注意） 
end
```
例外（エラー）を吐かないとロールバックされないので、updateではなくupdate!を使います
詳しい使い方は調べてみてください
（特にrescueはむやみに使うべきではありませんのでご注意ください:warning:）

参考
https://qiita.com/huydx/items/d946970d130b7dabe7ec

## モデルメソッド
重要度：　★★★★★

モデルにメソッドを生やすことで、次のようなメリットがあります🉐

* DRYな書き方ができて、ファイルの肥大化を抑えられる
* あとでロジックを変えたいときに、変更箇所を１箇所にまとめられる
* メソッドを呼び出すときに、メソッド名からどんな処理をしてる直感的にわかる

使い方は以下のようにモデルファイルにメソッドを定義し、モデルクラスのインスタンスに対してメソッドを呼び出すイメージ

例は超簡単なメソッドなので旨味は感じにくいかもしれませんが、判定の処理が複雑になったり、計算させるようなメソッドだとありがたみが分かると思います

```ruby
class User < ApplicationRecord
  # usersテーブルにgenderカラムがあるとすると

  def male?
    gender == 'male'
  end
end

# 以下のように呼び出す
user = User.first
user.male?
# => true
```

## scope
重要度：　★★★★☆

データの絞り込みをするメソッドというイメージです🔍

コントローラ内でいちいちwhereやjoinsなどを駆使してレコードを取得していると、記述量が多くなるだけでなく、どんなデータを取得しているのか一発でわかりにくくなります



```ruby
# ↓このように書くといちいち条件を読まないといけない
users = User.where('age > ?', 19).where(gender: 'male')

# ↓検索条件をモデルに書いておけば以下のように書けて、スッキリしてわかりやすい
users = User.adult_male
```

使い方

```ruby
class User < ApplicationRecord

  scope :male, -> { where(gender: 'male') }

  # ↓のように引数も使える
  scope :age_limit, ->(limit) { where('age > ?', limit) }

  # ↓組み合わせて別の名前に
  scope :adult_male, -> { male.age_limit(19) }
end

User.male
# => 男性のユーザーが全件取得される
User.age_limit(19)
# => ２０歳以上のユーザーが全件取得される
User.adult_male
# => 成人男性のユーザーが全件取得される（成人年齢の設定を変えたいときはscopeを変更するだけ）
```

参考
https://qiita.com/ozin/items/24d1b220a002004a6351
https://pikawaka.com/rails/scope


## サービスクラス
重要度：　★★★★☆

目的はコントローラの記述量を減らして、見通しをよくするためです

コントローラにいろいろロジックを書くとファットコントローラになって見通しが悪くなります

データをごにょごにょする処理はサービスクラスに切り分けて処理すると良いです
（簡単な処理であればモデルにメソッドを生やすだけでOK）

```ruby
# ↓のように書くとわかりにくい
def create
  @post = Post.new(post_params)
  if # 条件
    # 処理1
    # 処理2
    :
    :
  end
  return redirect_to posts_path if @post.save

  render :new
end

# ↓のように書くといい感じ
def create
  @post = Post.new(post_params)
  CalcHogeService.new(post: @post).execute
  return redirect_to posts_path if @post.save

  render :new
end

# サービスクラス（例えば何かの計算）
CalcHogeService
  def initialize(post:)
    @post = post
  end

  def execute
    # 処理1
    # 処理2
    :
    :
  end
end
```
サービスクラス内では保存に成功したら`true`、失敗したら`false`を返すようにして、コントローラでは`if`で条件分けして成功・失敗パターンの処理だけ書くともっとスッキリします
いろいろ試してみてください

使い方参考
https://qiita.com/chrischris0801/items/58a12d17a440b842db02

参考
https://qiita.com/chrischris0801/items/58a12d17a440b842db02



## コントローラ継承
`before_action`をコントローラ間で共通で使いたいときなんかは継承を使うと良いです

例えばマイページ内で使う各コントローラは基本的にログインしていなければ使えないので、Mypageコントローラに`require_login`などの`before_action`を用意しておき、各コントローラには`before_action`を記述しないという感じです（不要なときだけ`skip_before_action`で発動しないようにする）

```ruby
class MypageController < ApplicationController
  before_action :require_login

  private

  def require_login
    return if user_signed_in?

    redirect_to root_path
  end
end

class UpdateEmailsController < MypageController
  # before_actionはMypageControllerのものが呼び出される

  def update
    # メール更新処理
  end
end

```


## resource（ルーティング）
重要度：　★★★★☆

indexアクションがないものはidで識別する必要がないので、resourcesを使う必要がありません
代わりにresourceを使います

resourceはresourcesと比べて次のような違いがあります

* resourceはindex以外のアクションが用意される
* URLにidが含まれなくなる

例えばマイページはcurrent_userのみしか登場しないので、resourceで問題ないです

参考
https://qiita.com/Atsushi_/items/bb22ce67d14ba1abafc5
https://qiita.com/Tamitchao/items/6f45aa6daf1412b78d10


## %記法
重要度：　★★★☆☆

`%w`や`%i`を使うと文字列やシンボルをスッキリ書けます
慣れないうちは使いにくいですが、慣れて少しでも楽しましょう

routesやbefore_actionのonlyに渡すシンボルでよく使います

```ruby
['inu', 'neko', 'kuma']
[:inu, :neko, :kuma]

# 上の2つは下の2つに書き換えられます
%w[inu neko kuma]
%i[inu neko kuma]

# よく見るやつ
# routes.rb
resources :posts only: %i[index new create]
# controller
before_action :set_post, only: %i[show edit update]
```
参考
https://qiita.com/mogulla3/items/46bb876391be07921743




## おまけ

フロントエンドでよく使うものです
知っている方からすれば当たり前ですが、僕がスクールで教わらなかった（見落としていた可能性が高い笑）ので、軽く紹介します

#### require
重要度：　★★★★☆
フォームにrequireをつけると、入力しないと送信ボタンを押せないようになります
バックエンドはバリデーションで未入力データを防いで、フロントではrequireを使って防ぐイメージです

#### data confirm
重要度：　★★★★★
送信ボタンを押したときに、確認のポップアップが表示されます
削除ボタンなんかは必須でこれをつけるべきです

例は以下のような感じです

```slim:slimファイル

= form_with model: @post, url: post_path, local: true do |f|
  .content-form
    = f.label :content
    = f.email_field :content, class: 'content-field', required: true # ← これ

  .submit
    = f.submit '送信', data: { confirm: "送信しますか？"} # ← これ
```


## 最後に

上記の全てをポートフォリオアプリに適用する必要はないかと思いますが、１度は使ってみることをオススメします

これを利用して実はまともなコード書けますアピールをしてください笑