しふみんのブログ

しふみんのブログです。

Raycastスクリプトコマンド内でpbcopyを利用する際はlocaleを設定する

3行で

環境

  • macOS: Sonoma 14.6.1
  • Raycast 1.82.5

問題

MacでRaycastという便利ランチャアプリを使っている。
Raycastでは自分でスクリプトを書いて動かせるのだが、Raycastスクリプトで pbcopy を利用していたところ、日本語を取り扱った際に文字化けが発生したので原因を調べた。

echo "ミジュマル" | pbcopy

pbpaste
# => ミジュマル% 
# ミジュマルが文字化けしてしまう

原因はどうやら、Raycastスクリプト内と手元ではlocaleの設定が異なっているからのようだった。

Raycastスクリプト内で echo $LANG すると en_JP.UTF-8

# Raycastスクリプト内の実行
echo $LANG
# => en_JP.UTF-8

ローカルでecho $LANG すると ja_JP.UTF-8

# ローカルでの実行
echo $LANG
# => ja_JP.UTF-8

UTF-8はUTF-8でも言語コードが違うとダメらしい。それはそうか。

解決方法

localeの環境変数を設定する。
具体的には LANG=ja_JP.UTF-8 を設定する。

export LANG=ja_JP.UTF-8
echo "ミジュマル" | pbcopy

ちなみに、下記では環境変数を設定できなかった。なるほどね。

LANG=ja_JP.UTF-8 echo "ミジュマル" | pbcopy

実際はRaycastスクリプトをRubyで書いており、Rubyから pbcopy を呼び出していたので下記のようになった。

IO.popen({ 'LANG' => 'ja_JP.UTF-8' }, 'pbcopy', 'w') { |clipboard| 
  clipboard.write(content) 
}

その他

pbcopy はエンコーディングの際に LANG のlocaleを見ている。

man pbcopy

{略}
* Encoding:

pbcopy and pbpaste use locale environment variables to determine the
encoding to be used for input and output.  For example, absent other
locale settings, setting the environment variable LANG=en_US.UTF-8 will
cause pbcopy and pbpaste to use UTF-8 for input and output.  If an
encoding cannot be determined from the locale, the standard C encoding
will be used.  Use of UTF-8 is recommended.  Note that by default the
Terminal application uses the UTF-8 encoding and automatically sets the
appropriate locale environment variable.
{略}

/

基本的にpbcopy (pbpaste)を使う時は local to localだから文字エンコードを気にしたことはなかったけど、こういうこともあるのだなあ。

RubyKaigi 2024に参加してきた


5/15から5/17まで那覇文化芸術劇場なはーとで開催していたRubyKaigi 2024に参加してきました。
5/14のday0の夜に前乗りして15-17はRubyKaigi 2024に参加し、18-19は妻と沖縄を少し観光しました。
RubyKaigiへの参加は初めてでした。沖縄も初めて。

今回は会社で参加に係る費用を出してくれるということで初参加が叶いました。5/1入社にも関わらず既存の社員と同様に参加させていただけて感謝です。
というわけで、参加するからには最大限に楽しみたいより多くのものを吸収したいということで、少しは準備して臨みました。

まず、実家への帰省が被っていて参加できなかった RubyKaigi 2024事前勉強会 の資料を読み漁りました。

また、下記の記事もとても参考になりました。

そして、株式会社SmartHRが提供している公式スケジュールアプリであるところの RubyKaigi Schedule.select でスケジュールと睨めっこしながらどのセッションを聴くかの予定を立てました。

このアプリはなんとOSSなんですね!

事前勉強会の資料は全てがとても参考になったのですが、特に松田さんの「初心者のためのRubyKaigi入門/RubyKaigi Introduction」が良かったです。

speakerdeck.com

というのも、参加前はかなり気負っていましたが、このスライドを読み「トークはほぼ「わからない」のが当たり前。それは気にしない。Rubyistたちが集まってRubyの話をするそれ自体がお祭りで楽しい。その熱を浴びに行く」と少し肩の力が抜けた気がしたからです。

セッション感想

あとで、聴講したものの書けていないセッションの感想も追記したい。

