RubyのワンライナーHTTPサーバ
さくっとアクセス確認したいときなどに。
ruby -rwebrick -e 'WEBrick::HTTPServer.new(:DocumentRoot => "./", :Port => 80).start'
ES2015のコードをBabel+Jestでテストする
まだ試したことがなかったので、やり方をメモ。
ライブラリのインストール
# bableのインストール npm install --save-dev @babel/core @babel/cli @babel/preset-env # jestのインストール # babel-jestも一緒にインストールされる npm install --save-dev jest # importはNode.jsで使えないため # requireに変換するライブラリをインストール npm install --save-dev @babel/plugin-transform-modules-commonjs
babelの設定
babel.config.js
module.exports = { presets: [ [ "@babel/preset-env" ] ], env: { test: { // テスト環境でimport->require変換を有効にする plugins: [ "transform-es2015-modules-commonjs", ], }, }, };
jestの設定
jestの設定ファイル(jest.config.js)作成
npx jest --init
jest.config.jsは初期状態のままでOK。
設定詳細 https://jestjs.io/docs/ja/configuration
scriptの追加
package.json
"scripts": { "test": "jest", "test:w": "jest --watch", "test:coverage": "jest --coverage" },
テストを書いてみる
app/models/user.js
class User { constructor(attrs = {}) { this.firstName = attrs.firstName; this.lastName = attrs.lastName; } getFullName() { return `${this.firstName} ${this.lastName}`; } } export default User;
test/models/user.test.js
import User from '../../app/models/user'; describe('getFullName', () => { it('firstName + lastName の値が返される', () => { let user = new User({firstName: 'taro', lastName: 'yamada'}); let fullName = user.getFullName(); expect(fullName).toEqual('taro yamada'); }); });
テスト実行
# ファイルを指定して実行 npm run test test/models/user.test.js # テスト後にカバレッジ率を表示 npm run test:coverage test/models/user.test.js
メモ
しばらくbabelから離れている間に色々変わっていた。
- babelが7系からパッケージ名が @bable/xxx になっていた。
- babelの設定ファイルが babel.config.js になっていた。
- babelrcは個別設定用という位置づけに変わっていた。
- preset-2015が不要になり、preset-envが設定に応じて適切なライブラリを選択してくれるようになっていた。
参考にさせていただきました
Babel + Jest で JavaScript のテストをする - かもメモ
シェルでヒアドキュメントを使う
そういればやり方知らなかったのでメモ。
# これで標準出力できる。 cat << EOS hoge fuga piyo EOS # ヒアドキュメント内で変数展開できる。 # 標準出力になるので変数に代入したいときはバッククォート使う。 params=`cat << EOS { "login_id": "${LOGIN_ID}", "password": "${PASSWORD}" } EOS ` res=`curl -X POST \ -H "Content-Type: application/json; charset=utf-8" \ -H "Authorization: ${ACCESS_TOKEN}" \ -d "${params}" "${API_HOST}/api/v1/users/auth"`
Javascriptのオブジェクト指向について
Javascript書くときに雰囲気でオブジェクト指向してたので復習。
function構文使う
// 関数オブジェクトはnew演算子でインスタンスを作ることができる。 // インスタンスは、this.xxxで定義したプロパティにアクセスできる。 const Human = function(name){ this.name = name; this.say = function() { return "My name is " + this.name; } }; const human = new Human('taro'); human.say(); // -> My name is taro // 関数オブジェクトのprototype代入したオブジェクトのプロパティは、 // 生成したインスタンス同士で"共有"されて使われる。 const prototypeObject = { type: 'Human', toString: function() { return this.type + ': ' + this.name; } } Human.prototype = prototypeObject; const jiro = new Human('jiro'); jiro.toString(); // -> Human: jiro const hanako = new Human('hanako'); hanako.toString(); // -> Human: hanako // 共有されているので書き換えると、すべてのインスタンスに影響がある。 Human.prototype.type = 'Homo sapiens'; jiro.toString(); // -> "Homo sapiens: jiro" hanako.toString(); // -> "Homo sapiens: hanako" // prototypeを使ってErrorオブジェクトを継承をする const MyError = function(msg) { this.message = msg || 'Exception occured'; this.name = 'MyError'; }; MyError.prototype = new Error(); e = new MyError(); e.toString(); // -> "MyError: Exception occured"
class構文使う
classはプロトタイプベース継承の糖衣構文。ECMAScript 2015 で導入。
今まで出来ていたことを、class構文を使っても書けるようになりましたよ的なもの。
// class構文を使って関数オブジェクトを定義。 // インスタンスの作成/プロパティへのアクセスは同じ。 class HumanKlass { constructor(name) { this.name = name; } say() { return "My name is " + this.name; } } tom = new HumanKlass('tom'); tom.say(); // -> My name is tom // extendsを構文を使ってErrorオブジェクトを継承 class MyErrorKlass extends Error { constructor(msg) { super(); this.message = msg || 'Exception occured'; this.name = 'MyError'; } } ek = new MyErrorKlass(); ek.toString(); // -> "MyError: Exception occured" MyErrorKlass.prototype // -> Errorのインスタンスが入っている。
db:migrateで巨大なテーブルへadd_column+default値設定をする
Rails + PostgreSQL環境での話。
数千万行あるような巨大なテーブルに対して、add_column+default設定をまとめて設定すると、サービスを止めてしまうほどに長時間テーブルロックかかってしまう。AccessExclusiveLockなのでSELECTも通らない。
原因は、ALTER_TABLEのテーブルロックかかった中で、カラム追加+デフォルト設定に加えて全レコードに対してUPDATE(デフォルト値で上書き)が走るため。
こんな書き方すると発生する。
class AddXxxIdToBigRecords < ActiveRecord::Migration def change add_column :big_records, :xxx_id, :integer, null: false, default: 0 end end
対応方法としては下記となる。
ただ、テーブルサイズがでかいので、1行ごとUPDATEするのに相当の時間がかかる。
class AddXxxIdToBigRecords < ActiveRecord::Migration # まずこれを設定しないと、upメソッド内全部にトランザクションかかってしまう。 disable_ddl_transaction! def up # 最初にカラムを足す。AccessExclusiveLockかかるけど一瞬で終わる。 # (MySQLと違って一瞬で終わる。) add_column :big_records, :xxx_id, :integer # 次にデフォルト値を設定する。AccessExclusiveLockかかるけど一瞬で終わる。 # カラムの値は更新されない。 change_column :big_records, :xxx_id, :integer, default: true # 全レコードに対してUPDATEを実行する update_all_with_default_value # NOT NULLのフタをする change_column :big_records, :xxx_id, :integer, default: true, null: false end def down remove_column :big_records, :xxx_id end # # 全レコードにたいして一つずつUPDATEをかけていく。 # Rails5以上であれば、 # in_batchedとupdate_allで複数行をまとめて更新かけたほうがいいかも。 # def update_all_with_default_value total = BigRecord.count return true if total == 0 start_f = Time.zone.now.to_f BigRecord.find_each.with_index do |record, i| n = i + 1 record.update_columns(xxx_id: 0) if n % 1000 == 0 || n == total STDOUT.puts "-- update_all_with_default_value (#{i+1} / #{total})" end end end_f = Time.zone.now.to_f STDOUT.puts " -> #{end_f - start_f}s" return true end end
Grape+Rspec環境でAPIをテストする
既存RailsアプリにRspecを入れる - コピペコードで快適生活
の続き。
まず、JSONのテストを簡単にするgemを入れる。
gem 'json_expressions'
テストコードはこんな感じで書ける。
require 'rails_helper' require 'json_expressions/rspec' describe "GET api/orders/:order_ids" do context '存在しないIDを指定するとき' do it '空の配列が返る' do order_ids = "aaaa,bbbb" get "/api/v1/orders/#{order_ids}" pattern = { orders: [], total_count: 0 } expect(response.status).to eq 200 expect(response.body).to match_json_expression(pattern) end end end describe 'PATCH api/orders/:order_id/cancel' do context '存在しないIDを指定するとき' do it '204が返る' do order_id = "aaaa" params = {xxx: 2} patch "/api/v1/orders/#{order_id}/cancel", params expect(response.status).to eq 204 end end end
capistranoでデプロイされているブランチのコミットハッシュを確認する
cat ${PATH}/repo/refs/heads/${BRANCH}