Slack から「PRレビューお願いします」を抹殺する

どうも、@shutooike です。

今回は

"GitHub でレビュワーに設定して、Slack で「@reviewer PR#1 のレビューお願いします!」と連絡する"

というめんどくさい運用の Slack 側のメッセージを抹殺できる GitHub の機能 「Scheduled reminders」 を紹介します。

抹殺方法

設定画面

「Scheduled reminders」は設定した時間に自分 or チームがレビュワーにアサインされているPRのリストを Slack に通知してくれる GitHub の機能です。

この機能には「Enable real-time alerts」という設定があり、オンにすると GitHub のイベントをリアルタイムで Slack に通知できます。

僕は以下イベント

  • 自分がレビュワーに設定された時
  • チームがレビュワーに設定された時
  • 自分のPRが approved か changes requested された時
  • 自分のPRにコメントがついた時
  • 自分の参加しているスレッドにコメントがついた時
  • コメントでメンションされた時
  • 自分のPRがマージされた時

の通知が来るように設定したことで

  • 「PRレビューお願いします」
  • 「レビューしました。修正お願いします」
  • 「コメント返してます」
  • 「レビューの修正しました。再度レビューお願いします。」

Slack で行われていた上記のようなメッセージを抹殺することができました。

(詳しい設定方法は GitHub のドキュメント をご参照ください。)

おわりに

現在「PRレビューの待ち時間」がチームの課題としてあり、施策を練っているときにこの機能を見つけました。*1

Slack の GitHub アプリでは個人向けに通知をカスタマイズ出来ず、無理なのかーと諦めていましたが、GitHub 側にあったんですね。無知、恐ろしい...

【宣伝】 インゲージでは弊社のバリューでもある「成功も失敗も共有」できるエンジニアを募集してます!

詳しくは下記リンクまで!

ingage.co.jp

ではまた!

*1:「PRレビューの待ち時間」の話はまたどこかでできたらなと思います。

docker compose cpコマンドでホスト・コンテナ間のファイルコピーする

どうも、にしむらです。

docker-composeにはコンテナとのファイルコピーをするコマンドが無いので、 docker cpコマンドを使っていたのですが docker compose にはコピーコマンドcpがありました。

docker compose cp リファレンス

動作確認環境

  • Mac OS 11.6.5

  • Docker Desktop 4.8.1

コンテナからホストへのコピー

# docker compose cp [OPTIONS] SERVICE:SRC_PATH DEST_PATH

# appコンテナの /app/log/development.log をホストのカレントにコピーする
docker compose cp app:/app/log/developoment.log . 

# appコンテナの /app/log/development.log をホストのカレントの/tmp/log/にコピーする
docker compose cp app:/app/log/ ./tmp/log/

ホストからコンテナへのコピー

# docker compose cp [OPTIONS] SRC_PATH SERVICE:DEST_PATH

# カレントtmp/test.sqlをdbコンテナのルートディレクトリへコピーする
docker compose cp ./tmp/test.sql db:/

# カレントtmp/sql/をdbコンテナのルートディレクトリへコピーする
docker compsoe cp ./tmp/sql/ db:/sql/

コンテナのユーザー権限によってはパーミッションエラーになる場合があります。その場合はroot権限でコンテナに入って権限を変更する必要があります。

docker compose exec app -u root bash

インゲージではエンジニアを募集しています!

ingage.co.jp

仕事環境を作りました

こんにちは、masm11 です。

インゲージに入社して在宅勤務をするようになってから、7年になろうとしています。 仕事をするための環境をずっと作っておらず、ベッドでごろごろしながら、または ノートをベッドに置いて自分はベッドの横に座って、という姿勢でした。

去年8月に引っ越してフルリモートになってからもそんな感じだったのですが、 チューナーレス TV が人気だなどのニュースを見ていると、 そろそろ TV を捨てようか (そして仕事環境を作り上げようか)、 という気になってきました。

というわけで、今回は最近作った私の仕事環境のご紹介です。 なお、そのチューナーレス TV を買ったわけではありません。 また、今回はエンジニア的な話はほぼ皆無です。

全景

全景はこんな感じです。

仕事だけでなく趣味にも使いますが、Mac だけは会社のものなので業務専用です。

モニタ

  • DELL U2723QX
  • 27", 4K, IPS, 非光沢
  • 入力: USB-C (PDあり), HDMI, DisplayPort

