うなすけとあれこれ

2019年12月25日

C97の4日目南リ17aでFANBOXのまとめ本を頒布します

お品書き

概要3行

もっと詳しく

コミックマーケット 97 4日目(2019-12-31 火曜) 南リ17a サークル名「キリンセル」にて、以下3冊の同人誌を頒布します。

この同人誌は、僕がpixivFANBOXに投稿しているOSS watchをまとめて時系列順に読めるように編集したものになります。01では 2018年05月から2018年9月末まで、02では2018年10月から2019年3月末までの投稿を集録しています。

(01には、少しだけでしたがpixivFANBOXには投稿していない、社内にだけ公開していた頃のものも収録しています)

また、どちらにも付録として、pixivFANBOXへの投稿をどのようにRe:VIEW形式によるテキストにまとめなおしたかを書き下ろしとして集録しています。

価格は1冊2,000円となります。pixivFANBOXにて支援していただいている方々には、特別に値引きがあります。当日はファンカードを売り子に提示できるようにしてください。詳しくはFANBOXの投稿をご確認ください。

また、物理本が売れ残ったらBOOTHにて販売します。電子書籍版もBOOTHにて販売する予定です。

FANBOXにて支援して頂いている方々には、電子版のPDFを後日配布させていただきますが、500円プラン以上 とさせていただきます。

当日の支払い方法ですが、Squareによるクレジットカード決済 (VISA・Mastercard・American Express・JCB・Diners Club・Discover) またはpixiv PAY、または現金でのお支払いを受け付けております。

なるべくクレジットカードもしくはpixiv PAYでの決済をお願いします

(回線状況によっては現金しか使用できない可能性があります)

取り置きについて

取り置きとして、pixiv PAYでの事前決済を受け付けています。以下のQRコードを読み取ってください。当日は受け取りの際にpixiv PAYアプリの購入履歴画面を提示して前払い済みであることを証明してください。

取り置きQR

ほとんどの内容を無料で公開しているので、物理の冊子はあまり買われないかも?という予測をし、あまり数を用意していません。どうしても冊子として欲しい!という場合は取り置きをおすすめします。

(もし取り置きのみで用意している分がなくなってしまったら、pixivFANBOXで支援していただいている方のぶんを優先で確保します)

うなすけファンブックについて

詳しくは28日の配信にて明らかになります!!!!!!

長々と書きましたが、当日、会場でお会いできることを楽しみにしています!

2019年12月25日
2019年12月16日

平成Ruby会議01参加記

Speaker and Staff

平成Ruby会議01に、発表者として、また当日スタッフとして参加しました。

資料はこれです。

heiseirubykaigi-1

もともと平成.rbのメンバーの一員であったということもあり、平成Ruby会議で何かしらお手伝いできればなと思ってはいたのですが、仕事、プライベート(原稿)などの兼ね合いで当日のみのお手伝いとなりました。 (スタッフ用のchat roomにも参加していましたが、トーク採択には関わっていません)

また別件で、発表内にもあるようにWindows環境でのテストに悪戦苦闘していたこともあり、これで何か話せそうだということでCfPに応募したところ採択されたので、発表者としても参加することができました。

感想

懇親会で、このような会に参加するのが初めての方や学生の方が多く、「平成」の名を冠することが良い方向に働いたようでとてもよかったです。

また個人的には、Re:VIEWについて高橋会長にご挨拶できたこと、 #unasukefm まだですかという声を沢山頂けたことが、もう、たまらなく嬉しかったです。

あ、あとこれ。

エモ散らかしてて限界なんだが #heiseirubykaigi https://t.co/SgSXIRv1bp

— うなすけ(C97火曜日南リ17a) (@yu_suke1994) December 14, 2019
2019年12月16日
2019年11月29日

Middlemanで生成したHTMLの<img>にloading="lazy"を付与するgemを作りました

performance

ChromeがNative lazy loadingをサポートするようになったので、僕のblogでもこれを使おうと作ってみたgemになります。

Markdownの変換に介入する

Chrome v76から、Native lazy loadingがサポートされるようになりました。

Native lazy-loading for the web

ところで僕のblogは、本文をMarkdownで記述したものをHTMLに変換しています。Markdown記法で画像を挿入した場合に、どのようにloading属性を付与すればいいのでしょうか。Markdown内にHTMLを記述した場合、それはそのまま出力されるので、手でタグを書くことで実現できますが、果たしてそのようなことをしたいでしょうか。面倒です。

なので、Markdownの変換過程に介入することで解決できないだろうかと思い、調べてみました。

僕のblogでは、Markdown engineとしてRedcarpet gemを使用しています。そして、RedcarpetにはCustom Rendererという仕組みがあります。これで、![alt text](src) の変換に介入します。

Railsでカスタムmarkdownを実装する - k0kubun’s blog

