LoginSignup
165
150

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-11-23

はじめに

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

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

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

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

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

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

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

用意されたリソースだけを使ったルーティング

重要度: ★★★★★

ルーティングは基本的に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アクションでステータス更新

# これはあまりよろしくない
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

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

if文をスッキリ書く

重要度: ★★★★★

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

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

早期リターン

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

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

render :new

三項演算子

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

# ↓このように書くとスッキリ
case_a ? '条件はAです' : '条件はAではありません'

ただし三項演算子は複雑な条件分岐や、処理を書こうとすると可読性が下がるので注意が必要です:warning:
参考
https://qiita.com/lasershow/items/160c854e4256ba596ec5

変数を条件ごとに設定

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

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

アソシエーションが組まれたレコードの保存

重要度: ★★★★★

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

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

# 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です

つまり

  • 全部のデータ作成(または更新)が成功
  • 1件も作成(または更新)されない

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

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

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

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

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

  def male?
    gender == 'male'
  end
end

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

scope

重要度: ★★★★☆

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

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

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

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

使い方

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)
# => 20歳以上のユーザーが全件取得される
User.adult_male
# => 成人男性のユーザーが全件取得される(成人年齢の設定を変えたいときはscopeを変更するだけ)

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

サービスクラス

重要度: ★★★★☆

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

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

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

# ↓のように書くとわかりにくい
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で発動しないようにする)

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に渡すシンボルでよく使います

['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ファイル

= 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: "送信しますか?"} # ← これ

最後に

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

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

165
150
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
165
150