Writing Weird Code @tompng

  • スライド: rubykaigi2024_keynote_tompng.pdf - Google ドライブ
  • Quineの話
    • Quineとは、自分自身のソースコードをそのまま出力するプログラムのこと
      • Quineのコードがどうやって自己再現性を実現しているのかまだ分かっていない
  • まさに魔法を見ているようだった。ビジュアル的にはもちろんだし、セッションの流れも美しい発表だった
  • 随所で他のセッションを紹介していく流れで、ここから始まるRubyKaigi 2024の期待がどんどん高まっていくの感じた。ブチ上がった
  • アニメーションのレンダリングを半分の速度にするために、Time.nowをoverrideして1/2にした
    • 真の逆転の発想って感じだ

Unlocking Potential of Property Based Testing with Ractor @ohbarye

  • スライド: Unlocking Potential of Property Based Testing with Ractor - Speaker Deck
  • Ractorでパラレルに実行できるとProperty based testの実行時間というデメリットを改善できるのではという話
  • Property based testingの概念は初見だった
    • 概要を詳しく解説していただいていてありがてえ
    • テスト失敗時の入力データのShrinkingがミソなんだろうな(小並)
  • Ractorを現実世界で使うのは難しい
    • 確かに性的解析などでRactor互換かどうかがパッと分かって欲しい
    • (既存の)RSpecのテストに導入するだけで大変ということがわかった
    • Ractor互換なコードかつ、明らかにCPUがボトルネックになっているテストだと、Ractorを最大限に活用できる。うーん、厳しそうw

Exploring Reline: Enhancing Command Line Usability @ima1zumi

  • スライド: Exploring Reline: Enhancing Command Line Usability - Speaker Deck
  • RelineのGNU Readlineとの機能差を埋めていっているという地道だがとても大事な話
    • vi-editing-mode うぉぉぉ!
  • ちゃんと設定ファイルの利用実態を調査して需要を見極めているの偉すぎる
    • 実際の仕事に通ずるものがある
    • .inputrcを初めて知りました。。。
  • Undoって普段何気に使っているけど、確かに実装しようとすると意外と考えることが多い
  • irbいつも便利に使わせてもらっていますという感謝の気持ちでいっぱいになった

An adventure of Happy Eyeballs @coe401_

- Ruby Committers and the World @CRuby Committers

  • コミッターの方々でも結構意見が分かれるトピックもあるんだな
    • 「(私は)Rubyのいいところは〇〇なところだと思っているから、△△な立場」のような発言に信念を感じた
  • いくつかのトピックについては会場の聴講者に質問形式でアンケートを聞いていて、RBSのコメント記法などは自分が少数派の考えに属していてなるほど感があった
  • async/await構文の「あれ本当に便利になっているの?」にはw
  • まだしばらくはRubyに型は導入されなさそうだと思った

The state of Ruby dev tooling @vinistock

  • Rubyのツールには便利で開発が楽しくなるものがたくさん存在するという話
    • が、いわゆるデファクトだとされるツールは定まっておらずfragmentationが存在している
  • 今でこそそれぞれのツールは立ち位置はある程度理解しているつもりだが、確かに初学者は混乱してしまうかも
  • Rustは割と定まっているらしいの知らなかった。Goもかな?
    • 定まっているというかツール群がConsolidatedなのか
  • ruby-lspにはめちゃくちゃ期待しています

Matz Keynote @yukihiro_matz

  • かつてのRuby2(not 2.0)の構想の話
  • Ruby2の構想で残っているNamespaceが導入されたらRuby 4.0とメジャーバージョンを上げようかなという発言で会場が沸いていた
  • Rubyは十分に成熟していると思っているが、まだRubyの未来にワクワクさせられそうだ
  • 『Rubyは死んだ』というネタを擦っていたら、海外で「希望を持って初めてカンファレンスに来た人たちをがっかりさせるからやめろ」と怒られた話面白い

交流

友人知人、初めての人、久しぶりに会った人、お世話になった人など色々な人と交流できました。

一番良かったのは、1日目のキーノートセッション後にホールから出て「ランチどうしようかな?会社の人と合流するか?」と思ってスマホを見ていたら、後ろから「しふみんさん?」と声をかけられて振り返ると僕がエンジニアに転向した時に入った会社のCTOがそこにいたことです。まさかいるとは思っていなかったのでめちゃくちゃ驚きました。元同僚たちと当時の話や近況の話をしながらご飯を食べました。そして奢ってもらった。 そしてその後に別で来ていた社長にも遭遇するという。挨拶できて良かった。

当時はSESで様々な会社の多くの人にお世話になったのですが、そのお世話になった何社かの方々も参加されておりブースなどでお話し挨拶できたのも良かったです。勝手に自分の歴史を感じていました。

