Chiroru's Diary

日々の学びをちょこちょこメモしていきます

ESLintのインストール(+VSCodeにESLintのプラグイン導入)

ESLintをJavaScript Standard Styleで利用+VSCodeプラグイン導入したかったのですが、思ったより手間取ってしまったのでまとめておきます!

【目次】

ESLintとは

JSの静的検証ツールです。コードのバグや、コロン・スペースなどのスタイルをルールに従って検証します。

VSCodeプラグインを導入

事前にこちらを導入しておきます。

marketplace.visualstudio.com

参考:VS CodeにESLintを設定する

インストール方法

package.json作成

まず$ npm initでpackage.jsonを作成します。
質問がいくつかされますが、ここはenterでOK。

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (eslint_test) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to /Users/name/xxx/eslint_test/package.json:


// package.json↓
{
  "name": "eslint_test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes)

パッケージインストール

$ npm install eslint --save-devでESLintパッケージをインストールします。

$ npm install eslint --save-dev
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN eslint_test@1.0.0 No description
npm WARN eslint_test@1.0.0 No repository field.

+ eslint@7.8.1
…


// インストール確認
$ npm list --depth=0 
eslint_test@1.0.0 /Users/name/xxx/eslint_test
└── eslint@7.8.1

この時--save-dev(-D)オプションをつけて実行することで、開発時のみ使用することを示すpackage.json の「devDependencies 一覧」に追加されます。

 // package.json
{
  "name": "eslint_test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "eslint": "^7.8.1"
  }
}

また、パッケージをインストールするとnode_modules というディレクトリが自動的生成され、その中にパッケージに関連するファイルが格納されます。

.eslintrc.json作成

$ npx eslint --initで.eslintrc.jsonを作成します。
質問への回答はプロジェクト内容により異なりますが、今回は以下のようにしました。

ルールはJavaScript Standard Styleになります。

f:id:chiroru_memo:20200904135723p:plain

また「Would you like to install them now with npm? · No / Yes」をYesにすると以下の必要なファイルをその場でダウンロードすることができます。

(私はこの必要なファイルのインストールを行わなかったため、最初eslintコマンドが実行できませんでした・・)

✔ Would you like to install them now with npm? · No / Yes
Installing eslint-config-standard@latest, eslint@>=6.2.2, eslint-plugin-import@>=2.18.0, eslint-plugin-node@>=9.1.0, eslint-plugin-promise@>=4.2.1, eslint-plugin-standard@>=4.0.0
npm WARN eslint_test@1.0.0 No description
npm WARN eslint_test@1.0.0 No repository field.

+ eslint-config-standard@14.1.1
+ eslint-plugin-promise@4.2.1
+ eslint-plugin-node@11.1.0
+ eslint-plugin-import@2.22.0
+ eslint-plugin-standard@4.0.1
+ eslint@7.8.1
added 68 packages from 48 contributors, updated 1 package and audited 177 packages in 5.667s

実行する

$ npx eslint ファイル名で実行し、$ npx eslint ファイル名 --fixで自動修正してくれます。

f:id:chiroru_memo:20200904141757p:plain

上記のファイルを実行した結果です↓

$ npx eslint test.js

/Users/name/xxx/eslint_test/test.js
  1:13  error  'aaa' is not defined  no-undef
  1:17  error  Extra semicolon       semi

