satoryuの日記

忘れっぽいから覚えてるうちに書いておかないと。

Typetalkに通知を送るGitHub Actionを作った: Typetalk Notify

無料プランがあるということでなんとなくTypetalkを使い始めてみた。 Slackなんかと同じようにAPIやWebhookなどがありカスタマイズが可能で、GitHubとの連携もサポートされている。

GitHubと連携することで、ブランチのプッシュ、Issueやプルリクエストの作成・変更が起きる度に通知がTypetalkのトピック(Slackのチャネルに相当)に届くようになる。

しかし、リリースなどどうやらサポートされていないイベントが一部あり、またメッセージをカスタマイズしたいことがある。 ということで、GitHub Actionsを作ってみた。

github.com

使い方

オプションについてはREADMEを参照してほしい。 同じREADMEに記載されているworkflowファイルの記述例を載せる。

name: Release announce

on:
  release:
    types: [published]

jobs:
  steps:
    - uses: satoryu/typetalk-notify@v0.0.1
      with:
        token: ${{ secrets.TYPETALK_TOKEN }}
        topic_id: 165952  # Replace your topic's id
        message: "${{ github.repository }} ${{ github.ref }} has been released :tada: :white_flower:"

この設定ファイルをリポジトリ.github/workflows/ ディレクトリ以下にrelease_announce.yml などのようにして保存する。 その後に、release tagを作ると、以下のような通知がTypetalkのトピックに投稿されるようになる。

f:id:satoryu:20200331104506p:plain

上に書いた設定を変更すれば、push時やissue作成時など別のGitHub上のイベントが発生した時に好みのメッセージを投稿することができる。

おまけ: GitHub Hackathon にエントリー

ちなみに、3/31までGitHub Hackathonが開催されていて、GitHub Actionsがテーマになっている。 ちょうど良かったので今回作成したActionでエントリーしてみた。

githubhackathon.com

Rails のGeneratorのデフォルト値はどこから来るのか

ことの発端

Rails のController generator で不要なrouting が書き込まれてしまうので、毎度--skip-routes オプションを付けるようにしている。 けれど、オプションを付け忘れることもあって、面倒なことになることがよくあった。 調べてみると、 config/application.rb などでconfig.generators を以下のように指定することでデフォルトのオプションとして指定できることがわかった。

config.generators do |g|
  g.skip_routes = true
end

github.com

該当の変更だけを見ても、仕組みが理解できなかったので調べてみた。

TL;DR

controller generator にオプションの渡し方は、以下の3通り。

  1. config.generators.controller = { skip_routes: true }
  2. config.generators.rails = { skip_routes: true }
  3. config.generators.skip_routes = true

個別のgeneratorのためのオプションとして最優先されるのは1の設定の仕方。 2と3は同等の扱いで、他のgeneratorとも共有される。

探求編

コードを読んでいく方針として次の順で行った。

  1. 参照側: ControllerGeneratoroptions[:skip_routes] はどこを見ているのか
  2. 設定側: Rails::Application.config.generators に指定した値はどこに入るのか
  3. 上記2つはどこでつながるのか

参照側

module Rails
  module Generators
    class ControllerGenerator < NamedBase # :nodoc:
      argument :actions, type: :array, default: [], banner: "action action"
      class_option :skip_routes, type: :boolean, desc: "Don't add routes to config/routes.rb."
     # 
     # 中略
     # 
      def add_routes
        return if options[:skip_routes]
        return if actions.empty?
        routing_code = actions.map { |action| "get '#{file_name}/#{action}'" }.join("\n")
        route routing_code, namespace: regular_class_path
      end

add_routes メソッド内で、options[:skip_routes] を見ているので、options がどこから来るのかを探る。 ControllerGenerator 自身は持っていないので、継承しているNamedBaseを見てみるがこれもoptionsを持っていない。更に上位クラスのRails::Generators::Baseを見ると、これがThor::Group を継承していることがわかる。 RailsのgeneratorなどCLIは、CLIを作成するDSLを提供するthorを使用している。 Thorのドキュメントを見ると、Thor::Group がincludeしているモジュールThor::Baseoptionsメソッドが存在していることがわかる。