また、久しくリアルでは会えていなかった関東の友人たちと沖縄でご飯を食べて近況報告できたのも良かったです。久しく会えてない人もイベントだと会うことができる。あると思います。

惜しむべくは、気づいたらOfficial Partyの参加登録期限が過ぎておりDrinkup系はAfter Partyにしか参加できなかったことです。各種Drinkupは早めに参加登録しておかないといけないという学びを得ました。次回に活かす。

/

初参加のRubyKaigiはとても楽しかったです。
Rubyのコアについて「なにもわからない」ということを実感できました。無知の知ってこういうことなんだなあ。
逆説的に、普段は即効性の高いインスタントな情報ばかりを摂取してしまっているということだと思います。僕は釣竿の振り方ばかり学んでいて釣竿自体のことは何もわかっていなかった。

RubyKaigiで見聞きした技術については「そういえばRubyKaigiで話されていたな」というインデックスを貼ることだけはできたと思います。それをフックに今後の学習に活かせたらいいな。
Webアプリケーションよりの仕様や実装についてはある程度「〇〇の理由でこっちの方がいいと思う」と言えるようにはなりましたが、Rubyコアのようないわゆるprimitiveな言語仕様については現状全く答えることができないので、そういったことに対しても自分の信念を持てるようになりたいと思います。

あとは、英語ですね。英語セッションを聴講できるようにリスニング力を上げていきたいと強く感じました(しかし、どうやって?)
例えば、英語として発話される(?)”Ruby”は聞き取れるわけで、結局は単語力が必要な気がします。あとはリエゾンへの理解が必要そう。 あるいは、スマホなどのリアルタイム翻訳のソリューションがあと1年で実用的になってくれ〜〜〜という気持ちがあります。

また、リアルならではの””熱””を感じることができたのも良かったです。
前職はフルリモートでこの5月から働き始めた会社もフルリモートなのですが、リアルで集まることの意義を感じました。そこにいる人の熱を浴びることができる。そして何かをディスカッションするにせよ情報量が多くてコミュニケーションの密度も高い。これがリアルの醍醐味だと思いました。

自分のブログなので自分語りするのですが、僕が異業種からエンジニアに転向した最も大きな理由の1つにTwitterでエンジニアが技術について楽しそうに語ったり議論しているのを見たというのがあります。
その初心を改めて思い出しました。

最後になりましたが、運営スタッフの皆様、登壇者の皆様、スポンサー企業の皆様ありがとうございました!初参加でしたがとても楽しむことができました。BIG感謝です!

次回のRubyKaigi 2025は愛媛の松山で開催。次回も参加したい!
次回も行けるようにこの1年頑張るぞo(^-^)o

AWS CLIのCommand completionとauto-promptを有効にする

最近AWS CLIを使い始めました。
コマンドに不慣れなため自動補完的なsomethingが欲しいと思い調べたところ、AWS CLIにはCommand completionとauto-promptがあることが分かりました。
というわけで、設定をしていきます。

自分の環境

  • シェルはZsh
  • Homebrewでawscliをインストール
$ echo $SHELL
/opt/homebrew/bin/zsh
$ aws --version
aws-cli/2.15.0 Python/3.11.6 Darwin/23.1.0 source/arm64 prompt/off

ここの prompt/off はauto-promptとは関係がないっぽい。

Command completion

docs.aws.amazon.com

いわゆるコマンドの自動補完です。
コマンド入力時にタブキーを押下すると入力内容が自動的に補完されます。
利用するためにはシェルのprofileに設定を追加する必要があります。
Zshの場合は .zshrcに設定を追加します。

~/.zshrc

autoload bashcompinit && bashcompinit
autoload -Uz compinit && compinit
complete -C '/opt/homebrew/bin/aws_completer' aws

completeにはaws_completerのpathを設定する必要があります。
Homebrewでawscliをインストールしている場合、aws_completerも同時にインストールされており、そのpathは /opt/homebrew/bin/aws_completer となっています。

Bashの場合は、compinit周りの設定が不要なため.bashrcに下記の設定のみを追加します(たぶん)

complete -C '/opt/homebrew/bin/aws_completer' aws

aws (awsの後ろにスペース)の状態でタブキーを押すと次の入力が補完されます。
コマンドだけではなくサブコマンドやオプションの内容も補完されます。
(aws-cliってこんなにサブコマンドの種類があるのか。。。)