class CustomRenderer < ::Redcarpet::Render::HTML
  def image(link, title, alt_text)
    "<img loading=\"lazy\" src=\"#{link}\" alt=\"#{alt_text}\" />"
  end
end

上記のようなコードで、 に loading attrを付与することができます。そして、それをgemにしたのがこれです。

実際には loading 属性には lazyの他にも autoとeagerを指定できるので、gemでもRendererをそれぞれ使い分けることでlazy以外を指定できます。 (なのでgem名にlazyと入れたのはちょっと失敗だったかもしれない)

ここまでが表参道.rb #51 での発表内容になります。

omotesandorb #51

生成されたHTMLを改変する

ところで、この手法は上手くいきませんでした。実際にこのgemを使用してblogをdeployすると、画像が全てリンク切れとなってしまったので、急いでrevertしてdeployをし直しました。

この原因は、Middleman側の設定である asset_hash を有効にしていたためです。この設定によって尊重されるべきMiddleman側のCustom Rendererを自分のgemで上書いてしまっていたために、画像のsrcが正しくないものになってしまっていました。

asset_hash を有効にしたまま loading="lazy" をするには、もう変換後のHTMLを編集するMIddleman pluginを作成するしかなさそうです。

という訳で、after_build のタイミングで以下のようなコードを実行するようなgemを作成しました。

def after_build(builder)
  files = Dir.glob(File.join(app.config[:build_dir], "**", "*.html"))
  files.each do |file|
    contents = File.read(file)
    replaced = contents.gsub(%r[<img], "<img loading=\"#{options[:loading]}\"")
    File.open(file, 'w') do |f|
      f.write replaced
    end
  end
end

このコードは、build directory 以下に存在する .html ファイルについて、全ての <img> タグに対して loading 属性を付けて上書き保存します。

ここまでが表参道.rb #52 での発表内容になります。

omotesandorb #52

改変してはいけない <img> を除外する

この実装では、 pre や code の内部の img にも loading 属性を付与してしまいます。これはどうやって解決するか悩んだのですが、Nokogiriによって親に pre 、 code 、blockquote がある img に関しては属性を編集しない、というように処理を変更しました。

https://github.com/unasuke/middleman-img_loading_attribute/pull/1/files#diff-ea964f9b9ef0f5bc6bdab0998e722d4e

まとめ

これで、ようやく僕のblogでlazy image loadingが実現できました。といってもChrome v76以降のみですが。

ということで、Starやpull reqなど頂けると大変ありがたいです。

2019年11月29日
2019年10月28日

Awayのスーツケースを買いました

Away

スーツケースがあると便利

スーツケースを持っていなかった(Dockercon 2016のときに使っていたのは帰国時に壊れた )ので、RubyKaigiや帰省などの遠出のときには、少し大きいトートバッグ のなかに全部詰めこんでいました。

ふつうの帰省時にはこれで問題なかったのですが、冠婚葬祭で礼服も持ち帰らないといけないときに、このスタイルだと礼服だけ別で持つ必要があり、移動がとても大変でした。

スーツケースを検討する

さすがにスーツケースが欲しいなと思いはじめたのは、まつもとりーさんのnoteを読んでからでした。

これを見て、REMOWAのスーツケースよさそう、とはなったのですが、新規に購入するとなると思ったより高価なので、とりあえずレンタルしてRubyKaigi 2019で使ってみました。

デカいスーツケース、荷物をたくさん入れられて便利!!!!!!!!!!!!!!!!!!!!!!!!!!!!

— うなすけ (@yu_suke1994) April 14, 2019
また、そのときにhmskさんが使っていたスーツケースを見て、これもいいなと思って調べたところ、REMOWAほど高価でなく手が届きそうなので迷いがでてきました。

機内持ち込めてバッテリーとラップトップ用のスリーブが付いたAway届いた。かわいい。 https://t.co/M8XUZU0nYL pic.twitter.com/IciROgwXMC

— Kengo Hamasaki (@hmsk) March 27, 2019
これから結婚式など礼服が必要になるイベントが増えることを考えると、スーツケースは確実に必要なので、現時点で手が届くAwayのものを買うことに来めました。

スーツケース、まつもとりーさんオススメのREMOWAはちょっと高価が過ぎるのでhmskさんの使ってるAwayにしようと思ったが日本に発送できなそうでア

— うなすけ (@yu_suke1994) May 31, 2019

ただ公式では日本への発送はしておらず、BUYMAを利用して代理購入 → 日本への発送 という手続を踏みました。

購入したモデル

https://www.awaytravel.com/suitcases/bigger-carry-on-pocket/asphalt

これです。

Away

大きすぎず、小さすぎずちょうどいいサイズで気に入ってます。

2019年10月28日
2019年09月25日

Container Runtime Meetup #1でNOTIFY SOCKETについて話してきました