ControllerGeneratorのコードを再度見ていくと、skip_routesオプションをclass_option で定義している。 これもまたthorに存在するメソッドである。しかし、デフォルト値を指定するためのdefaultをキーに持つハッシュが引数に渡されていない。 Rails::Generators::Base を見ると、class_option メソッドをオーバーライドしている箇所がある。

      def self.class_option(name, options = {}) #:nodoc:
        options[:desc]    = "Indicates when to generate #{name.to_s.humanize.downcase}" unless options.key?(:desc)
        options[:aliases] = default_aliases_for_option(name, options)
        options[:default] = default_value_for_option(name, options)
        super(name, options)
      end

これは、Thor::Base.class_optionsuperで呼ぶ前に、デフォルト値を設定している。 default_value_for_optionは以下のようになっている。

        def self.default_value_for_option(name, options) # :doc:
          default_for_option(Rails::Generators.options, name, options, options[:default])
        end

さらにdefault_for_optionを呼び出している。

        def self.default_for_option(config, name, options, default) # :doc:
          if generator_name && (c = config[generator_name.to_sym]) && c.key?(name)
            c[name]
          elsif base_name && (c = config[base_name.to_sym]) && c.key?(name)
            c[name]
          elsif config[:rails].key?(name)
            config[:rails][name]
          else
            default
          end
        end

これは以下のようにデフォルト値を決めている。

  1. config(ここだとRails::Generators.options)の中にgenerator名(ここだとcontroller)をキーとして持っていて、なおかつその値がハッシュでname(ここだとskip_routes)をキーとして持っていると、その値をデフォルト値とする
  2. config の中にbase_name(ここだとrails)をキーとして持っていて、なおかつその値がハッシュでnameをキーとして持っていると、その値をデフォルト値とする
  3. configの中にシンボルrailsをキーとして持っていて、なおかつその値がハッシュでありそのハッシュがnameをキーとして持っている場合、その値をデフォルト値とする
  4. 以上のどれにも当てはまらない場合、引数のdefaultをデフォルト値とする

つまり、Rails::Generators.options がどのようになっているのかで決まる。 この順番で先に見つかった値が使われる。

設定編

Rails Guideに、Generatorの設定についての記述があり、そこに下記のサンプルコードがある。

config.generators do |g|
  g.orm :active_record
  g.test_framework :test_unit
end

Rails::Engine::Configuration#generatorsを見ると、

      def generators
        @generators ||= Rails::Configuration::Generators.new
        yield(@generators) if block_given?
        @generators
      end

Rails::Configuration::Generatorsをブロックに渡している。 Rails::Configuration::Generatorsmethod_missing が定義されていて、任意の名前で設定値を与えることができる。 下記のように4通りの記述が可能である。

  1. config.generators.controller = { skip_routes: true }
  2. config.generators.controller skip_routes: true
  3. config.generators.rails = { skip_routes: true }
  4. config.generators.skip_routes = true

1、2は同じ扱いで、controller generator固有のオプションを設定している。 3、4もまた同じ扱いで、controller generatorのオプションのデフォルト値だけでなく他のgeneratorからも参照される可能性がある。 「可能性がある」というのは、先述のようにgenerator固有の設定(つまり上の1、2で指定された値)を優先するためである。

どこでつながるのか

これで一件落着のように感じるが、Rails::Generators.optionsの中を見ると、以下のようにDEFAULT_OPTIONSにあるハッシュを返しているだけである。

      def options #:nodoc:
        @options ||= DEFAULT_OPTIONS.dup
      end

rails generatorコマンド実行時にRails::Configuration::Generators に設定された内容をRails::Generators.options に入れている。

rails/generate_command.rb at c0d91a4f9da10094ccdb80e34d1be42ce1016c9a · rails/rails · GitHub

このload_generatorsRails.application.load_generatorsを呼んでいるだけのメソッドである。 Rails.application.load_generators の実体はRails::Engine#load_generators で、この中で下記を実行している。

Rails::Generators.configure!(app.config.generators)

引数のapp.config.generators は、設定編で解説した設定で生成したRails::Generatorsオブジェクトである。 レシーバーのconfigure!の中で、Rails::Generators.optionsの値に、Rails::Configuration::Generators.optionsの値をmergeしている。

