コピペコードで快適生活

明日使えるソースを自分のために

prependでクラスメソッドを拡張する

singleton_classにprependすればOK。

class Hoge
  def self.hogehoge(*args)
    puts 'hogehoge'
  end
end

Hoge.hogehoge
# => hogehoge

Hoge.singleton_class.prepend Module.new {
  def hogehoge(*args)
    super(*args)
    puts 'extended hogehoge'
  end
}

Hoge.hogehoge
# => hogehoge
# => extended hogehoge

# 継承ツリー
Hoge.ancestors
# => [Hoge, Object, Kernel, BasicObject]
Hoge.singleton_class.ancestors
# => [#<Module:0x007fc15e090f70>, #<Class:Hoge>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

class SomeClass
  class << self
    def some_classmethod
      "Hello"
    end
  end
end

SomeClass.some_classmethod
#=> Hello

singleton_class(特異クラス)とは、
すべてのオブジェクトが持つ自分だけの隠しクラスのこと。
インスタンスに独自に定義したメソッド(特異メソッド)は、
この特異クラスに定義しているという扱いらしい。

参考
https://qiita.com/ponoda/items/bfcf5533b532a6d32111
https://allabout.co.jp/gm/gc/453836/#note1

既存RailsアプリにRspecを入れる

既存RailsアプリにRspecを入れたときのメモ。

インストール

Gemfileに追記

group :development, :test do
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  gem 'database_cleaner'
end

RSpecの設定
必要なファイルが作成される。

bundle install
bundle exec rails generate rspec:install

設定をする

config/application.rb

# config/application.rb
config.generators do |g|
  # 不要なファイルを生成しない
  g.test_framework :rspec,
    fixtures: true,
    view_specs: false,
    helper_specs: false,
    routing_specs: false,
    controller_specs: true,
    request_specs: false

  # テストデータのパス設定
  g.fixture_replacement :factory_bot, dir: "spec/factories"
end

spec/rails_helper.rb

# spec/rails_helper.rb
RSpec.configure do |config|
  # 略

  # user = FactoryBot.create(:user) -> user = create(:user) とかけるようにする
  config.include FactoryBot::Syntax::Methods

  # Time.zone.nowを、スライドできるようにする
  config.include ActiveSupport::Testing::TimeHelpers
end

spec/spec_helper.rb

# spec/spec_helper.rb
require 'database_cleaner'
# 略
RSpec.configure do |config|
  # 略

  # テスト後にDBのデータを消す設定
  config.before(:suite) do
    DatabaseCleaner[:active_record].strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end
  
  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run
    end
  end

  # 略
end

テストデータ/テストコードを書く

development環境のDB構造をtest環境へクローンする

bundle exec rake db:test:clone

テストデータを作る

#  spec/factories/my_app/subscription.rb
FactoryBot.define do
  # class: でどのクラスのインスタンスを作るか指定できる
  factory :subscription, class: ::MyApp::Subscription do
    user_id 1
    course_id 1
    status nil

    # 引数で与えられた値で属性を変えることができる
    trait :trial do
      status :trial
    end

    trait :contracted do
      status :contracted
    end

    # 関連テーブルも一緒に作る場合
    trait :with_entitlements do
      after(:build) do |subscription|
        subscription.entitlements << FactoryBot.build(:entitlement__svod)
      end
    end
  end
end

テストコードを書く

# spec/models/my_app/subscription_spec.rb
require 'rails_helper'

describe ::MyApp::Subscription do
  before do
    # createはDB保存された状態のインスタンスを作る
    # beforeで定義した変数はインスタンス変数にしないと内部で参照できない
    @user = create(:user)
  end

  describe 'お試し契約する (テスト対象を書く)' do
    context '契約可能期間のとき (条件を書く)' do
      before do
        # 引数つきで属性を変える / buildeだとDB保存はしない
        @subscription = build(:subscription, :trial, :with_entitlements)
      end
      it '保存できる (期待する結果を書く)' do 
        result = @subscription.save
        expect(result).to be_truthy
        # expect(result).to eq true でもOK
      end
    end

    context '契約可能でない期間のとき' do
      before do
        @subscription = build(:subscription, :trial, :with_entitlements)
      end
      # 時間移動はaroundでも書ける
      # around do |e|
      #  travel_to('2016-2-29 10:00'.to_time){ e.run }
      # end
      it '保存できない' do 
        # このブロック内でTime.zone.nowが1年後になる
        travel 1.year
        result = @subscription.save
        expect(result).to be_falsey
        # travel_back すると Time.zone.nowが戻る
      end
    end
  end