git grep NOTIFY_SOCKET

Container Runtime Meetup #1 - connpass に参加して NOTIFY_SOCKET について調べたことを話してきました。

この記事ではその内容の書き起こしと、その場で行われた会話についてのメモについて書きます。

参加するきっかけ

@udzuraさんにそそのかされたことがきっかけです。

@yu_suke1994⁩ 情報です https://t.co/U5zsvUlz30

— Uchio KONDO 🔫 (@udzura) August 27, 2019

あまりよくないExecuteをすると、普段はRails APIを運用していてRubyしか書いておらず、Goも数年前にCLI toolを作った程度で、GoやLinuxのコンテナ回りに詳しいという訳ではありません。

読む対象について

connpassのイベント概要に

少人数の輪読形式です。「参加枠1」の方々にはあらかじめ「runc run」周辺のコードをざっと読んできてもらい、当日、それに関連するトピックをそれぞれ発表して頂きます。「聴講のみ」の方々は、発表の必要はありません。

とあったので、その時点での最新リリースである v1.0.0-rc8 を対象に読むことにしました。

https://github.com/opencontainers/runc/releases/tag/v1.0.0-rc8

runc run が実行されたとき、呼び出される実体は run.go だろうとアタリをつけ、周辺を読んでいきます。

        status, err := startContainer(context, spec, CT_ACT_RUN, nil)
        if err == nil {
            // exit with the container's exit status so any external supervisor is
            // notified of the exit with the correct exit status.
            os.Exit(status)
        }

https://github.com/opencontainers/runc/blob/v1.0.0-rc8/run.go#L76

まずここで startContainer によりContainerがstartするものと思われます。中を見ていきます。

func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
    id := context.Args().First()
    if id == "" {
        return -1, errEmptyID
    }

    notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
    if notifySocket != nil {
        notifySocket.setupSpec(context, spec)
    }

https://github.com/opencontainers/runc/blob/v1.0.0-rc8/utils_linux.go#L405-L414

startContainer 内部、 411行目にて os.Getenv("NOTIFY_SOCKET") としている部分があります。この環境変数は何でしょう?気になったので、ここを掘っていきました。

この時点で僕の NOTIFY_SOCKET に対する認識は、

「環境変数がある状態で起動させると色々な通知が飛んでくるのだろうか?」

くらいのものでした。

Dive into code

では、 newNotifySocket で何が行われているかを見ていきます。

func newNotifySocket(context *cli.Context, notifySocketHost string, id string) *notifySocket {
    if notifySocketHost == "" {
        return nil
    }

    root := filepath.Join(context.GlobalString("root"), id)
    path := filepath.Join(root, "notify.sock")

    notifySocket := &notifySocket{
        socket:     nil,
        host:       notifySocketHost,
        socketPath: path,
    }

    return notifySocket
}

https://github.com/opencontainers/runc/blob/v1.0.0-rc8/notify_socket.go#L23-L38

この関数の返り値として、 notifySocket 構造体のインスタンス(のポインタ?)が得られます。

ちなみに notifySocket 構造体はこのように定義されています。

type notifySocket struct {
    socket     *net.UnixConn
    host       string
    socketPath string
}

https://github.com/opencontainers/runc/blob/v1.0.0-rc8/notify_socket.go#L17-L21

ところで newNotifySocket 関数の引数として渡される context *cli.Context は何でしょうか。

これはruncが採用しているCLIツール用パッケージ https://github.com/urfave/cli で定義されている Context 構造体を指しています

https://godoc.org/github.com/urfave/cli#Context

startContainer では、 newNotifySocket を呼んだ直後に、返ってきた notifySocket に対して setupSpec を呼んでいます。これも見ていきます。

// If systemd is supporting sd_notify protocol, this function will add support
// for sd_notify protocol from within the container.
func (s *notifySocket) setupSpec(context *cli.Context, spec *specs.Spec) {
    mount := specs.Mount{Destination: s.host, Source: s.socketPath, Options: []string{"bind"}}
    spec.Mounts = append(spec.Mounts, mount)
    spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", s.host))
}

https://github.com/opencontainers/runc/blob/v1.0.0-rc8/notify_socket.go#L44-L50

この関数のコメントにもあるように、systemdが何か関係していそうだということがわかりました。

ここでの処理は runtime-spec を読むとわかりそうです。とりあえず、Process.Env に対して NOTIFY_SOCKET 環境変数を追加しているようです。

startContainer に戻ると、 createContainer を呼んだあとに notifySocket.setupSocket() を呼んでいます。これを見ていきます。

func (s *notifySocket) setupSocket() error {
    addr := net.UnixAddr{
        Name: s.socketPath,
        Net:  "unixgram",
    }

    socket, err := net.ListenUnixgram("unixgram", &addr)
    if err != nil {
        return err
    }

    s.socket = socket
    return nil
}