Image from Gyazo

auto-prompt

docs.aws.amazon.com

コマンド入力時に対話式のプロンプトを用いてコマンドとそのオプションを理解しながら入力できる機能です。
利用するためには、CLI実行にオプションで設定する、環境変数に設定する、設定ファイルに設定するの3択がありますが、ここでは設定ファイルに設定を追加します。
~/.aws/configに設定を追加します。

~/.aws/config

[default]
cli_auto_prompt = on

aws だけ入力してコマンドを実行すると、下記のようなpromptが表示されます。
(ドキュメントや出力パネルもあってリッチや。。。)

Image from Gyazo

また、 cli_auto_prompt は on 以外にも on-partial の設定もあります。

[default]
cli_auto_prompt = on-partial

違いは、

  • on : full auto-promptモード。 aws コマンド実行時に常時auto-promptを利用するモード
  • on-partial : partial auto-prompモード。 aws コマンドが不完全な場合(サブコマンド等などで)にauto-promptを利用するモード

docs.aws.amazon.com

cli_auto_prompt = on-partial の方はコマンドが完全な場合はそのまま実行されるため、通常に利用する分はこちらの方が便利かもしれません。
auto-promptに頼りたい場合はコマンドがあやふやな場合であると思うため。

/

auto-promptの方はやりすぎな気もしますが、AWS CLIの理解を深めるためにしばらく使ってみようかと思います。

参考

Amazon Linuxでffi gemをnative buildするのに必要な依存パッケージ

AWSが提供しているlambda/rubyのbaseイメージでffi gemをインストールしようとしたら、native buildで失敗しました。

gallery.ecr.aws

再現

Gemfile

# frozen_string_literal: true

source 'https://rubygems.org'

gem 'ffi'

Dockerfile

FROM public.ecr.aws/lambda/ruby:3.2

# 通常はGemfile.lockもコピーするだろうけど再現には必要ないため省略
COPY Gemfile ${LAMBDA_TASK_ROOT}/

RUN gem install bundler && bundle install

こんな環境でdocker buildしてbundle installしようとすると、

$ docker build --platform linux/arm64 -t lambda-ruby-test:test .

[+] Building 2.5s (7/7) FINISHED                                                          
 => [internal] load build definition from Dockerfile                                 0.0s
 => => transferring dockerfile: 263B                                                 0.0s
 => [internal] load .dockerignore                                                    0.0s
 => => transferring context: 2B                                                      0.0s
 => [internal] load metadata for public.ecr.aws/lambda/ruby:3.2                      0.0s
 => CACHED [1/3] FROM public.ecr.aws/lambda/ruby:3.2                                 0.0s
 => [internal] load build context                                                    0.0s
 => => transferring context: 106B                                                    0.0s
 => [2/3] COPY Gemfile /var/task/                                                    0.0s
 => ERROR [3/3] RUN gem install bundler && bundle install                            2.4s                      2.4s
------                                                                                    
 > [3/3] RUN gem install bundler && bundle install:
(略)
#0 1.937 Fetching gem metadata from https://rubygems.org/..
#0 1.976 Resolving dependencies...
#0 1.979 Fetching ffi 1.16.3
#0 2.144 Installing ffi 1.16.3 with native extensions
#0 2.327 Gem::Ext::BuildError: ERROR: Failed to build gem native extension.
#0 2.327 
#0 2.327     current directory: /var/lang/lib/ruby/gems/3.2.0/gems/ffi-1.16.3/ext/ffi_c
#0 2.327 /var/lang/bin/ruby extconf.rb
#0 2.327 checking for ffi.h... *** extconf.rb failed ***
#0 2.327 Could not create Makefile due to some reason, probably lack of necessary
#0 2.327 libraries and/or headers.  Check the mkmf.log file for more details.  You may
#0 2.327 need configuration options.
(略)
#0 2.327 /var/lang/lib/ruby/3.2.0/mkmf.rb:490:in `try_do': The compiler failed to
#0 2.327 generate an executable file. (RuntimeError)
#0 2.327 You have to install development tools first.
#0 2.327 
#0 2.327        from /var/lang/lib/ruby/3.2.0/mkmf.rb:616:in `block in try_compile'
#0 2.327        from /var/lang/lib/ruby/3.2.0/mkmf.rb:565:in `with_werror'
#0 2.327        from /var/lang/lib/ruby/3.2.0/mkmf.rb:616:in `try_compile'
#0 2.327        from /var/lang/lib/ruby/3.2.0/mkmf.rb:1157:in `block in have_header'
#0 2.327        from /var/lang/lib/ruby/3.2.0/mkmf.rb:989:in `block in checking_for'
#0 2.327        from /var/lang/lib/ruby/3.2.0/mkmf.rb:354:in `block (2 levels) in postpone'
#0 2.327        from /var/lang/lib/ruby/3.2.0/mkmf.rb:324:in `open'
#0 2.327        from /var/lang/lib/ruby/3.2.0/mkmf.rb:354:in `block in postpone'
#0 2.327        from /var/lang/lib/ruby/3.2.0/mkmf.rb:324:in `open'
#0 2.327        from /var/lang/lib/ruby/3.2.0/mkmf.rb:350:in `postpone'
#0 2.327        from /var/lang/lib/ruby/3.2.0/mkmf.rb:988:in `checking_for'
#0 2.327        from /var/lang/lib/ruby/3.2.0/mkmf.rb:1156:in `have_header'
#0 2.327        from extconf.rb:10:in `system_libffi_usable?'
#0 2.327        from extconf.rb:46:in `<main>'
#0 2.327 
#0 2.327 To see why this extension failed to compile, please check the mkmf.log which can
#0 2.327 be found here:
#0 2.327 
#0 2.327 /var/lang/lib/ruby/gems/3.2.0/extensions/aarch64-linux/3.2.0/ffi-1.16.3/mkmf.log
#0 2.327 
#0 2.327 extconf failed, exit code 1
(略)
#0 2.327 An error occurred while installing ffi (1.16.3), and Bundler cannot continue.
#0 2.327 
#0 2.327 In Gemfile:
#0 2.327   ffi
------
Dockerfile:8
--------------------
   6 |     COPY Gemfile ${LAMBDA_TASK_ROOT}/
   7 |     
   8 | >>> RUN gem install bundler && bundle install
   9 |     
--------------------
ERROR: failed to solve: process "/bin/sh -c gem install bundler && bundle install" did not complete successfully: exit code: 5

native buildのコンパイルで失敗しておりgemをインストールできません。

必要な依存パッケージ

github.com

リポジトリのRequirementsを見るとCコンパイラと libffi ライブラリが必要だと書いています。
加えて、そもそもmakeができていなかったので make も必要そうでした。

lambda/rubyのbase OSはAmazon Linux 2 なのでパッケージ管理システムはyumです。 したがって、yumで下記のパッケージをインストールすればエラーを解消できそうです。

  • gcc
  • make
  • libffi-devel

Dockerfile(改修後)

FROM public.ecr.aws/lambda/ruby:3.2

# 依存パッケージを追加する
RUN yum update -y && yum install -y gcc make libffi-devel

COPY Gemfile ${LAMBDA_TASK_ROOT}/

RUN gem install bundler && bundle install

無事にbundle installが成功しました。

$ docker build --platform linux/arm64 -t lambda-ruby-test:test .
[+] Building 19.7s (9/9) FINISHED                                                         
 => [internal] load .dockerignore                                                    0.0s
 => => transferring context: 2B                                                      0.0s
 => [internal] load build definition from Dockerfile                                 0.0s
 => => transferring dockerfile: 261B                                                 0.0s
 => [internal] load metadata for public.ecr.aws/lambda/ruby:3.2                      0.0s
 => CACHED [1/4] FROM public.ecr.aws/lambda/ruby:3.2                                 0.0s
 => [internal] load build context                                                    0.0s
 => => transferring context: 28B                                                     0.0s
 => [2/4] RUN yum update -y && yum install -y gcc make libffi-devel                 12.3s
 => [3/4] COPY Gemfile /var/task/                                                    0.0s
 => [4/4] RUN gem install bundler && bundle install                                  6.7s
 => exporting to image                                                               0.7s 
 => => exporting layers                                                              0.6s 
 => => writing image sha256:1234567890                                               0.0s 
 => => naming to docker.io/library/lambda-ruby-test:test                             0.0s 

エンジニア採用面接初心者が参考にするリンク集

近々採用面接担当としての仕事が降ってくる気配あるので、そのためのインプットを行なっている。
せっかくだからその参考リンク集をHackMDに書き出した。
まだしばらくはインプットは続くはずなので更新しやすいHackMDに切り出した。