end

実行する

# ファイル指定
bundle exec rspec spec/models/my_app/subscription_spec.rb

# ディレクトリ以下実行
bundle exec rspec spec/

nslookupメモ

使い方

nslookup www.google.com
Server:		192.168.135.1    # 問い合わせたDNSサーバのIP(LAN内のDNSサーバ)
Address:	192.168.135.1#53 # 上記 + ポート番号

Non-authoritative answer:    # キャッシュDNSサーバがレスしたの意味
Name:	www.google.com
Address: 216.58.197.196      # ドメインに紐づくIPアドレス

# ----------
nslookup www.google.com 8.8.8.8  # 問い合わせるDNSサーバを指定することも可能

オプション

-type
タイプ
表示内容をmx(MXレコード、mail exchange)、ns(name server)、soa(start of authority)で指定する

-port
番号
使用するポート番号を指定する(デフォルトは53)

-timeout
秒数
タイムアウトの値を設定する

-debug
問い合わせの内容を表示する

Dockerの基本的な使い方メモ

Dockerを触ることになったので、基本的な使い方をメモしておく。

用語

イメージ: コンテナのベースとなるファイル群
コンテナ: イメージから作成されたインスタンス

pullできるdockerのイメージはここから確認できる
https://hub.docker.com/explore/

基本的なコマンド

# イメージの取得
docker pull keymetrics/pm2

# イメージの確認
docker images

# イメージからコンテナを作成
# 末尾のコマンドを指定すると起動時に実行される。
# 指定しないとDockerfileで指定したCMDが実行される。
docker run --name [コンテナ名] -d [イメージID] /sbin/init

# コンテナにログイン(exitでログアウト)
docker exec -it [コンテナ名] /bin/sh

# コンテナの一覧
docker ps -a

# コンテナを起動する
docker start [コンテナ名]

# コンテナを止める
docker stop [コンテナ名]

# コンテナを削除する(事前にstopしておく必要がある)
docker rm [コンテナ名]

# イメージの削除
docker rmi [イメージID]
docker rmi -f [イメージID] # 強制削除

こちらで勉強させていただきました。
https://qiita.com/butada/items/3e6cd338cb703eef64b4

Dockerfile作る

↓みたいな感じでDockerfileを作る

FROM keymetrics/pm2:8-alpine        # 使用するイメージ

ARG app_dir=/usr/src/app            # 変数の定義
WORKDIR ${app_dir}                  # 以下のコマンドをこのディレクトリで実行する
ENV DEBIAN_FRONTEND noninteractive  # 環境変数の設定 入力待ちでブロックしなくなる
ENV TZ JST-9                        # 環境の設定 タイムゾーンをJSTにする
RUN apk add --update perl           # perlのインストール(Alpine Linuxのコマンド)

# COPYでローカルのファイルをコンテナへコピー
# RUNでシェルを実行
COPY ./package.json .
COPY ./yarn.lock .
COPY ./tools ./tools
COPY ./.npmrc .
COPY ./public ./public
COPY ./ecosystem.config.js ./ecosystem.config.js
RUN touch ./public/healthCheck.html

RUN yarn install --production --no-progress
COPY build/public/assets/ public/assets
COPY build/server.js .
COPY build/assets.json .

# 以降の実行をnodeユーザで行う
# CMDはコンテナ実行時のコマンド。Dockerfileの中では1度だけ指定できる。ここではpm2を起動している。
USER node
CMD [ "pm2-runtime", "start", "ecosystem.config.js", "--env", "production", "-i", "max"]

Dockerfileのあるディレクトリで実行することで、記載された内容に基づいてイメージを作る。

docker build .

他メモ

## --rmオプションつきで起動して終了するとコンテナ破棄する
docker run --rm -t -i ubuntu /bin/bash

# ポートフォワード
# -pオプションで、外部からアクセスされるポート番号:コンテナ側のポート番号を指定
# -dオプションはバックグラウンドで実行
$ docker run --name some-nginx -d -p 8080:80 nginx

参考
http://docs.docker.jp/engine/reference/builder.html#

PostgreSQLのEXPLAINについて

PostgreSQLで重たいクエリの調査したときのメモ。

EXPLAINについて