使っていた TV が 32" で、その代わりのモニタをそれ以上小さくしたくは なかったのですが、27" も選択肢に入れると選択肢がわりと増えることに 気づきました。 机のサイズを考えると 27" もいいかな、と思うようになり、結局 27" にしました。

4K については、4K モニタもそろそろ流行りというか、値崩れが起き始めましたね。 以前は十数万していましたが、ようやく手が届くようになってきた感じです。

非光沢については、 以前、光沢ありの画面を見てて室内照明の反射光で目を痛めてしまったので、 もう非光沢しか使いません。 いまさらですが、目は大切にしましょう…

主な用途としては、業務, TV, ゲーム(Windows), FireTV, 家庭内サーバの console、 といった感じで多岐にわたります。

ただ、なんでか時々外付けスピーカーから音が出なくなるとか、 USB デバイスが認識されなくなるとかの症状があるんですよね・・・ そういう時はモニタの電源を入れ直したり Mac 側 USB-C ポートを 変えてみたりとかすれば回復してるのですが・・

Mac

  • MacBook Pro 13 2019
  • 会社備品

USB-C でモニタに接続しておけば、このケーブル一本で

  • 画面表示
  • Mac 本体充電
  • 外付けキーボード使用
  • 外付けマウス使用
  • 外付けカメラ使用

ができます。今、Mac にはこの USB-C 1本しかケーブルはつながってないです。 また、趣味の Windows PC を使うときにも USB-C 1本差し替えるだけです。

素晴らしいですね。

モニタが有線 LAN の口を持っていて、USB-C につながっているので、有線 LAN も いけますが、LAN ケーブルが増えてしまうので、無線で使っています。

キーボード

私はキーボードと言えば HHKB です。 ただし、これは今回買ったわけではないです。

英語配列です。

キーボード側は USB-C、反対側は USB-A で、モニタにつながっています。

趣味で Windows PC で使う時には裏の dipsw を変更して使っていたのですが、 だんだん切り替えるのが面倒になり、OS 設定でなんとかならんかと試行錯誤中です。

Mac の場合は、Karabiner-Elements で本体キーボードの Ctrl と CapsLock を 入れ替えていますが、 HHKB は元から入れ替わっているので、その入れ替え設定は Apple Internal Keyboard のみに 設定しています。

HYBRID なので Bluetooth にも対応していますが、PC 側を USB-C 一本で 全部切り替わるようにしてあるので、Bluetooth では使っていません。

マウス/マウスパッド

Mac〜レシーバは USB で、レシーバ〜マウスは無線です。

Blue LED は流行りですね。

マウス本体の電源は単三乾電池1本です。少し動かさないと、すぐに省電力モードに入り、 次に動かす時には一瞬動きません。

あと、最初に書いたとおり、この環境は趣味にも使っています。 ゲームもします。 原神をプレイしていてボタンを連打するため、ボタンが傷んできたように 思います…

最近、濃い青が好きで、マウスパッドも濃い青を選択しました。

スピーカー

  • YAMAHA SR-C20A
  • 入力は HDMI, ANALOG, OPTICAL, BLUETOOTH がいける
    今回は ANALOG でモニタから LINE 入力

今回買ったものではないです。TV のスピーカーが TV の後ろに付いているのが 気に入らなくなり、音が私に向けて出るように、と思って買いました。

この環境ではゲームもします。モニタのスピーカーでは音質が足りないだろう と思って引き続き使っています。

右ちょっと手前の赤いボタンが見えてるのはリモコンです。

カメラ

  • eMeet Nova
  • スペック: 1080p, オートフォーカス, 無指向性マイク2つ
  • レンズカバー付き
  • 使用中ランプ付き
  • USB でモニタに接続

モニタの上にちょこっと乗せるのですが、こんないい加減な設置でわりと 転げ落ちないもんなんですね…

部屋が狭いので、サイズはかなり悩みました。 幅は 80, 100, 120, 140 がありました。80 だと狭い、120 だと広すぎる、 と判断して 100 にしました。 奥行は 60, 70 があったのですが、70 欲しいけど設置場所的に大きすぎるのではと 考えて 60 にしました。

作業していて、目が画面に近すぎると感じることが時々あり、やっぱり 70 にすべきだったか? と思うこともあります。

天板下のスペースは高さ 69.5 あります。 実際に座ってみて、脚を組んでちょうどの高さでした。 引き出しがあったら脚を組めなかったです。 たぶんそうだろうと思ったので引き出しなしを選んだのですが、 でも Mac をしまうスペースが欲しかったな、とも思ってます。