https://github.com/opencontainers/runc/blob/v1.0.0-rc8/notify_socket.go#L52-L65

net.UnixAddr とは何でしょうか。これの実体は https://golang.org/pkg/net/#UnixAddr にあります。

type UnixAddr struct {
    Name string
    Net  string
}

ここでの unixgram は datagram socketを指すようで、UDPのような送りっぱなしのプロトコルのようです。 ( https://github.com/golang/go/blob/master/src/net/unixsock_posix.go#L16-L27syscall.SOCK_DGRAM を参照 )

net.ListenUnixgram によってconnectionを張り、それを notifySocket.socket に格納したものを、 runner 構造体の notifySocket フィールドに入れています。

type runner struct {
    init            bool
    enableSubreaper bool
    shouldDestroy   bool
    detach          bool
    listenFDs       []*os.File
    preserveFDs     int
    pidFile         string
    consoleSocket   string
    container       libcontainer.Container
    action          CtAct
    notifySocket    *notifySocket
    criuOpts        *libcontainer.CriuOpts
}

https://github.com/opencontainers/runc/blob/v1.0.0-rc8/utils_linux.go#L254-L267

この後に、 notifySocket に対して行われている操作は、runner.run 内部で以下のコードを呼んでいる部分があります。

// Setting up IO is a two stage process. We need to modify process to deal
// with detaching containers, and then we get a tty after the container has
// started.
handler := newSignalHandler(r.enableSubreaper, r.notifySocket)

https://github.com/opencontainers/runc/blob/v1.0.0-rc8/utils_linux.go#L305-L308

一旦まとめ

一旦ここまでをまとめると、

といったところでしょうか。

NOTIFY_SOCKET を調べる

何やらsystemdが関係していそうなことはわかっているので、単純に NOTIFY_SOCKET でググってみると、いくつか記事が見付かりました。

freedesktop.orgでは

These functions send a single datagram with the state string as payload to the AFUNIX socket referenced in the \$NOTIFYSOCKET environment variable. If the first character of \$NOTIFY_SOCKET is “@”, the string is understood as Linux abstract namespace socket.

https://www.freedesktop.org/software/systemd/man/sd_notify.html#Notes

「sd_notifyの通信方法 - Qiita」では

systemdのマネージャ(デーモンプロセス)は、起動プロセスの最後の方でsdnotifyという関数を用いて、起動が完了したことをsystemd本体(PID=1)に通知する。(注:sdnotifyは実際にはもっと汎用的なステータス通知に使える。)

https://qiita.com/ozaki-r/items/ced43d5e32af67c7ae04

ざっくりとした理解でいくと、プロセスが起動した、などのステータスの通知に使用されているんだろうということがわかりました。

Dockerの場合

ここで、「ではDockerの場合はどうなのだろう」と思い、 https://github.com/docker/cli 内をgrepしましたが、見当りません。いや、Dockerはmobyに移行したのでした。 案の定、 https://github.com/moby/moby 内をgrepすると見付かりました。

$ git grep NOTIFY_SOCKET
libcontainerd/supervisor/remote_daemon.go:200:  // clear the NOTIFY_SOCKET from the env when starting containerd
libcontainerd/supervisor/remote_daemon.go:203:          if !strings.HasPrefix(e, "NOTIFY_SOCKET") {
vendor/github.com/coreos/go-systemd/daemon/sdnotify.go:49:// If `unsetEnvironment` is true, the environment variable `NOTIFY_SOCKET`
vendor/github.com/coreos/go-systemd/daemon/sdnotify.go:53:// (false, nil) - notification not supported (i.e. NOTIFY_SOCKET is unset)
vendor/github.com/coreos/go-systemd/daemon/sdnotify.go:54:// (false, err) - notification supported, but failure happened (e.g. error connecting to NOTIFY_SOCKET or while sending data)
vendor/github.com/coreos/go-systemd/daemon/sdnotify.go:58:              Name: os.Getenv("NOTIFY_SOCKET"),
vendor/github.com/coreos/go-systemd/daemon/sdnotify.go:62:      // NOTIFY_SOCKET not set
vendor/github.com/coreos/go-systemd/daemon/sdnotify.go:68:              if err := os.Unsetenv("NOTIFY_SOCKET"); err != nil {
vendor/github.com/coreos/go-systemd/daemon/sdnotify.go:74:      // Error connecting to NOTIFY_SOCKET

https://github.com/moby/moby/search?q=NOTIFY_SOCKET&unscoped_q=NOTIFY_SOCKET

ということは、Dockerを使っているだけでそのようなsocketが作成されているのではないでしょうか。調べてみましょう。

Docker daemonが動作しているマシン上で、systemdが関係していそうなunixドメインソケットの数を出してみました。

$ ss --family=unix | grep systemd | wc -l
110

けっこうな数ありますね。grepする単語を変えてみます。

$ ss --family=unix | grep container
u_str    ESTAB   0  0     /var/snap/microk8s/common/run/containerd.sock 21833469  * 21836828
u_str    ESTAB   0  0     /var/snap/microk8s/common/run/containerd.sock 21836830  * 21836829
u_str    ESTAB   0  0        /var/run/docker/containerd/containerd.sock 23108     * 23106   
u_str    ESTAB   0  0        /var/run/docker/containerd/containerd.sock 23114     * 23113   
u_str    ESTAB   0  0        /var/run/docker/containerd/containerd.sock 23110     * 25748   

それらしきものが存在しています。

もっと見ていきましょう。dockerdのpidを調べます。

$ ps aux --forest # 抜粋
root  601  /usr/bin/dockerd -H fd://
root  769   \_ containerd --config /var/run/docker/containerd/containerd.toml --log-level info
root 1213       \_ containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/61f9489e17355a4f00594feb5c
root 1230           \_ bash

pid 601 の環境変数を見てみます。

$ sudo cat /proc/601/environ # 改行を入れています
LANG=ja_JP.UTF-8
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/var/lib/snapd/snap/bin
NOTIFY_SOCKET=/run/systemd/notify
LISTEN_PID=601
LISTEN_FDS=1
LISTEN_FDNAMES=docker.socket
INVOCATION_ID=e65738cc4b8f461e968d23c6740a557e
JOURNAL_STREAM=9:22835

確かに NOTIFY_SOCKET がありますね。

ここまでのまとめと今後の目標

ここまでがMeetupでの発表内容になります。

当日の議論

ここからは、当日の発表のあと、その場で行われた議論の簡単な書き起しになります。

2019年09月25日
2019年09月12日

フリーランスになっていました

社内でも僕しか持っていないTシャツ

2019年5月1日から、個人事業主として株式会社バンクで1日8時間の週4日間で稼動していました。

理由

「週4日労働にしたかった」 ためです。好きなようにコードを書いたり、映画など娯楽に興じたり、心身を養ったりする時間のために、もう1日休日が欲しいという気持ちは2019年になってから心のどこかにずっと引っかかっていました。

交渉の過程

ただ、フリーランスになるとは言ってもバンクを離れたくはありませんでした。当時から計画されていた新サービスの実装、まだまだ綺麗にし足りないコード、インフラ構成のキャッチアップ……やりたいことは山ほどありました。

なのでまずは人事の方に相談し、こちらの意向として

ということを伝えました。

結果としてですが、フリーランスとして契約をし直すということになりました。

最初に人事の方に「フリーランスを検討している」という意向を伝えたのが2月28日、面談を行ったのが3月8日、代表の光本さんと話したのが3月15日と19日、そこから総務の方と話し、有休の消化等の兼ね合いで、5月1日からフリーランスとして勤務する、という時系列になっています。

実力

その他、詳細や発表資料などについては https://unasuke.com/activity などにまとめてあります。また各種ポートフォリオサービスへのURLは以下になります。

これから

バンクでの契約が一区切りしたタイミングというのもあり、このタイミングで公開しました。一区切りしたとはいえ、契約更新のタイミングであったというだけで、まだバンクで引き続きお仕事をしています。

( ※ この日記は、フリーランスになった5月頃から、最初の契約更新のタイミングである11月1日に公開するつもりで下書きを作成していましたが、解散発表のタイミングで公開することにしました。 )

これからフリーランスとして仕事をしていくなかで、ずっとバンクだけと関わっていく訳ではないので、 興味のある方は声をかけていただけるとありがたいです。

(解散に伴なって色々あり、来年までは埋まっているかもしれません。状況を見つつ動きたいと思います。)

ただ、将来的に実家のある福井に戻りたいという展望があり、徐々に仕事をリモートで行えるようにしていきたいと思っているので、フルリモートが可能であると(より)嬉しいです。

また「交渉の過程」でも触れたように、「週4日勤務」という条件を満たせるのであれば正社員としてのオファーも嬉しいです。

参考文献

誰?

うなすけです。

2019年09月12日
2019年08月10日

pixivFANBOXの #クリエイター優待 でOKAMURAのバロンを買いました

ありがとうございます

pixivFANBOXのクリエイター優待 で

FANBOXで支援者数が50名以上いる方を対象に特別価格で提供していたOKAMURAのオフィスチェアですが、この度なんと!対象を「支援者数10名以上」に緩和して再開しました 🎉🎉
今まで欲しくても手が届かなかった方はこの機会にぜひチェックしてみてください! #クリエイター優待https://t.co/qaektxPHCB

— pixivFANBOX公式 (@pixivFANBOX) July 1, 2019

#pixivFANBOX やっててよかった!!

— うなすけ (@yu_suke1994) July 1, 2019

毎月支援していただいている皆様、本当にありがとうございます。

バロンへの思い入れ

前職、spice life時代には、好きな椅子を何でも一つ購入できるという制度があり、覚えているだけでも社内にアーロンチェア、エンボディチェア、バロン、コンテッサがありました。

それらオフィスチェアのなかでも、僕の身体にはバロンがしっくりくるようで、いずれは自宅にもバロンを……という目標をその頃から立てていました。が、いわゆる高級オフィスチェアなのでなかなか手が出ずにいました。

そこにpixivFANBOXでのクリエイター優待としてオカムラのオフィスチェアを購入できるようになり、ついにこの時が!となったものの、公開当初は50人以上の支援がないと利用できず、僕は対象外で悔しい思いをしたものです。

【お知らせ】
FANBOXの支援者を集めているクリエイターを対象に!
なんと…!

OKAMURAのオフィスチェアを特別価格で購入できるようになりました〜 🎉

日頃の創作活動により集中できるように、普段より良い椅子を使ってみませんか??詳細はこちら↓↓ #クリエイター優待https://t.co/qaekty7iu9

— pixivFANBOX公式 (@pixivFANBOX) April 9, 2019

そして、今自宅で使っている椅子は、どこで買ったのかも覚えていない1、上京したとき購入したものです。座面はへたってほぼ板、というかネジか何かが座面に飛び出している、軋みがうるさい、腰が痛くなる……4年ちょっと使っている安物なので、こんなものでしょう。しかしストレスが溜まっていきます。

たぶんニトリの椅子

(部屋が汚ない)

そんなタイミングで、冒頭にあるようにOKAMURAのオフィスチェア優待が10人以上の支援者からでも申し込み可能と知ったときの嬉しさったらありません。

届いた

8月1日に申し込んで、8月9日に自宅に届きました。

オカムラ バロン

(部屋が汚ない)

最高の座り心地ですね。やはり良い椅子は良いです、ありがとうpixivFANBOX。ありがとう支援者の皆さん。重ねて御礼申し上げます。

旧椅子の処分

さて、注文時のオプションに「椅子引き取り」というものがあり、使用中の椅子を引き取ってくれるとのことで、これを有効にして注文しました。

しかし配送の方には「そんなのは聞いていない」と言われてしまい、アレ~となりました。注文確認メールにはしっかり「椅子引き取り」のオプションが有効になった状態になっている2のですが……まあ配送の方を詰めても何もいいことはない3ので、そのまま帰っていただき、こちらで処分することにしました。

我が家には2週間に1回くらいのペースで廃品回収のチラシが来るのですが、ある程度の見積りは書いてあるものの、やはり呼んでから法外に請求されるのは怖いので、行政の廃品回収をお願いすることにしました。行政だと値段が事前にわかるので最高、安心ですね。

そのかわり、狭い部屋に二脚の椅子でより狭いという状況がしばらく続きそうです。


  1. おそらく https://www.nitori-net.jp/store/ja/ec/Chair/WorkChair/6620437-6620439s の旧モデル 

  2. 郵送で届いた領収書にも「椅子引き取り」の文字がしっかり記載されていました……何? 

  3. でも服にOKAMURAのロゴが入ってたし社員かもしれなかったのでもうすこし追求してもよかったかもしれない? 

2019年08月10日
2019年08月06日

Railsの config/routes.rb の内容からOpenAPIのpathsの定義を生成する

GitHub

OpenAPIによる定義から実装を生成したいニーズはあり、その方法は存在します。

「スキーマファースト開発」という言葉もあるように、一般的にはREST API schemaを定義してから実装にとりかかります。

しかし、様々な事情で「既存のREST API実装に対してOpenAPI schemaを記述したい」というニーズがあります。

例えばRailsの config/routes.rb の内容から OpenAPI の paths に相当するYAMLやJSONを出力するようなgemがあると助かるのですが、rubygems.org を “openapi” で検索してもそれらしいgemは見当りませんでした。

なので、そういうgemをつくりました。

実装にあたって

※ 以下、特記していない場合には Rails v5.2.3 時点のコードになります。

実装にあたって、まず参考にしたのがお馴染み bin/rails routes の処理になります。このとき何が行われているのでしょうか。

bin/rails routes で実行されるコードは以下です。

# frozen_string_literal: true

require "optparse"

desc "Print out all defined routes in match order, with names. Target specific controller with -c option, or grep routes using -g option"
task routes: :environment do
  all_routes = Rails.application.routes.routes
  require "action_dispatch/routing/inspector"
  inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)

  routes_filter = nil

  OptionParser.new do |opts|
    opts.banner = "Usage: rails routes [options]"

    Rake.application.standard_rake_options.each { |args| opts.on(*args) }

    opts.on("-c CONTROLLER") do |controller|
      routes_filter = { controller: controller }
    end

    opts.on("-g PATTERN") do |pattern|
      routes_filter = pattern
    end

  end.parse!(ARGV.reject { |x| x == "routes" })

  puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, routes_filter)

  exit 0 # ensure extra arguments aren't interpreted as Rake tasks
end

https://github.com/rails/rails/blob/v5.2.3/railties/lib/rails/tasks/routes.rake

ここでの本質は

inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)

puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, routes_filter)

の2行でしょう。

では、 ActionDispatch::Routing::RoutesInspector は何でしょう。

##
# This class is just used for displaying route information when someone
# executes `rails routes` or looks at the RoutingError page.
# People should not use this class.
class RoutesInspector # :nodoc:
  def initialize(routes)
    @engines = {}
    @routes = routes
  end
  ...sinp

https://github.com/rails/rails/blob/v5.2.3/actionpack/lib/action_dispatch/routing/inspector.rb#L54-L127

はい、private API ですね。

この RoutesInspector に適切なFormatterを渡して、routesの結果を整形すればよさそうです。

では ActionDispatch::Routing::ConsoleFormatter を見てみます。

class ConsoleFormatter
  def initialize
    @buffer = []
  end

  def result
    @buffer.join("\n")
  end

  def section_title(title)
    @buffer << "\n#{title}:"
  end

  def section(routes)
    @buffer << draw_section(routes)
  end

  def header(routes)
    @buffer << draw_header(routes)
  end

  def no_routes(routes)
    @buffer <<
    if routes.none?
      <<-MESSAGE.strip_heredoc
      You don't have any routes defined!

      Please add some routes in config/routes.rb.
      MESSAGE
    else
      "No routes were found for this controller"
    end
    @buffer << "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
  end

  private
  # ...snip