ということで、このように設定した値がController Generatorから参照されていることがわかった。 Generatorを自分で作成する必要がありそのGenerator固有のオプションを持つ場合、そのオプションのデフォルト値はconfig.generators.my_generator = { my_option: 'foobar' } といったように作成したgenerator名に値を与えることで設定できる。

docx v0.5.0 をリリースした

Wordなどで作られるOffice Open XMLファイルをRubyで扱うためのライブラリdocx のコミット権を昨年末にもらった。 今の所、既にあるIssueを整理し、既に出ていたプルリクエストをmergeできるものから取り込んでいった。 それらをまとめてv0.5.0としてつい先日リリースしたので、ここで今回のリリース内容を紹介します。

https://github.com/ruby-docx/docx

機能拡張

Added opening streams and outputting to a stream #66

これまではdocx形式のファイルしか扱えなかったのですが、このバージョンからストリームを扱うことができるようになりました。

Added supports for Office 365 files #85

前置きをしておくと、docxファイルはXMLファイルをzip圧縮したものです。展開してできたXMLファイルの構成やスキーマがOffice Open XMLで決められています。

通常であればdocument.xmlに本文のデータが保存されるのですが、Office365が生成するdocxファイルだとdocument 2.xmlに保存される事が報告されました。 そのようなファイルもこのリリースから扱えるようになりました。

バグ修整

  • Docx::Document handles a docx file without styles.xml #81

たまにプレインテキストのように、何の装飾もされていない文書があるようで、そういったファイルにはそれを記載するstyles.xmlが含まれていない事がわかりました。 これまではそのようなファイルを読み込もうとするとエラーになってしまったのですが、このリリースから問題なくその他の文書データを読み込めるようになりました。

  • Fixes insert text before after were switched #84

文書のブックマークを指定し、その前後にテキストを挿入するメソッドがそれぞれあったのですが、メソッド名とやっていることが逆だったので直しました。

バグ、リクエストの報告先

GitHubで受け付けています。 何かお困りの点がありましたら下記からIssueをオープンして下さい。

https://github.com/ruby-docx/docx/issues/new/choose

Agile PBL祭りに行ってきた。 #agilepbl

個人スポンサーとしてエントリーしていたAgile PBL祭りに参加してきた。

agilepbl.com agilepbl.connpass.com

個人スポンサーした経緯についてはこちら。 satoryu.github.io

Twitterで一時トレンド入りした #agilepbl のまとめ togetter.com

北は北海道、南は沖縄から、プロジェクトベースドラーニング(PBL)でこの1年間チーム開発をしてきた学生の方々の発表とブースでのデモを拝見させてもらった。 「祭り」という名にふさわしく、ブースの準備段階からすでに賑やかな雰囲気が出ており、「楽しい」雰囲気で会場が終始満ち溢れていたように感じた。

f:id:satoryu:20200221100439j:plain

プレゼンがとてもうまく、デモも楽しそうにプロダクトを見せてくれた。 いずれのチームも話を聴いていると、プロダクトで解決したい課題が、メンバー自身が当事者であるということが共通していた。 「こういうものがあると便利そう」といったものではなく、自分たち自身でプロダクト自体を評価できるので、どのチームも自分たち自身で使う、ということを当たり前のようにやっていた。 なぜそこにこのボタンがあるのか、など聞いていっても、どういうシチュエーションでどういうことのためなのか具体的に説明してくれるチームばかり。 そんな人、なかなか開発の現場で見たことないぞ… 良いものを想像して作ることをあたかもすごいことだと思っていたりするPOやステークホルダーがいたりする。 「きっとこういう人がいて、こういうシチュエーションがもしあったら」という前提条件ばかりが多すぎて、作る意味があるのかがわからない。検証するにも、そういうユーザーがいるのかどうかも探しづらいような状況がある。 きっと、彼らは自分たちで色々試せる環境を作れたのだろうな。とても素晴らしい。 あるチームのデモブースでプロダクトを見せてもらった時、「実際に彼(PO)が普段使っているんです」と聞いて、素晴らしすぎると思った。 え?当事者じゃないですか。