テレワーク用に PC を使った業務を念頭に作られており、 ケーブルの取り回しをしっかり考えられているようです。

椅子

  • KOKUYO Monet
  • キャスター付き
  • いろいろ調整できる
  • 座面ふかふか
  • 肘掛けなし

いい椅子は5万円以上するらしいことを知ってびっくりしました。 そんな高いものだったのか... これもかなりいい椅子です。

肘掛けなしは私の好みです。

チェアマット

  • Umi
  • サイズ 130cm x 160cm
  • 厚み 4mm

椅子を1日使ってみて、フローリングにかなり凹みができていたので、急遽購入しました。 これ以上凹まないことを期待します (時々剥がして見てて、大丈夫そうではあります)。

裏面は滑り止めになっています。接着ではありません。賃貸マンションなので…

横を 130cm にして、奥行を 160cm にして使っています。 また、机が横 100cm なので、30cm 余ります。机の左寄りに座っているので、 左に 20cm、右に 10cm はみ出す感じで敷いています。

椅子のキャスターはフローリング用ですが、 このチェアマットを敷いても重くは感じないです。

テーブルタップ

  • 6口
  • 各口にスイッチあり
  • マグネットは別で購入

各口のスイッチは要らないですね… 「え? 電気来てない!?」と戸惑うデメリットが大きいです。

裏面にマグネットがなかったので別で購入しました。

HDMI セレクタ

裏にマグネットが付いてるのですが、ほとんど飾りでした… 一応付かないこともないのですが、次の日には外れてます。

総額

品名 価格 備考
モニタ ¥69,801
Mac - 会社備品
キーボード (¥35,200) 今回買ったものではない
マウス ¥2,000 概算
マウスパッド ¥1,500 概算
スピーカー (¥16,318) 今回買ったものではない
カメラ ¥3,999
¥16,720
椅子 ¥51,728
チェアマット ¥5,499
テーブルタップ ¥3,419 概算
HDMI セレクタ ¥4,380
合計 ¥159,046 ()の金額は含まない

まとめ

いやはや、16万ですか… 椅子がピンきりでした… ほんとびっくりしました。 でも、快適な環境ができました!

あと、モニタを何インチにしようが、4K にしようが、 一枚の画面に載る情報量 (文字数) は変わらないんですね。

インゲージではエンジニアを募集しています。 詳細は以下のページからお願いします!

https://ingage.co.jp/recruit/

sidekiq の優先度設定

https://sidekiq.org/assets/2018/horizontal.svg

id:kizashi1122 です。
恥ずかしながらも最近ちゃんと理解した Sidekiq の優先度設定について書きたいと思います。

結論

github.com

ここのオフィシャルをちゃんと読みましょう。

優先度と重みの設定方法

Sidekiq のキューの設定は設定ファイルに記述することができます。

:queues:
  - critical
  - default
  - low

こんな感じですね。私はこれ(↑)と、これ(↓)が同じ意味だと思っていました。

:queues:
  - [critical, 1]
  - [default, 1]
  - [low, 1]

この2つの書き方は意味が違うんです。上の方の設定については本家の方も

This means that any job in the default queue will be processed only when the critical queue is empty.

と書いています。重みを付けていない書き方の場合は、キュー内に criticaldefaultlow のジョブが混在していたとしても critical を全部捌いてから、次に default のジョブを捌くことになります。途中で critical のジョブが入ってこようものなら、まず critical を捌く。あくまで指定した順番で捌いてくれるわけです。

一方、下の方の設定は、こう説明があります。

You can get random queue priorities by declaring each queue with a weight of 1, so each queue has an equal chance of being processed:

キュー内に criticaldefaultlow のジョブが混在していたとすると、全て同じ重みで優先度を付けずに捌いてくれます。
数字の部分が重みなわけですが、当然 1 以外の数値を指定することもできます。

:queues:
  - [critical, 3]
  - [default, 2]
  - [low, 1]

とすると criticallow の3倍の重みがついているので low よりも3倍チェックしてくれることになります。完全に優先度をつけるわけじゃないけど、より高頻度で処理してほしい場合はこうすればよいわけですね。

本家ソースコードをみる

sidekiq/cli.rb at e7d154eeb5e237a6e6e591a3bfca0a8f7bce32f6 · mperham/sidekiq · GitHub