hackmd.io

株式会社グロービスに入社しました

正確には「株式会社グロービスにエンジニアとして入社して3ヶ月が経ちました」です。 晴れて試用期間が満了したことと、同日‪の4月1日に入社して同じチームで働いている‪ @mktakuya ‬が入社エントリを書いたので後に続こうというお気持ちです。

blog.m6a.jp

あと、つよつよエンジニアの転職エントリはそこらに転がっているけど、そうではないエンジニアの転職エントリも僕は読みたいなと思ったので自分で書くことにしました。

で、誰?

  • @shifumin
  • 新卒で地元のジャパニーズトラディショナルローカルガバメント(notエンジニア) -> 小さなWeb系受託開発会社 -> グロービス
  • Railsエンジニア
  • 主にAPIを作ったりデータモデリング周りを触っていた
  • グロービスに入社してReactも触り始めた

グロービスという会社

recruiting-tech.globis.co.jp

グロービスと聞いて思い浮かぶのは、経営大学院、グロービスMBAシリーズの書籍、法人向け研修、VCといったところでしょうか。
これら(だけではないけど)に加えて、グロービスは2016年にGDP(グロービス・デジタル・プラットフ ォーム)という部署を組織してEdTechの領域に参入しました。
現在提供しているサービスは大きくくくると「グロービス学び放題」と「グロービス・ラーニング・プラットフォーム」で、前者はビジネススキルを学べる定額型動画学習サービス、後者はグロービスの提供している研修・学習システムを集約し一括管理するためのプラットフォームです。
社内のエンジニアの大部分はGDPに集まっています。

入社の経緯

前職は受託開発の会社でずっと自社内でクライアントワークをカタカタやっていたのですが、社内のやむごとなき理由によりある日表参道名刺管理アプリ社にSESとして客先常駐することになりました。
そこで初めてスクラムを体験してプロダクトだけでなく開発プロセスの改善等を通じてチームも成長していくところに「これや!」感を感じ、当事者として関わりたいという思いとともに転職活動を始めました。

業種は特にこだわりはなかったのですが、森博嗣の「今はもうない」の次の一節

舗装された林道が開通し、そこを走る大型トラックによる輸送が、おそらくこの軽便鉄道の使命を奪ったのであろう。廃線になって十年、いやそれ以上経っているに違いない。しかし、別にどうということはない。いつだって、より効率の良いものが、どんなに親しまれ、どんなに美しく伝統的な手法にも、必ず勝る。それがシステムというものだ。ようするに楽であること……、それ以外に人間を魅了するものはない、といっても過言ではない。

がはてなダイアリー時代のブログのヘッダーにずっと貼っていたくらい大好きで、要は利用する人が楽できるプロダクトの開発に関わりたい(あわよくば自分も楽したい)という気持ちがあったのと、広告主が見え隠れすると意識がブレそうなので「ビジネスモデルが広告収益モデルでないユーザーから直接お金を頂く」ビジネスモデルの会社がいいなという気持ちと、あとバックエンドがRailsならすぐに力になれそうだという全てを満たすような会社に話を聞きに行きました。ついでに転職ドラフトで指名いただいた会社はとりあえず話を聞きに行ったのでトータルで15社くらいのカジュアル面談を受けた気がします。

カジュアル面談で色々な会社の話を聞きに行ったはいいもののじゃあどの会社の本選考を受けるかの決め手がなくて悩んでいたところ、たまたま去年の僕の誕生日にQiitaが開催していたCareer Meetupに参加して、 @sue738 がグロービスを紹介している発表を聞くことができました。

logmi.jp

元々学ぶことが好きだったことと、GDPは今参加しても面白そうなフェイズだなと感じたことと、上で上げた条件を全て満たしていることの全てが線で繋がったのでこの船に乗るしかないなと思い選考を受けた結果、ご縁がありグロービスに入社する運びとなりました。
僕は地方出身なのですが、東京に来てから地方って学習モチベがあってもそのため環境が都会に比べて整っていないことを強く実感するようになって、それをどうにかして是正したいということを選考で熱く語った記憶があります。

何をしているか

グロービス・ラーニング・プラットフォームの開発に携わっています。主にはAPIを作成したり、他サービスとの連携部分のコードを書いています。
あと、チームの生産性向上やスクラムマスターに興味ありますと言っていたらスクラムマスターのロールが降ってきたのでスクラムマスターも始めました。