懇親会で学生と話を聞いていると、始めからチーム開発をやりたいという思いはなく、「単位のため」といった学生としては当たり前の目標だったようだ。 けれどもチームで活動をしていく中で、お互いのことがわかるようになり、この活動自体をとても楽しいと思えるようになっていったという。 終いには、「単位もらえなくてもいいから作りきりたい」とか「単位なしで来年も履修したい」とか言っていた。 途中で脱落してしまう学生も何人かいたようだったが、残った学生たちは単位だけでない何か価値を見出したんだろうな。 話の中で、「グループからチームになれた」といったことがサラッと出てきた。 チームで意見が対立していても、プロダクトを良くしたいという同じ想いが共有できてたので、安心して意見をぶつけ合えていた。って言ってた。え、ちょっと待って、それって最近悲しくもバズワード入りしちゃった心理的安全性さんの本来の姿ですよね? きっと彼らが得た単位以外のものの1つは、チームなんだろう。 そりゃ、楽しいだろうな。

懇親会では、昨年度の履修生で今年度はTAと今年度の履修生とで話をしているのを近くで聞いていた。 今年度のチームでの活動の中で感じたことを今年度の学生が語ると、それについて大きく頷くTA。 良い授業が再現されていることがここで証明されている気がした。 TAになった彼は、チームでの活動が楽しくて、今度はそんなチームを支援する側に立ちたかったらしい。 その話を今度は履修生が大きく頷く。 この世代交代の流れ、もしかして無限に続けられるのでは?

日中のプレゼンやデモブースもそうだったんだけど、懇親会会場*1の風景を見ていると、どこに先生がいるのかわからないくらいに溶け込んでいた。 PBLは、作り固められた授業のコンテンツを提供するようなことは難しいので、先生たちの中にも試行錯誤がたくさんあったのだと思う。 その都度、先生たち自身が悩んでいる場面を学生たちの前で見せていたようだ。 それを見て、授業を良くするための意見を言うと先生たちは聞いてくれる。 その経験が学生たちにとって新しい体験だったようで、自分の授業を良くするということを考えた、なんてことを教えてくれた。 学生が先生と同じ目線に立って授業を良くすることを考える、ってどんだけ運営すごいの?

残念ながら、それぞれのプロジェクトは終わってしまった。 でも、どのチームの人たちもとても楽しく自分たちのチームのこと、プロダクトのことを語っていた。 「まだ続けたい」という悔しい気持ちを残しつつ終えてしまう。 チーム活動から生まれた関係というのはそうそう崩れるものではないと思うので、また別のプロダクト開発でもプロダクト開発以外でも何かやってみると面白いことになるんじゃないかな、って思う。

残念なことに、enPITというプロジェクトは来年度までということらしい。 なので、このAgile PBL祭りのような場の元になるPBLを実施する土台が来年度で無くなってしまう。 「弊社、アジャイル開発やってます!」って普段言ってるオジサマ・オバサマ達は、この取組が継続できるような支援をしてあげてほしい。 学生のひとたちにとって、どの会社がアジャイル開発をやっているかは知らない。 なので、採用の観点として、この授業を支援することで会社の認知度を上げる、というのはいかがだろうか。 もちろん、金は出すが口は出さないままで。 他にも企業なり自治体なり石油王なりがこの授業を支援するためのアイデアがあったら、ぜひ渡辺先生にお伝えしてほしい。 ちなみに、作ったプロダクトのソースコードなんかは、権利上問題無ければ、ぜひGitHubなどでOSSとして公開してみてはどうだろうか。緩やかに開発を続けるためにも、もしかしたら知らないところで使いたい人が見つかるかもしれないので。

さーて、きっと今頃先生たちはロスってるんだろうな。

*1:そんな大層な場所ではなく、居酒屋のこと

UglifyjsWebpackPluginが非推奨になったので乗り換えた

Vue.js やらWebpackやらPWAを勉強するために小さいアプリケーションを作っていたのだが、それがいつの日からかリリース時のビルドのみ失敗するようになった。

TL;DR

修正したPull Requestはこちら。 github.com

何に乗り換えるか

そもそもUglifyjsWebpackPluginは、2ヶ月ほど前に非推奨(deprecated)になっているのを見つけた。 GitHub - webpack-contrib/uglifyjs-webpack-plugin: [deprecated] UglifyJS Plugin