このあたりですかね。最終的には ops[:queues] を作りたいわけですが、このブログの一番上の例だと

ops[:queues] = ['critical', 'default', 'low']
ops[:strict] = true

になるようですね。このブログの真ん中の例だと、

ops[:queues] = ['critical', 'default', 'low']
ops[:strict] = false

一番下の例だと

ops[:queues] = ['critical', 'critical', 'critical', 'default', 'default', 'low']
ops[:strict] = false

となると。重みの数だけ要素が増えて、読み出し側からみると参照する機会が増えるということですね。
ソースをよく読むと、「重みがない場合」や「重みにマイナスを指定した場合」なんかも理解できます。マイナスは普通しないでしょうが重みを指定しないことはあるようです。

mastodon の設定例

github.com

これは mastodon の sidekiq の設定ですが、

:queues:
  - [default, 6]
  - [push, 4]
  - [mailers, 2]
  - [pull]
  - [scheduler]

とあります。pullscheduler には重みがありません。
ちょっと不安になりますが、 defaultpush など重みが0より大きい設定があるので ops[:strict]false になります。
重みのないキューは [nil.to_i, 1].max で 1 になります。(nil.to_i は 0 になります)

個人的には明示的に、

:queues:
  - [default, 6]
  - [push, 4]
  - [mailers, 2]
  - [pull, 1]
  - [scheduler, 1]

と書きたくなってしまいます。

要件によって、この優先度、重み設定をうまくつかいわけるとよりサービスの価値をあげることができるかもしれません。

みなさまもお試しを。

インゲージではエンジニアを募集しています。ぜひご応募を!

採用情報 – 株式会社インゲージ

最小二乗法再入門

こんにちは。ksr_cyclです。

最近こんな素敵な本に出会いまして、読み始めているのですが、数学の基礎部分がかなり記憶から薄れていた為、その中でも最小二乗法を再入門する形で勉強しました。


最小二乗法

最小二乗法とは、データの組xyに直線的な関係をあると推察出来る時、説明変数をxとし、目的変数をyとした場合、説明変数から尤もらしい直線である関数y=f(x)を求める方法です。尤もらしい直線は以下の式で求めることが出来ます。



y=Ax+B


傾きAと切片Bは、それぞれ以下の式になります。


\displaystyle{
A=\frac{Cov(x, y)}{\sigma^2_x}=\frac{\frac1n\sum_{i=1}^n(x_i-\bar{x})(y_i-\bar{y})}{\frac1n\sum_{i=1}^n(x_i-\bar{x})^2}
}


\displaystyle{
B=\bar{y} - A\bar{x}
}



数式を解説すると、


Cov : 共分散。 xの偏差とyの偏差を掛けて、その平均を取る。

\displaystyle{
Cov=\frac1n\sum_{i=1}^n(x_i-\bar{x})(y_i-\bar{y})
}


\sigma^ 2 : 分散。 xの偏差を2乗し、その平均をとる。

\displaystyle{
\sigma^ 2=\frac1n\sum_{i=1}^n(x_i-\bar{x})^2
}


 \bar{x},\bar{y} : それぞれの平均値

\displaystyle{
\bar{x}=\frac1n\sum_{i=1}^n x_i
}


最小二乗法は別名で回帰(線形回帰)と呼ばれ、今回の例では説明変数(x)が1つとなるため、単回帰分析になります。


実装


Pythonでスクラッチ実装をすると、以下のようになります。


import numpy as np
import matplotlib.pyplot as plt


# ランダムな整数20個の配列を作る
np.random.seed(42)
X = np.random.randint(1, 20, 20)
y = np.random.randint(1, 20, 20)

# 共分散
def covariance(X, y):
    X_deviation = X - X.mean()
    y_deviation = y - y.mean()
    cov = (X_deviation * y_deviation).mean()
    return cov

# 分散
def variance(X):
    var = ((X - X.mean()) ** 2).mean()
    return var

# 上の共分散、分散を使って傾きと切片を求める関数
def least_squares_method(X, y):
    A = covariance(X, y) / variance(X)
    B = y.mean() - A * X.mean()
    return A, B

# 求められた傾きと切片から最小二乗法を適用する関数
def f(X, y):
    A, B = least_squares_method(X, y)
    return A*X + B


上の関数を使い、出力をグラフにプロットしてみます。