社内の雰囲気

働きやすいです。
内製の開発組織が全くなかった状態から2年数ヶ月でこのエンジニア組織とその制度を整えるのは正直すごいなということをまず思いました。
フレックスタイム、リモートワーク、副業許可、壁一面ホワイトボード、ソファスペース等Web系ベンチャーでよくありそうな制度は大体用意されています。
教育事業を行っているだけあって、エンジニア自体学習意欲が高めな人が多いことを差し置いても学習意欲の高い人が多いと感じました。

もちろん課題もあって、個人的には下記を感じます。

  • 組織拡大による人数の増加・チームの分割等の影響でbiz側dev側の期待値調整・優先順位のすり合わせが弱いことがある
  • サービス開始から数年経ち技術的負債が溜まり始めた

前者はフィットする会議体、組織構成を変化させながら塩梅を模索中、後者は機能開発とバランスを取りながら負債返却にメスが入り始めた状況です。

いい感じに課題が転がっていてまだまだ人が足りなくエンジニアを絶賛募集中なので興味ある方はぜひ下記あたりから応募しましょう!

www.wantedly.com

全方位で募集しているけど、特にiOS、QA、フロントエンドのエンジニアが欲しい雰囲気が社内にあります。 また、色々と中のことをお話しすることもできますのでぜひご飯でも誘ってください。

現場からは以上です。

あとでここ見てって言う用のあれこれ

お金

皆大好きお金の話ですが、アカウントを作れば誰でも指名額を見れる 転職ドラフトによると大体の提示額が分かるかと思います。リードクラスのエンジニアの採用チャネルとして転職ドラフトを利用しているようです。 残念ながら僕は転職ドラフト経由ではないことと1人しか同僚の年収の正確な額を知らないので、実態はよく分かりません。。。

その他

  • Q.エンジニアはどれくらいいる?
    • A.GDPにいるエンジニアは50人くらい(業務委託含む)。毎月2人ずつくらい入社している印象。
  • Q.支給マシン等は?
    • A.MacBook Pro15インチマシマシとDellの27インチディスプレイ(not 4k)1枚
  • Q.リモートワークやフレックスは利用されている?
    • A.""自由と自己責任""に則って結構利用されていると思う。「作業集中のため終日RWします」「荷物受け取りで午前RW、午後から出社します」「通院のため早退します」あたりは毎日のようにSlackの勤怠chで見る。
  • Q.勤怠はどんな感じ?
    • A.10時台に来て19時台に帰る人が多いイメージ。みなし残業45時間だけど、実感として残業月10時間くらいのイメージがある(所定は7.5 時間/日)
      • 以下、私の勤怠例。左が標準的な週、右が色々やっていた週の出勤時間と退勤時間。
      • Image from GyazoImage from Gyazo

create_tableでカラムを定義するのと同時にユニークインデックスを貼るやつ

なぜかレビューを通っていた下記のようなmigrationがあったのですが、 unique indexが貼られていなくて(当たり前ですが)先日ひどい目にあいました。

# db/migrate/20181231235959_create_products.rb
class CreateProducts < ActiveRecord::Migration
  def change
    create_table :products do |t|
      # (略)
      t.integer :shop_id, unique: true
      # (略)
    end
  end
end

はい。 ActiveRecord::ConnectionAdapters::TableDefinition の column メソッドの opthions に unique: true を渡してもunique indexは貼られませんね。

create_table でカラムを定義するのと同時に unique index を貼るやつの結論

上記のオプションの渡し方の正解は下記ですね。

t.integer :shop_id, index: { unique: true }

下記でいけるので。

t.type :column_name, index: { unique: true }

下記はだめ。

t.type :column_name, unique: true

そんなオプションの渡し方はない。

ActiveRecord::ConnectionAdapters::SchemaStatements の create_table メソッドのコードを読む

まあ、以上なのですが、せっかくなのでこの部分が実際にどういう実装になっているかを知るために ActiveRecord::ConnectionAdapters::SchemaStatements の create_table メソッドについて Active Recordのコードを読んでみますか。

読んだ環境はRails 5.2.2 (GitHub - rails/rails at v5.2.2)です。

create_table

https://github.com/rails/rails/blob/v5.2.2/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb#L290

def create_table(table_name, comment: nil, **options)
  td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
  # (中略)
  yield td if block_given?
  # (中略)