https://github.com/rails/rails/blob/5.2.3/actionpack/lib/action_dispatch/routing/inspector.rb#L129-L185

RoutesInspectorと同様に(明記されていませんが)これもprivate APIでしょう。

少し下に /rails/info/routes で使用される HtmlTableFormatter も定義されており、それと見比べると、 resultsection_titlesectionheaderno_routes を定義した独自のFormatterを作成すればよさそうに見えます。

OpenAPI v3 の記法

さて、 OpenAPI v3 では、以下のような記述をするよう、仕様で定義されています。

openapi: 3.0.2
info:
  title: example
  description: OpenAPI example
  version: 0.1.0
servers:
  - url: http://api.example.com/v1
    description: example server
paths:
  /users:
    get:
      summary: get users
      description: Return all user list
      responses:
        '200':
          description: users json
          content:
            application/json:
              schema: 
                type: array
                items: 
                  type: string

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md

これらの定義のうち、 paths 以下のいくつかについては、 config/routes.rb から自動生成できそうです。

なので、以下のようなFormatterを作成すると、それらしい定義を生成できます。

module ActionDispatch
  module Routing
    class OpenAPI3Formatter
      def initialize()
        @view  = nil
        @buffer = []
        @openapi_structute = {
          'openapi' => '3.0.0',
          'info' => {
            'title' => '',
            'description' => '',
            'version' => '0.1.0'
          },
          'paths' => {}
        }
      end

      def section_title(title)
      end

      def section(routes)
        routes.filter do |r|
          !r[:verb].empty?
        end.each do |r|
          @openapi_structute['paths'][r[:path]] ||= {}
          @openapi_structute['paths'][r[:path]][r[:verb].downcase] = {}
          @openapi_structute['paths'][r[:path]][r[:verb].downcase] = {
            'summary' => r[:name],
            'description' => r[:reqs],
            'responses' => nil
          }
        end
      end

      def header(routes)
      end

      def no_routes(*)
      end

      def result
        YAML.dump @openapi_structute
      end
    end
  end