fig = plt.figure(figsize=(10,8))
plt.style.use('seaborn')
plt.scatter(X, y, c='r', label='(xi,yi)')
plt.plot(X, f(X, y), 'b', label='y=f(x)')
plt.xlabel('X')
plt.ylabel('y')
plt.legend(loc='upper left', bbox_to_anchor=(1,1), fontsize=16)
plt.show()



ランダムに生成したデータの為、 (x_i,y_i)に直線的な関係は感じられないものになっていますが、それに対して直線を当てはめられています。


ちなみにScikit-learnを使って実装すると以下のようになります。


from sklearn.linear_model import LinearRegression

# modelに入力可能な二次元配列にする
X = np.array([[x] for x in X])

model = LinearRegression()
model.fit(X, y)

preds = model.predict(X)


同様にグラフにプロットします。


fig = plt.figure(figsize=(10,8))
plt.style.use('seaborn')
plt.scatter(X, y, c='r', label='(xi,yi)')
plt.plot(X, preds, 'b', label='y=f(x)')
plt.xlabel('X')
plt.ylabel('y')
plt.legend(loc='upper left', bbox_to_anchor=(1,1), fontsize=16)
plt.show()



先程と同様に直線を当てはめられました。


尤もらしい直線である、関数y=f(x)(x_i,y_i)とのy方向の誤差の二乗和(二乗誤差)\sum_{i=1}^ n(y_i - Ax_i - B)^ 2を最小化するのが、最小二乗法です。すなわち関数をデータに当てはめた結果の二乗誤差が最小になるパラメータ(傾きAと切片B)を推定する方法になります。

ここでいう誤差はグラフのこの部分になります。



全ての点に引くと見づらくなるため3つの点だけに引いてますが、このy方向の誤差を最小化する、尤もらしい直線を求めるのが最小二乗法になります。

尤もらしい直線である、関数y=f(x)が意味するものの直感的な理解としては、説明変数(x)を元に目的変数(y)を説明するに当たって、xyになるべく近い形で直線を通し、先程解説した誤差を最小化していく、というようなイメージです。



データセットを使ってフィッティング


次に実データを使って、分析してみます。

KaggleのHeights and weightsデータセットを使います。



ではデータを読み込んで、表示します。


import pandas as pd

dataset = pd.read_csv(
    'heights_and_weights/data.csv',
    dtype={
        'Height': np.float32,
        'Weight': np.float32
    }
)
display(dataset.head())



このデータセットはこのように身長と体重がペアになったデータセットです。

今回は身長を説明変数、体重を目的変数として、体重を説明する直線を引いていきます。



# 先程書いた関数群をクラス化した回帰モデル

class LeastSquaresMethod(object):
    
    @staticmethod
    def covariance(X, y):
        X_deviation = X - X.mean()
        y_deviation = y - y.mean()
        cov = (X_deviation * y_deviation).mean()
        return cov

    @staticmethod
    def variance(X):
        var = ((X - X.mean()) ** 2).mean()
        return var

    @classmethod
    def least_squares_method(self, X, y):
        A = self.covariance(X, y) / self.variance(X)
        B = y.mean() - A * X.mean()
        return A, B

    @classmethod
    def f(self, X, y):
        A, B = self.least_squares_method(X, y)
        return A*X + B


上のLeastSquaresMethodクラスを使って、データにフィッティングし、結果をプロットします。


X = dataset['Height'].to_numpy()
y = dataset['Weight'].to_numpy()

preds = LeastSquaresMethod.f(X, y)


fig = plt.figure(figsize=(10,8))
plt.style.use('seaborn')
plt.scatter(X, y, c='r', label='(Heights,Weights)')
plt.plot(X, f(X, y),, 'b', label='y=f(x)')
plt.xlabel('X')
plt.ylabel('y')
plt.legend(loc='upper left', bbox_to_anchor=(1,1), fontsize=16)
plt.show()



尤もらしい直線が引けています。次に回帰モデルの出力をデータに加えて眺めてみます。



目的変数である体重データと比較して、体重データの説明として良い結果が出力出来ているように見えます。


次にこの回帰モデルの性能を評価するため、回帰の評価指標である決定係数(R^ 2)を使って、実際に評価します。



決定係数( R^ 2)

\displaystyle{
{R^{2}} = 1 - \frac{ \sum_{i=1}^{N} { ( {y}_i - \hat{y}_{i} ) }^{2} }{ \sum_{i=1}^{N} { ( {y}_i - \bar{y}) }^{2} }
}