end

yield に渡しているのは td なので、普段 t.integer みたいに書いている t は create_table_definition で定義されているようだ。

create_table_definition table_name

https://github.com/rails/rails/blob/v5.2.2/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb#L1278

def create_table_definition(*args)
  TableDefinition.new(*args)
end

td は TableDefinition のインスタンスだった。

TableDefinition

https://github.com/rails/rails/blob/v5.2.2/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb#L257

class TableDefinition
  include ColumnMethods
  # (中略)
end

ColumnMethods をincludeしている。

ColumnMethods

https://github.com/rails/rails/blob/v5.2.2/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb#L201

module ColumnMethods
  # (中略)
  # Appends a column or columns of a specified type.
  #
  #  t.string(:goat)
  #  t.string(:goat, :sheep)
  #
  # See TableDefinition#column
  [
    :bigint,
    :binary,
    :boolean,
    :date,
    :datetime,
    :decimal,
    :float,
    :integer,
    :json,
    :string,
    :text,
    :time,
    :timestamp,
    :virtual,
  ].each do |column_type|
    module_eval <<-CODE, __FILE__, __LINE__ + 1
      def #{column_type}(*args, **options)
        args.each { |name| column(name, :#{column_type}, options) }
      end
    CODE
  end
  alias_method :numeric, :decimal
end

普段使っているシンタックスシュガーがメタプロで定義されている。
t.integer だと column(name, :integer, options) } なので、結局 column メソッドに options 引数が渡されて実行されている。

column

https://github.com/rails/rails/blob/v5.2.2/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb#L355

def column(name, type, options = {})
  name = name.to_s
  type = type.to_sym if type
  options = options.dup
  # (中略)
  index_options = options.delete(:index)
  index(name, index_options.is_a?(Hash) ? index_options : {}) if index_options
  @columns_hash[name] = new_column_definition(name, type, options)
  self
end

options の index キーの中身は index メソッドに引数として渡されている。

index

https://github.com/rails/rails/blob/v5.2.2/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb#L380

# Adds index options to the indexes hash, keyed by column name
# This is primarily used to track indexes that need to be created after the tab
#
#   index(:account_id, name: 'index_projects_on_account_id')
def index(column_name, options = {})
  indexes << [column_name, options]
end

コメントの通りですが、 indexes に options を追加している。

create_tableに戻る

https://github.com/rails/rails/blob/v5.2.2/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb#L314

def create_table(table_name, comment: nil, **options)
  td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
  # (中略)
  unless supports_indexes_in_create?
    td.indexes.each do |column_name, index_options|
      add_index(table_name, column_name, index_options)
    end
  end
  # (中略)
end

td.indexes のそれぞれの要素の2つ目の index_options は add_index メソッドに index_options として渡している。
結局 add_index が呼ばれていますね。

--

ActiveRecordのコードはこれ以上追いませんが、結局

t.type :column_name, index: { key: value }

の index 部分のオプションは

 add_index(table_name, column_name, {key: value})

のように、 add_index にオプションとして渡されるのでした。
そして add_index は unique indexを作成するときは オプションとして unique: true を渡す必要ので、 create_table の中の t.type でunique indexを貼りたいときはオプションとして index をキーにして、

t.type :column_name, index: { unique: true }

としないといけないのでした。

コードを読むのは疲れますが、実際の処理の中身がよく分かるのでこれからも適宜読んでいきたいですね。
こちらからは以上です。

補足

上記で見た、ActiveRecord::ConnectionAdapters::TableDefinition の column メソッドのオプションでunique indexを貼る方法が以外の方法は下記がある。

ActiveRecord::ConnectionAdapters::SchemaStatements の add_index メソッド

テーブル作成後にindexを追加するやつ。

create_table :products do |t|
  t.integer :shop_id
end

add_index :products, :shop_id, unique: true, name: 'products_shop_id_index'

ActiveRecord::ConnectionAdapters::TableDefinition の index メソッド

create_tableの中でindexを追加するやつ。

create_table :products do |t|
  t.integer :shop_id  
   
  t.index :category_id, unique: true, name: 'products_shop_id_index'
end

ActiveRecord::ConnectionAdapters::Table の index メソッド

change_tableでindexを追加するやつ。
create_table 内で利用する1つ上と形は一緒ですね。

change_table :products do |t|
  t.index :shop_id, unique: true, name: 'products_shop_id_index'
end

参考