end

https://github.com/unasuke/openapi3_definition_generator-rails/blob/3973f11c50a1ccdc69c1d97fce502222ecd92870/lib/openapi3_definition_generator/rails/openapi3_formatter.rb

gemify

そして、それをgemにしたのがこれです。

https://github.com/unasuke/openapi3_definition_generator-rails

使いかたはREADMEにあるとおり、Gemfileに追記して bundle installした上で、 $ bin/rails openapi3_definition:generate_yaml

雑に表参道.rbで話したのが、これです。

omotesandorb #49

上で述べたように、内部でPrivate APIにしっかり依存しているので、いつ動かなくなるかは保障できず、そのため Rails v5.2.3 以上 v6 未満でしかインストールできないようになっています。Pull Requestは大歓迎です。

今後

今後、実装するとしたら

くらいと、あとは表参道.rbでもアドバイスを頂いたように、Rails本体への機能追加も考えています。

2019年08月06日
2019年07月08日

異常独身男性エントリ

abnormal bachelor

オタク!退職エントリより
異常独身男性エントリ書いて!

— 衰咲ふち💬アーバンキャット (@otoroesaki) July 2, 2019

経緯

「異常独身男性注意マスキングテープ (マスキングテープ - テープ幅 15mm)」を 限界集落 で購入しました! https://t.co/7Z8WodNZtK #booth_pm