y_i : 各レコードのデータ

\hat{y_i} : 各レコードに対するモデルの予測値

\bar{y}yの平均値


決定係数は最大が1で、1に近いほど良い当てはまりが出来ていて、性能が高いと考えられます。

ちなみに、この決定係数を最大化することはRoot Mean Squared Errorを最小化することと等価でもあります。


決定係数をPythonでスクラッチ実装した場合、以下のようになります。


def r2_score(y_true, y_pred):
    return  1 - ((y_true - y_pred) ** 2).sum() / ((y_true - y_true.mean()) ** 2).sum()


目的変数である体重データと、回帰モデルの出力結果を評価指標に通してみます。


r2_score(y, preds)

[output] 0.9891969224457968


決定係数のスコアからもかなり当てはまりのよい結果になりました。

最小二乗法はy方向の誤差を最小化する特性から、極端な外れ値がデータに含まれている場合、そこに引っ張られる形で、外れ値の影響を大きく受ける特性となっています。(大幅な外れ値を含むこと、すなわち平均値に大幅な変動が起きます。)このデータセットはグラフからも見れる通り、データにはっきりとした直線的関係をある為、きれいに直線を当てはめられていて、且つスコアもよいものとなっております。



データセットを分割して検証


次に先程使ったデータセットを訓練データとテストデータに分割し、訓練データにフィッティングさせたあとに、回帰モデルにとって未知データであるテストデータで性能評価をします。回帰モデルであるLeastSquaresMethodクラスもそれに合わせて作り変えます。


# 訓練用を8件、テスト用を7件に分割する
X_train = X[:8]
X_test = X[8:]
y_train = y[:8]
y_test = y[8:]


訓練データから得たパラメータ(A,B)を保持し、テストデータの説明変数(x)のみを与えて、関数y=f(x)を予測させる形に実装します。


# 保持しておくパラメータの定義と、
# fitメソッドとpredictメソッドを追加

class LeastSquaresMethod(object):
    def __init__(self):
        self.A = 0
        self.B = 0
    
    @staticmethod
    def covariance(X, y):
        X_deviation = X - X.mean()
        y_deviation = y - y.mean()
        cov = (X_deviation * y_deviation).mean()
        return cov

    @staticmethod
    def variance(X):
        var = ((X - X.mean()) ** 2).mean()
        return var

    def least_squares_method(self, X, y):
        A = self.covariance(X, y) / self.variance(X)
        B = y.mean() - A * X.mean()
        return A, B

    def fit(self, X, y):
        self.A, self.B = self.least_squares_method(X, y)

    def predict(self, X):
        return self.A*X + self.B


model = LeastSquaresMethod()
model.fit(X_train, y_train)


まず、先程フィッティングした、訓練データに対する出力結果をプロットしてみます。


preds = model.predict(X_train)



データセット全件の時と同様に、尤もらしい直線が引けています。


次にテストデータを予測させて、出力結果をプロットしてみます。


preds = model.predict(X_test)



テストデータを予測させた結果、先程のような尤もらしい直線ではなくなりました。


次にこの結果を決定係数に通してみます。


r2_score(y_test, preds)
[output] 0.617812842130661


決定係数のスコアもだいぶ下がりました。

訓練データにフィッティングし、取得したパラメータを元にテストデータを予測させた結果になります。

これがこの回帰モデルの未知データに対する汎化性能と言えます。

ひとまず、説明変数のみから、関数y=f(x)を予測することができました。


以上で最小二乗法の解説は終わりになります。

Github APIを使ってissueを取得する

こんにちは、最近ノコギリで自分の指をギコギコしたHaraShoです。
(皆さま、刃物の取り扱いには十分お気をつけ下さい。。)

最近、プロジェクト管理ツールとして導入されたAsanaにGithubのissueを移そうと思い、Github APIを使ったときの内容です。

Asanaには CSV インポート機能 があるので、issueをcsvで取得する方法はないかと調べていたところ、以下を発見しました。

curl -u ":username" "https://api.github.com/repos/:owner/:repos/issues?state=open" | jq -r '["number","title","html_url"], (.[] | [.number,.title,.html_url]) | @csv' > issues.csv

上記コマンドの:username, :owner, :reposを適宜変更して実行してみたところ、以下の結果に。