EXPLAINは問い合わせプランを表示するコマンド。

PosgreSQLではSQL実行前に、プランナで問い合わせの実行コストを見積もり、
最小コストの問い合わせプランを算出する(問い合わせの最適化)。
問い合わせプランとは、
・どの方式でテーブルを検索するか
・複数のテーブルがある場合はどの順序や結合方式で検索するか
などを記述したもの。

実行してみる

EXPLAINと先頭につけることでコストが表示される。
cost=初期コスト..トータルコスト の意味。トータルコストの値が重要。
シーケンシャルI/Oで1ページ読み込みコストを1.0とした場合の相対値として表示。

EXPLAIN SELECT * FROM articles ORDER BY articles.id DESC NULLS LAST;
                                              QUERY PLAN
------------------------------------------------------------------------------------------------------
 Gather Merge  (cost=12167.24..13390.38 rows=10636 width=1588)
   Workers Planned: 1
   ->  Sort  (cost=11167.23..11193.82 rows=10636 width=1588)
         Sort Key: id DESC NULLS LAST
         ->  Parallel Seq Scan on articles  (cost=0.00..3109.36 rows=10636 width=1588)

ANALYZE で 実際の実行時間を表示できる 。

(actual time=158.749..188.486 rows=18081 loops=1)

loopsは処理の繰り返し回数。timeは繰り返し全体の時間を表す。

EXPLAIN ANALYZE SELECT * FROM articles ORDER BY articles.id DESC NULLS LAST;
                                                                     QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------
 Gather Merge  (cost=12167.24..13390.38 rows=10636 width=1588) (actual time=158.749..188.486 rows=18081 loops=1)
   Workers Planned: 1
   Workers Launched: 1
   ->  Sort  (cost=11167.23..11193.82 rows=10636 width=1588) (actual time=144.924..153.350 rows=9040 loops=2)
         Sort Key: id DESC NULLS LAST
         Sort Method: external merge  Disk: 12256kB
         ->  Parallel Seq Scan on articles  (cost=0.00..3109.36 rows=10636 width=1588) (actual time=0.049..40.413 rows=9040 loops=2)

EXPLAIN演算子について

テーブルスキャン

Seq scan
インデックスを使わず。全件検索。

Index scan
インデックスを使用してスキャン。

Bitmap scan
ビットマップを使用してスキャン。
ORのときにに有効。インデックスを使って検索した結果をビットマップとして保持し、ビットマップ同士でOR演算する方式。

結合

Nested Loop
ネステッドループ結合を行う。
外部テーブルを1レコードずつ取り出し、その都度内部テーブルの全レコードとマッチングして結合していく方式。
データが小さい場合に向いている。

Merge join
ソート・マージ結合を行う。
事前に両方のテーブルを結合キーでソートし、両方のテーブルを先頭からマッチングしていく方式。→ テーブルを1回調べればよいので、テーブルの操作回数が減る。
データ量が多い場合に向いている。

Hash Join
ハッシュ結合を行う。
事前に内側テーブルのハッシュ表をメモリに読み込み、外側テーブルとハッシュ表を突き合わせて結合。→ メモリ内のハッシュ表の検索は高速。
ソートメモリが十分にある場合に向いている。

制御

・set enable_hashjoin to off を実行すると、プランナはマージ結合を選択する。
・さらに set enable_mergejoin to off を実行すると、プランナはネスト化ループ結合を選択する。

こちらのスライドで勉強させていただきました。
https://www.slideshare.net/MikiShimogai/postgre-sql-explain

http://d.hatena.ne.jp/tgk/20090114/1231927095

sidekiq-unique-jobsのunique値ごとの挙動

ちょっと古いけど、sidekiq-unique-jobs v4.0.8のunique値ごとの挙動を確認したのでメモしておく。

GitHub - mhenrixon/sidekiq-unique-jobs at v4.0.8

while_executing

同じargsは実行中にならない。
同じargsを実行しようとしたらworkerは待ちになる。後続を処理せずに待つ。
同じargsのキューはされる

until_and_while_executing

同じargsは実行中にならない。
同じargsを実行しようとしたらworkerは待ちになる。後続を処理せずに待つ。
同じargsが待機状態になっているときはキューされない。

until_executed

同じargsは実行中にならない。
同じargsを実行しようとしたらworkerは待ちになる。後続を処理せずに待つ。
同じargsが待機状態、または実行中のときはキューされない。