ESLintのインストール(+VSCodeにESLintのプラグイン導入)
ESLintをJavaScript Standard Styleで利用+VSCodeにプラグイン導入したかったのですが、思ったより手間取ってしまったのでまとめておきます!
【目次】
ESLintとは
JSの静的検証ツールです。コードのバグや、コロン・スペースなどのスタイルをルールに従って検証します。
VSCodeのプラグインを導入
事前にこちらを導入しておきます。
インストール方法
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になります。
また「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
で自動修正してくれます。
上記のファイルを実行した結果です↓
$ 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
以上になります。
参考
MacにNode.jsをインストール
※nodebrewを使用
【補足】nvmかnodebrewか
Node.jsのバージョン管理ツールに関して、今回はnvmを利用しましたが、いろんなサイトで「近年はnodebrewがよく使われている」等見かけました。
気になって2つを調べてみたところ、nvmのが人気なようでした。
rbenv globalで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になります。
下記はパスするテストを実行した結果です。
そして下記は、期待値と実際の値が異なるコードを書き、失敗するテストを実行した結果です。
他にも便利な機能たくさんあるようです。
参考
TDDについて
自動テストのプラクティスが始まりました。その中でTDDについて学習したのでまとめてみました。
TDDとは何か
テスト駆動開発のことです。テストファーストで開発を進め、コードの追加や変更を行い、合わせてリファクタリングによる設計改善を行います。
TDDの流れ
テストリストとして仕様を整理する
リストに基づき、これから書くコードに対するテストを書き、失敗することを確認する(RED)
テストを通すための最低限のコードを追加して、テスト結果を失敗→成功にする(GREEN)
1~3を繰り返してコードが増えてきたら適宜リファクタリングを行う
目的
すばやいFBの確保
実装が問題なく上手くいっているかを確認できます。テストが失敗することで、テストが動いていることを確認でき、テストが成功すれば、追加したコードが正しく実装されていることが確認できます。
コード量が増えてきた際には都度リファクタリングを行い、テストが成功することをチェックすることでバグや副作用の混入を防止します。
設計改善
テストファーストにより、実行フローなどの細部に注意を奪われる前にインターフェースや実装のふるまいの適切性について検討できるようになります。結果的に、保守性に優れたインターフェースの実装や仕様の抜け漏れの検出ができるようになります。
ユニットテストの確保
回帰テストとして継続的インテグレーションに組み込むと、テストファーストやリファクタリングの起点となるテスト成功状態を維持しやすくなります。またテストコードを追加することで、網羅的なバグ出しや機能的な保証を行うユニットテストを容易に得ることができます。(が、純粋なTDDにおけるユニットテストは目的が異なるため、そのままの転用はできないので注意です。)
このユニットテストは、製品コードの仕様やふるまいをわかりやすく示しているため、テストコード自体が一種の仕様書になります。
ユニットテストとは
クラスやメソッドを対象とした、システム内の最小部品のテストのことです。
テストは自動化することで、複数のテストケースを漏れなく確実に実行できるようになります。
TDD三原則
失敗するテストより先にコードを書かないこと。
テストケースのコンパイルが通り、適切に「失敗」するまでは、次のテストケースを書かないこと。
全テストケースが成功するまで、次のコードを書かないこと。
参考
DHH流 コントローラーを分割する
DHHについて
DHHはどのようにRailsのコントローラを書くのかを参考に、コントローラーの書き方を変更することにしました。
自分の作ったコントローラの状態を悔やむのは決まって、作ったコントローラの数が少なすぎた時です。多くの処理を任せようとしすぎてしまうんです。
これを実行するのに私が用いているヒューリスティクスはこうです。コントローラが元々持っているRESTアクションやデフォルトの5つの機能にはないメソッドを付け加えたいと思ったら、いつだって新しいコントローラを作る。それだけでいいのです。
ポイント
コントローラーを分割する
今回以下のようなUsersController
を分割することにしました。
class UsersController < ApplicationController def index end def show end def following end def followers end end
デフォルトアクション(CURD)以外のfollowing
とfollowers
はこのように分割できます。↓
# 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
になるので、そちらを指定しています。
名前空間について
名前空間は以下のような構成になっています。
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をチェック
Rails ransackとjp_prefectureを利用して都道府県の絞り込み検索を行う
Railsのサービスにransackを利用して検索機能を実装しました。
具体的には、イベント一覧から希望のイベントを検索できるように、 フリーワード検索+jp_prefecture
で都道府県をセレクトして絞り込みできるようにしました。
作成する物
こんなものができました!
手順
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の負荷が増えてしまう
都道府県のセレクトボックスなので完全一致の方が適切
だということで訂正しました。
より適切なコードを書かないと、別の人が後から見たときに解釈の余地が増えてしまい読み取りに誤解が生じてしまう可能性があるので気をつけたいです。