Enter host password for user 'Githubのユーザー名':
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   124  100   124    0     0    441      0 --:--:-- --:--:-- --:--:--   459
jq: error (at <stdin>:4): Cannot index string with string "number"

jqコマンドに期待する結果が渡せていないようなので、curlだけ実行してみると以下のレスポンスが返ってきていました。

{
  "message": "Not Found",
  "documentation_url": "https://docs.github.com/rest/reference/issues#list-repository-issues"
}

(むむむ、issueが取得できていない。。)

どうもPrivateリポジトリから取得する場合は Personal access tokensで生成したトークンをAuthorization ヘッダに指定する必要があるようでした。 取得できた curl コマンドは以下です。

curl -u ":username" -H "Authorization: token :token" "https://api.github.com/repos/:owner/:repos/issues?state=open"

無事issueを取得することができましたが、デフォルトで取得できる件数は30件のようです。
こちらを参考に任意のパラメータを指定することで、必要なissueを取得することができました。(パチパチ)

今回はここまで、ご覧いただきありがとうございました!

インゲージではエンジニアを募集しています!
ご興味あればぜひご覧くださいませ〜。 ingage.co.jp

migrationファイルの「change」と「up、down」

こんにちは!

4月に入社した新人エンジニアのodaです。

Ruby on Railsを使ってアプリケーション開発をされている方は、マイグレーション機能を使ってテーブル定義に変更を加えているかと思います。

私自身、個人でRailsアプリを作成しているときは、もちろん、マイグレーション機能を使っていたのですが、ロールバックを意識することがなく、マイグレーションファイルの「change」と「up・down」の違いがわかっていませんでした。

今回は、この点について、掘り下げてみたいと思います。

(以下、Rails 7.0.2.3を使用しています。)


まずは、次のコマンドにより、Usersテーブルを作成します。

bin/rails g model Users name:string age:integer note:string


すると、次のようなマイグレーションファイルが作成されます。

class CreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string :name
      t.integer :age
      t.string :note

      t.timestamps
    end
  end
end


このマイグレーションファイルを次のコマンドにより実行するとname、age、noteのカラムを持ったUsersテーブルが作成されます。

bin/rails db:migrate


さて、次のコマンドを実行するとどうなるでしょうか?

bin/rails db:rollback


直前に行ったマイグレーションがロールバックされ、Usersテーブルが削除されます。

このとき、Railsは、changeメソッドを逆転実行するとどうなるかを自動的に判断してくれています。(「テーブルの追加」の逆は「テーブルの削除」)


次にnoteのデータ型をstringからtextに変更するために、以下のマイグレーションファイルを作成、実行します。

class ChangeDatatypeNoteOfUsers < ActiveRecord::Migration[7.0]
  def change
    change_column :users, :note, :text
  end
end


すると、noteのデータ型はstringからtextへ変更されます。

この時、先ほどのrollbackコマンドを実行するとどうなるでしょうか?

次のようなエラーメッセージが出るかと思います。

rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:

This migration uses change_column, which is not automatically reversible.
To make the migration reversible you can either:
1. Define #up and #down methods in place of the #change method.
2. Use the #reversible method to define reversible behavior.

要約すると、change_columnを使用したマイグレーションは自動的には元に戻せないため、1もしくは2の方法をとってくださいと書かれています。

それでは、方法1を使って、先ほどのマイグレーションファイルを次のように書き換えてみます。upメソッドにはマイグレーション時の処理を、downメソッドにはロールバック時の処理を書きます。

class ChangeDatatypeNoteOfUsers < ActiveRecord::Migration[7.0]
  def up
    change_column :users, :note, :text
  end

  def down
    change_column :users, :note, :string
  end
end

すると、無事、ロールバックをすることができるようになります。

また、方法2を使うと、次のようにマイグレーションファイルを書き換えることもできます。

class ChangeDatatypeNoteOfUsers < ActiveRecord::Migration[7.0]
  def change
    reversible do |dir|
      change_table :users do |t|
        dir.up   { t.change :note, :text }
        dir.down { t.change :note, :string }
      end
    end
  end
end

マイグレーションファイルを使うときは、上記を意識することで、スムーズに開発を進めていきたいと思います。

ちなみに、changeメソッドが逆転実行できるのは、add_columnchange_column_nullcreate_tablerename_columnなど、あらかじめ決められたメソッドに限られています。

みなさんも、マイグレーション機能により、テーブル定義に変更を加えるときは、ぜひ、意識してみてください!