— HolyGrail / 蜘蛛糸まな🕸️@新人VTuber (@HolyGrail) June 17, 2019

異常独身男性にマスキングテープ貼り付けるいたずらをやっていこうと思います

— HolyGrail / 蜘蛛糸まな🕸️@新人VTuber (@HolyGrail) June 17, 2019

左隣に座ってる同僚には絶対貼りたい

— HolyGrail / 蜘蛛糸まな🕸️@新人VTuber (@HolyGrail) June 17, 2019

ふちちゃん、一枚セールスしたわよ! pic.twitter.com/sgvFb7L114

— HolyGrail / 蜘蛛糸まな🕸️@新人VTuber (@HolyGrail) June 17, 2019

アアアアアーーーアアアアアーアーアーアーアーアーアアアーーーーーアアーア

異常独身男性:背面有〼 Tシャツ ブラック Mを買ったよ。 https://t.co/kWBTbhCXKp #suzurijp @suzurijpさんから

— うなすけ (@yu_suke1994) June 17, 2019

そして

pic.twitter.com/HcTk2jQFtU

— うなすけ (@yu_suke1994) July 5, 2019

異常独身男性 pic.twitter.com/WLL3xaBUUg

— HolyGrail / 蜘蛛糸まな🕸️@新人VTuber (@HolyGrail) July 8, 2019

異常独身男性 pic.twitter.com/GYMkDIDBO4

— HolyGrail / 蜘蛛糸まな🕸️@新人VTuber (@HolyGrail) July 8, 2019

絶対に嫌シリーズ

うなすけくんには異常独身男性界を背負って立ってほしい

— 衰咲ふち💬アーバンキャット (@otoroesaki) June 23, 2019

今生まれ出づる異常独身男性ニューエイジ
うなすけ

— 衰咲ふち💬アーバンキャット (@otoroesaki) June 28, 2019

独身男性の皆さん買いましょう

衰咲ふちの限界集落で異常独身男性シリーズが発売されたわよ! #衰咲

異常独身男性:背面有〼Tシャツ https://t.co/jk7jWiaztm

異常独身男性ステッカー(COLOR) | 限界集落 https://t.co/MIDepETLUe

異常独身男性マスキングテープ | 限界集落 https://t.co/wEfJrhzywj pic.twitter.com/7csRAsAb5E

— 衰咲ふち💬アーバンキャット (@otoroesaki) June 15, 2019

カラー色々!選べる異常独身男性!
ブルーカラーもホワイトカラーも一緒に仲良く異常独身男性着ようね!(皮肉) pic.twitter.com/zkiVFOaoZb

— 衰咲ふち💬アーバンキャット (@otoroesaki) June 15, 2019
2019年07月08日
2019年06月25日

Fortitude60を組みました

Fortitude60

経緯

2018年10月頃のgroup buyから積みっぱなしになっていたFortitude60を、重い腰を上げてようやく組み立てました。

Fortitude60 group buy round1

発端は、友人の @nikuzuki_29 が職場で布教された結果「自作キーボードを組んでみたい」という気持ちになり、では僕も、とスイッチ、キーキャップを揃えて組み立てることにした、という流れになります。

構成

組み立て

肉好きが初心者であることから、一日中遊舎工房のフリースペースを使うつもりで早起きして秋葉原に行き、組み立てを開始しました。(事実、閉店まで居ました)

僕も久々の半田付けであることから、まずはmeishiを各々組み立てて慣れておく作戦をとったのが良かったです。

遊舎工房で1日かけて組み立てたのですが、その場でArch LinuxからFirmwareを焼こうとするとエラーになってしまいました。レンタルスペースで焦っていたのもあったのでその場での解決は諦めましたが、帰宅後プルリクを出しmergeされました。

✌️

Install avrdude in Arch or Manjaro Linux by unasuke · Pull Request #6132 · qmk/qmkfirmware https://t.co/qhmxedwFbM

— うなすけ (@yusuke1994) June 16, 2019

CとHを取り違えたので、若干ヒビが入ってしまいました。

fortitude60(ヒビ)

トラブル

右手側が全く反応しません。テスターを買ったので、追い追い時間を見つけて調査と修正、あるいは再度購入をしていこうと思っています。

これから

適当なTai-HaoのキーキャップとBlank keycapsを購入したのですが、キーごとに高さが違う構成なので、PimpMyKeyboardで改めて好みのを買おうと思っています。またTRRS、USBケーブルも味気ないので買おうかなと考えており、沼感が出てきました。

2019年06月25日
新しい投稿
古い投稿