それを継ぐ選択肢として、terserを用いたterser-webpack-pluginがある。 しかし、そもそも今作っているアプリではBabelを使っていたので、依存をなるべく減らすためにもbabel-preset-minifyに乗り換えることにした。

使い方

まずはUglifyjsWebpackPluginをアンインストールし、babel-preset-minifyをインストールする。

npm uninstall -D uglify-webpack-plugin
npm install -D babel-preset-minify

次にWebpackの設定ファイルを以下のように変更する。

  module: {
    rules: [
      {
        include: [path.resolve(__dirname, "src")],
        loader: "babel-loader",
        options: {
          plugins: ["syntax-dynamic-import"],
          presets: [
+            "minify",    # <= 追加
            [
              "@babel/preset-env",
              {
                modules: false
              }
            ]
          ]
        },

        test: /\.js$/
      },

単に、babel-loaderのオプションの1つであるoptions.presets"minify"を追加するだけだ。 もし、babel-preset-minifyのオプションを指定したい場合、"minify"の代わりに以下のようなオブジェクトを渡す。

[
  "minify",
  {
    "removeConsole": {
      "exclude": ["error", "info"]
    }
  }
]

ちゃんとwebpack --mode=productionのときだけminifyしてくれるので、十分使えそう。

参考

Azure クラウド基礎講座に参加して、AZ-900を取得した

f:id:satoryu:20200206191808p:plain

今年は、色々と真面目に技術のことを勉強したいと思い、資格試験を受けてみようと思っている。 そこで、以前からMicrosoftさんが頻繁に開催している基礎講座に参加して、AZ-900 Azure Fundamentals を取得しました。 参加するだけで資格受験のための無料バウチャーも貰えるので、費用は完全無料でした。

microsoft-events.connpass.com

基礎講座について

これは9時30分から16時ころまでの講義形式のセミナーです。 講師はマイクロソフト社員の方*1。 内容は、もちろん資格試験Azure Fundamentalsをカバーしており、なおかつ試験対策についても教えていただけました。

また、資格の詳細のページに行くと、その資格勉強のためのMicrosoft Learnのコンテンツへのリンクがあります。

docs.microsoft.com

それらも予習として予めサラッと気になるところを流して見ていました。 当日の講座の内容とおそらくほぼ同等だと思います。

次の目標

Azure Fundamentalsは、その名前の通りに、基本的なことを理解しているかどうかの資格です。 次は、開発者として、Azure Developer を狙ってみようかと思います。

docs.microsoft.com

*1:遅刻して参加したのでお名前を聞けなかった。

GitHub Sponsors に登録した。

タイトルの通りGItHub Sponsorsに登録した。

GitHub Sponsorsとは

GitHubスポンサーのことではなく、GitHub上で自身のスポンサーを募ることができるサービスがGitHub Sponsors。

help.github.com

スポンサーするにはGitHubアカウントがあればできるが、スポンサーを受けるには審査が必要になる。 特に難しいことはなく、身分証明書と税金の支払いに関する情報を入力し、審査を待つだけだった。 自分の場合、身分証明書には運転免許証を使用した。 米国からの支払いの受け取りになるので、W-8BENという書類を作成しなければいけない。 これもオンライン上で作成できる。 Foreign Tax Identification Numberの項目に何を入れればいいのかわからず少しハマってしまった。どうやらこれは、マイナンバーの番号を入力すれば良いらしい。

スポンサーは対象のユーザーに月額定額でスポンサー費を支払うことができる。 この定額のプラン(Tier)を作成することができる。

なぜ登録するのか

自分の場合、OSS開発は、自分が書くコードで世の中に何か貢献したいという気持ちでやっていることが多い。 けれども、時間を割いている活動であるため、そこに投資できるには限りがある。 ましてや、仕事や家庭が忙しい時期に、このようなプライベートでの活動は優先順位が下がりがちになったりする。 そこで金銭というものが、OSSの開発をするためのモチベーションの1つになるのかと思った。 このスポンサーという仕組みを使うと、どのようにOSS開発が促進されるのか自分自身で試してみたいと思い、登録してみた。

ということで、ご支援してくださる方、お待ちしております。

github.com