✖ 2 problems (2 errors, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

$ npx eslint test.js --fix

/Users/name/xxx/eslint_test/test.js
  1:13  error  'aaa' is not defined  no-undef

✖ 1 problem (1 error, 0 warnings)

以上になります!

参考

Homebrewでnvm・Nod.jsをインストール

Homebrewでnvmをインストールし、nvmでNodejsをインストールしたのでその方法を書き残しておきます。

【目次】

各種の説明

Homebrew

macOSのパッケージ管理システムです。
以下のようにbrew~で命令することができます。

# インストール
$ brew install <パッケージ名>

# アンインストール
$ brew remove <パッケージ名>

# インストールの整合性チェック
$ brew doctor

# バージョン確認
$ brew -v

その他のコマンドについてはこちらを参照

nvm

Node.jsのバージョンを管理するものです。

# インストール確認
$ command -v nvm
nvm

# Node.jsの最新版をインストール
$ nvm install node

# 長期サポートの安定版インストール
$ nvm install --lts

# インストール済みのNode.jsバージョンを確認
$ nvm ls

# Node.jsのデフォルトのバージョン切り替え
$ nvm alias default v x.x.x

Node.js

JavaScriptの実行環境の一つであり、サーバーサイドのJavaScript環境になります。処理はもちろんJavaScriptを使用します。

インストール方法

まずはhomebrewをインストールし、brewを利用してnvmをインストールします。(homebewは事前にインストール済みだったのでパスしています。)

$ brew -v
Homebrew 2.4.2
Homebrew/homebrew-core (git revision 29f3d; last commit 2020-06-30)

$ brew install nvm
You should create NVM's working directory if it doesn't exist:

  mkdir ~/.nvm

Add the following to /Users/name/.bash_profile or your desired shell
configuration file:

  export NVM_DIR="$HOME/.nvm"
  [ -s "/usr/local/opt/nvm/nvm.sh" ] && . "/usr/local/opt/nvm/nvm.sh"  # This loads nvm
  [ -s "/usr/local/opt/nvm/etc/bash_completion.d/nvm" ] && . "/usr/local/opt/nvm/etc/bash_completion.d/nvm"  # This loads nvm bash_completion

表示されたように、ディレクトリの作成と.bash_profileに追記して、シェルスクリプトを実行します。

$ mkdir ~/.nvm

$ vim ~/.bash_profile

# 上記を記載

# bash_profileを実行してパスを通す
$ . ~/.bash_profile

$ nvm --version
0.35.3

$ command -v nvm
nvm

インストールできているのにnvm: command not foundが出た場合は、シェルスクリプトを実行しているか確認しましょう。(私は追記して実行を忘れていました💦)

$ brew list
docbook-xsl       heroku-node       libidn2         nvm         python            wget

$ nvm --version
-bash: nvm: command not found

# $ source~/.bash_profile
$ . ~/.bash_profile

$ nvm --version
0.35.3

nvmがインストールできたら、Node.jsをインストールしていきます。

# 最新版のインストール
$ nvm install node

# 安定バージョンのインストール
$ nvm install --lts

# インストール確認
$ nvm ls
       v12.18.3
->      v14.8.0
         system
default -> v14.8.0
node -> stable (-> v14.8.0) (default)
stable -> 14.8 (-> v14.8.0) (default)
iojs -> N/A (default)
unstable -> N/A (default)
lts/* -> lts/erbium (-> v12.18.3)
lts/argon -> v4.9.1 (-> N/A)
lts/boron -> v6.17.1 (-> N/A)
lts/carbon -> v8.17.0 (-> N/A)
lts/dubnium -> v10.22.0 (-> N/A)
lts/erbium -> v12.18.3

以上になります。

参考

【補足】nvmかnodebrewか

Node.jsのバージョン管理ツールに関して、今回はnvmを利用しましたが、いろんなサイトで「近年はnodebrewがよく使われている」等見かけました。

気になって2つを調べてみたところ、nvmのが人気なようでした。

f:id:chiroru_memo:20200826105928p:plain

rbenv globalでrubyのバージョン切替えがうまくいかなかった

【目次】

rubyのバージョン切り替えについてはこちらに記載してあります↓

rubyのバージョンを切り替える

状況

$ rbenv local <バージョン>でプロジェクトごとのバージョン切り替えはできるのに、$ rbenv global <バージョン> でのデフォルト切り替えがなぜかできませんでした。

$ rbenv global 2.7.1
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin18]

結論

誤ったファイルを読み込んでしまっていました。

それがこちら↓

$ rbenv versions  
  system  
  2.5.0  
* 2.6.3 (set by /Users/user_name/.ruby-version)  
  2.7.0    
  2.7.1  

.ruby-versionは使用するRubyのバージョンを自動で変更してくれるファイルで、バージョン指定した時に作成されるものですね。

解説

この.ruby-versionファイルがなぜか/Users/user_name配下に作られてしまっていたので$ rbenv global <バージョン> ができなかったというわけです😂

不要なのでこのファイルを削除したところ、ちゃんとglobalの変更が効くようになりました!

$ rbenv local 2.7.0
$ rbenv global 2.7.1
$ ruby -v
ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-darwin18]

$ rm .ruby-version

$ rbenv versions
  system
  2.5.0
  2.6.3
  2.7.0
* 2.7.1 (set by /Users/user_name/.rbenv/version)

感想

調べていると同じうようにバージョン切り替えがうまくいかない場合において、「パスの設定が間違っている」ことが原因であることが多いようでした。 しかし今回の私の場合は原因が違っていたため、いくら$ which rubyで参照先を確認しても問題なく/Users/user_name/.rbenv/shims/rubyとなっていました。アンインストールしないといけないかな?とかまで思っていたのですが、よく出力結果見ればわかる程度のことでした。

test-unitについて

自動テストのプラクティスで、引き続きtest-unitについて学んだのでまとめておきます。

【目次】

test-unitとは

ユニットテスト(単体テスト)を行うためのRubyのライブラリになります。 Ruby2.2.0以降、本体にバンドルされたもので、テスティングフレームワークのコア機能を提供します。 2.2.0以降は、このtest-unitとMinitestがRubyの標準テスティングフレームワークになっているようです。

が、下記の記事によればRspecやMinitestの方が、Rubyのテスティングフレームワークとして実際利用されているようでした。

参考

実際に使ってみる

Ruby標準のテスティングフレームワークで手軽にテストコードを書く方法 を参考に早速試してみます。

サンプルコード↓

require "test/unit"

class Sample
  def self.greeting
    "Hello, world!"
  end
end

class TestSample < Test::Unit::TestCase
  def test_greeting
    assert_equal "Hello, world!", Sample.greeting
  end
end

Test::Unit::TestCaseを継承したクラス(今回であればTestSample)に、test_xxxメソッド(今回であればtest_greeting)を定義すると、そのメソッドをテストすることができます。

assert_equal (期待値), (実際の値)で、期待値=実際の値になればテストはパスし、そうでなければ失敗します。 原則として、一つのメソッドに一つのassertになります。

下記はパスするテストを実行した結果です。

f:id:chiroru_memo:20200730180100p:plain
GREEN
そして下記は、期待値と実際の値が異なるコードを書き、失敗するテストを実行した結果です。
f:id:chiroru_memo:20200730180128p:plain
RED

他にも便利な機能たくさんあるようです。

参考

TDDについて

自動テストのプラクティスが始まりました。その中でTDDについて学習したのでまとめてみました。

TDDとは何か

テスト駆動開発のことです。テストファーストで開発を進め、コードの追加や変更を行い、合わせてリファクタリングによる設計改善を行います。

TDDの流れ

  1. テストリストとして仕様を整理する

  2. リストに基づき、これから書くコードに対するテストを書き、失敗することを確認する(RED)

  3. テストを通すための最低限のコードを追加して、テスト結果を失敗→成功にする(GREEN)

  4. 1~3を繰り返してコードが増えてきたら適宜リファクタリングを行う

目的

すばやいFBの確保

実装が問題なく上手くいっているかを確認できます。テストが失敗することで、テストが動いていることを確認でき、テストが成功すれば、追加したコードが正しく実装されていることが確認できます。
コード量が増えてきた際には都度リファクタリングを行い、テストが成功することをチェックすることでバグや副作用の混入を防止します。

設計改善

テストファーストにより、実行フローなどの細部に注意を奪われる前にインターフェースや実装のふるまいの適切性について検討できるようになります。結果的に、保守性に優れたインターフェースの実装や仕様の抜け漏れの検出ができるようになります。

ユニットテストの確保

回帰テストとして継続的インテグレーションに組み込むと、テストファーストリファクタリングの起点となるテスト成功状態を維持しやすくなります。またテストコードを追加することで、網羅的なバグ出しや機能的な保証を行うユニットテストを容易に得ることができます。(が、純粋なTDDにおけるユニットテストは目的が異なるため、そのままの転用はできないので注意です。)
このユニットテストは、製品コードの仕様やふるまいをわかりやすく示しているため、テストコード自体が一種の仕様書になります。

ユニットテストとは

クラスやメソッドを対象とした、システム内の最小部品のテストのことです。
テストは自動化することで、複数のテストケースを漏れなく確実に実行できるようになります。

TDD三原則

  1. 失敗するテストより先にコードを書かないこと。

  2. テストケースのコンパイルが通り、適切に「失敗」するまでは、次のテストケースを書かないこと。

  3. 全テストケースが成功するまで、次のコードを書かないこと。

参考

DHH流 コントローラーを分割する

DHHについて

DHHはどのようにRailsのコントローラを書くのかを参考に、コントローラーの書き方を変更することにしました。

自分の作ったコントローラの状態を悔やむのは決まって、作ったコントローラの数が少なすぎた時です。多くの処理を任せようとしすぎてしまうんです。

これを実行するのに私が用いているヒューリスティクスはこうです。コントローラが元々持っているRESTアクションやデフォルトの5つの機能にはないメソッドを付け加えたいと思ったら、いつだって新しいコントローラを作る。それだけでいいのです。

ポイント

  • 名前空間を利用して分割する

  • コントローラーには基本CRUD機能しか持たせない

コントローラーを分割する

今回以下のようなUsersControllerを分割することにしました。

class UsersController < ApplicationController
  def index
  end

  def show
  end

  def following
  end

  def followers
  end
end


デフォルトアクション(CURD)以外のfollowingfollowersはこのように分割できます。↓

# controllers/users_controller.rb  
class UsersController < ApplicationController
  def index
  end

  def show
  end
end
# controllers/users/followings_controller.rb
class Users::FollowingsController < ApplicationController
  def index
  end
end
# controllers/users/followers_controller.rb
class Users::FollowersController < ApplicationController
  def index
  end
end

ルーティング

今回Users::FollowingsControllerのルーティングを考えるにあたり、先にFollowingsControllerだった場合について考えます。

FollowingsController

この時の階層はcontrollers/followingsで、ルーティングは以下のようになります。

resources users do
  resources followings
end

パスは、users/:id/followingsを認識します。

Users::FollowingsController

一方今回の階層はcontrollers/users/followingsです。その時ルーティングは以下のようになります。

resources users do
  resources followings, controller: 'users/followings'
end

パスは同じく、users/:id/followingsを認識します。

controllerオプション

controller: 'users/followings' では、使用するコントローラーを指定しています。
パスはusers/:id/followingsを認識しますが、今回コントローラーはusers/followingsになるので、そちらを指定しています。

参考:Rails のルーティング

名前空間について

名前空間は以下のような構成になっています。

class Hoge
  TEXT = ...
end

Hoge::TEXT

Zeitwerk::NameError

名前空間を利用する上で不適切な箇所に(controllers配下に)ファイルを配置していたため、このようなエラーに遭遇しました。

NameError: expected file /app/~~~ to define constant Controller, but didn't

Railsガイドには、以下のように書かれています。

Railsアプリケーションで使うファイル名は、そこで定義されている定数名と一致しなければなりません。ファイル名はディレクトリ名と合わせて名前空間として振る舞います。 たとえば、app/helpers/users_helper.rbファイルではUsersHelperを定義すべきですし、app/controllers/admin/payments_controller.rbではAdmin::PaymentsControllerを定義すべきです。 Railsは、ファイル名をString#camelizeで活用するようZeitwerkを設定します。たとえば、app/controllers/users_controller.rbは以下のためにUsersControllerという定数を定義します。 "users_controller".camelize # => UsersController

具体的には以下のようなことです。

api/users_controller.rb

Api::UsersController
API::UsersController <= こっちはダメ

またこのルールにそぐわない名前をつけたければ、inflectionsに指定する方法もあるようです。

カスタマイズする必要が生じた場合(略語を追加するなど)は、config/initializers/inflections.rbをチェック

参考:定数の自動読み込みと再読み込み (Zeitwerk)

Rails ransackとjp_prefectureを利用して都道府県の絞り込み検索を行う

Railsのサービスにransackを利用して検索機能を実装しました。

具体的には、イベント一覧から希望のイベントを検索できるように、 フリーワード検索+jp_prefecture都道府県をセレクトして絞り込みできるようにしました。

作成する物

こんなものができました!

f:id:chiroru_memo:20200717130334g:plain
検索フォーム

手順

gemをインストール

検索フォームを作成するransackと、都道府県コードから県名を変換するjp_prefectureを使用します。

gem 'ransack'
gem 'jp_prefecture'

ライブラリの読み込み

# app/models/event.rb

class Event < ApplicationRecord 
  include JpPrefecture
end

DB修正

データベースにイベント開催地(県)を追加します。
この時カラムのタイプは integer か string で作成します。

今回は県名が欲しいのでstringになります。

# マイグレーションファイルの設定

class AddColumnsToEvent < ActiveRecord::Migration[6.0]
  def change
    add_column :events, :prefecture, :string  
  end
end

検索機能を追加

controllerに設定を追加します。

# app/controllers/events_controller.rb  

class EventsController < ApplicationController
  def index
    # 検索オブジェクト
    @search = Event.ransack(params[:q])
    # 検索結果
    @event = @search.result.page(params[:page]).per(30)    
  end
end

検索フォームを追加

ransackのヘルパーsearch_form_forを利用して、viewに設定を追加します。

# app/views/layouts/application.html.slim

= search_form_for @search do |f|
  .form-row.mt-3.mb-3.justify-content-md-center
    .col-md-4
      = f.search_field :title_or_body_cont, class: 'form-control'
    .col-md-2
      = f.collection_select :prefecture_eq, JpPrefecture::Prefecture.all, :name, :name, { include_blank: '開催場所' }, { class: 'custom-select' }
    .col-md-1
      = f.submit '検索', class: 'btn btn-primary'

jp_prefectureについて

上記より抜粋したこの部分は、都道府県のセレクトボックスを作成しています。

= f.collection_select :prefecture_eq, JpPrefecture::Prefecture.all, :name, :name, { include_blank: '開催場所' }, { class: 'custom-select' }

言葉に置き換えるとこのような形です。

= f.collection_select :カラム名_検索マッチャー, オブジェクトの配列, value属性の項目, 表示する項目, { include_blank: '空のオプション' }, { class: 'クラス名' }
  • collection_selectは、モデルの情報をもとにセレクトボックスを作成してくれるものです。

  • 検索マッチャー「_eq」は、完全一致検索を行います。
    この場合prefecture_eqですので、prefectureに検索文字列と完全一致するものを検索します。

  • :name(value属性の項目)がDBに送られます。
    (今回はDBにstring(name)で登録されているので、こちらもnameになっています)

  • :name(表示する項目)が表示されます。

  • include_blankは空のオプションを先頭に追加してくれるものです。

検索マッチャーについて

はじめprefecture_eq部分をprefecture_cont(like検索)と書いていたのですが、これだと

  • レコード数が増えた際にDBの負荷が増えてしまう

  • 都道府県のセレクトボックスなので完全一致の方が適切

だということで訂正しました。

より適切なコードを書かないと、別の人が後から見たときに解釈の余地が増えてしまい読み取りに誤解が生じてしまう可能性があるので気をつけたいです。

参考