rails 6.0.3 で api mode にした時の diff

主な違い

  • frontend 関連のファイルがごっそり消えている
  • sass や cabipara など、rails での view 出力に関わるライブラリが消えている
  • view に関わるファイルも消えている
  • config/application.rb での設定が変わっている
    • api サーバーに必要な gem のみ include するようになっている
    • config.api_mode = true になっている
  • rack-cors がデフォルトで入っている

以下から全ての diff が見れる。 https://github.com/nekootoko3/rails-diff/pull/1/files

rack-cors から CORS を理解する

web の仕様を理解するにはドキュメントを読み、その仕様の実装を見てみるのが良い方法だと思う。
ruby の CORS の制御を実装している rack-cors から CORS を理解する。

CORS について

  • Cross Origin Resource Sharing とは、異なるオリジン(protocol + port + domain)間で、リソースを認可するための仕様
    • 主に XMLHttpRequest または Fetch API を呼び出し時に利用されることが多い(つまり JavaScript を利用したサーバーとの通信全般)
  • この仕様の全体についてはまとめないので下のような記事を読むと良さそう
  • Simple Request と Preflight Request に関しては、自分もあまり覚えていなかったのでまとめる
  • Simple Request
    • サイト間リクエストがユーザーデータに影響を与えないようなリクエス
    • CORS プリフライトを伴わないリクエストで、 MDN のドキュメントによると以下 5 条件が満たされているリクエストのこと。
      • 許可されているメソッドのうちの一つであること
        • GET
        • HEAD
        • POST ユーザーエージェントによって自動的に設定されたヘッダーを除いて、手動で設定できるヘッダーが以下だけであること
        • Accept
        • Accept-Language
        • Content-Language
        • Content-Type (但し、下記の要件を満たすもの)
        • DPR
        • Downlink
        • Save-Data
        • Viewport-Width
        • Width
      • Content-Type ヘッダーが以下のいずれかであること
        • application/x-www-form-urlencoded
        • multipart/form-data
        • text/plain
      • リクエストに使用されるどの XMLHttpRequestUpload にもイベントリスナーが登録されていないこと。
      • リクエストに ReadableStream オブジェクトが使用されていないこと。
  • Preflight Request

rack-cors とは

rack アプリケーションに CORS の制御機能を追加する rack middleware。 独自の DSL で CORS を制御できるようにする。 rack-cors の README にあるサンプル。 どのオリジンが、どのリソースに、何の操作をしていいかを定義している。

use Rack::Cors do
  allow do
    origins 'localhost:3000', '127.0.0.1:3000',
            /\Ahttp:\/\/192\.168\.0\.\d{1,3}(:\d+)?\z/
            # regular expressions can be used here

    resource '/file/list_all/', :headers => 'x-domain-token'
    resource '/file/at/*',
        methods: [:get, :post, :delete, :put, :patch, :options, :head],
        headers: 'x-domain-token',
        expose: ['Some-Custom-Response-Header'],
        max_age: 600
        # headers to expose
  end

  allow do
    origins '*'
    resource '/public/*', headers: :any, methods: :get

    # Only allow a request for a specific host
    resource '/api/v1/*',
        headers: :any,
        methods: :get,
        if: proc { |env| env['HTTP_HOST'] == 'api.example.com' }
  end
end

実際に見ていく

実際にリクエストが来た時、Simple RequestPreflight Requestで制御が異なるのでそれぞれのケースを見ていく。

Simple Request のケース

  • Simple Request を扱うケース
    • レスポンス時に付加するヘッダーを取得する
    • Rack::Cors#process_cors
      • 定義した DSL の origin・resource にリクエストが発生している origin・resource の組み合わせがあるか確認する
      • 存在していればレスポンス時に付加するヘッダーを取得する
    • レスポンス時に Access-Control-* のヘッダーを付加する
      • リクエストヘッダーの Origin と レスポンスヘッダーの Access-Control-Allow-Origin が一致していなければリソースは利用されない。
      • Simple Request の時って、Access-Control-Allow-OriginAccess-Control-Allow-Credentials 以外のヘッダーはいらないのでは?

Preflight Request のケース

  • Preflight Request
    • Method が Options でヘッダーに HTTP_ACCESS_CONTROL_REQUEST_METHOD がある時は、Preflight Request としてリクエストを処理する
    • Rack::Cors#process_preflight
      • 定義した DSL の origin・resource にリクエストが発生している origin・resource の組み合わせがあるか確認する
      • なければ Preflight Request の処理を終了する。Access-Control-* がレスポンスヘッダーに付加されないので後続のリクエストは送られない
      • リクエストヘッダーの Access-Control-Request-MethodAccess-Control-Request-Headers が事前に許可するとしたメソッドとヘッダーに含まれていなければ Preflight Request の処理を終了する。
      • Rack::Cors#to_preflight_headers
        • レスポンスヘッダーに追加する Access-Control-* のハッシュを返す
      • 内側の rack アプリケーションにはリクエストを伝搬せず、ヘッダーを付加したレスポンスを返す

Ruby によるデザインパターン

この本は絶版になっており非常に高い。
中古を高いお金を出して買わずに https://github.com/davidgf/design-patterns-in-ruby や日本語でのまとめを探して読むので十分だと思う。
このまとめはコードは載せていないが、コードを載せているまとめも多くあるので多くあるので探してみるとよい。

1. よいプログラムとパターン

GoF 本に対する筆者の要約は次の 5 つのポイントになる。

  • 変わるものを変わらないものから分離する
  • インターフェイスに対してプログラムし、実装に対して行わない
  • 継承より集約
  • 委譲、委譲、さらに委譲
  • 必要になるまで作るな(YAGNI = You Ain't Gonna Need It)

Ruby によるデザインパターンでも同様にポイントになっているので常に意識して読むと良さそう

2. Ruby をはじめよう

Ruby の文法について

3. アルゴリズムを変更する: Template Method

  • 概略
    • 骨格となるメソッド(テンプレートメソッド)を持った抽象基底クラスを構築し、サブクラスが実際のテンプレートメソッドの実際の処理(フックメソッド)を提供する。
      • テンプレートメソッドは NotImplementedError を raise するメソッドかもしれないし何かしらの標準実装かもしれない。
      • 大枠の処理は抽象基底クラスに定義してあるので、サブクラスは断片的に見えるかもしれない。
  • メリット
    • 継承を使ってアルゴリズムに多様性をもたせることができる
    • 抽象基底クラスはテンプレートメソッドを通じて高レベルの処理の制御に集中できる。サブクラスはその詳細を埋める
  • デメリット・注意点
    • 継承を使っていることは問題になりうる。継承より集約
      • 抽象基底クラスとサブクラス間で将来的にも is-a という関係を保ち続けられるか?(リスコフの原則)
      • 継承階層が深くなってしまい可読性が落ちてしまわないか?
    • YAGNI
      • 最初からテンプレートメソッドで多くの場合に対処できるようにはしない。最初はシンプルな実装で必要になってからこのパターンを適用する。
      • 大量のフックメソッドの実装を強いるテンプレートクラスを作らない。

4. アルゴリズムを交換する: Strategy

  • 概略
    • Template Method では継承を使って変わるものを変わらないものから分離するを実現したが Strategy では委譲によってこれを実現する
      • 変わる部分を別のオブジェクトに切り出し、利用したいオブジェクトは切り出したオブジェクトを取替可能なパーツとして扱う。
        • 切り出された同じ目的を持ったオブジェクト群をStrategyと呼び、Strategy の利用者をContextと呼ぶ。
        • コンテキストからストラテジに渡す引数はコンテキスト自身にするケースもある(もともとはストラテジはコンテキストの一部なので合理的に思える)
        • 簡易的なストラテジであれば Proc オブジェクトを使って実装することもできる
  • メリット
    • 委譲を使ってアルゴリズムに多様性をもたせることができる
    • ストラテジを切り出すことで関心を分離できる
    • 移譲と集約に基づいている(継承ではない)ので、実行時にストラテジの切り替えが簡単にできる
  • デメリット・注意点
    • コンテキストとストラテジのインターフェイスに一貫性をもたらさないと、ストラテジはあくまでコンテキストから切り出したものである、という関係性が崩れる
    • コンテキストと特定のストラテジの依存関係を高めると複数のストラテジへの対応が難しくなるのでメリットが小さくなる

5. 変更に追従する: Observer

6. 部分から全体を組み立てる: Composite

  • 概略
  • メリット
    • 任意の深さの木構造を構築でき、どのノードも同じように扱うことができる
  • デメリット・注意点
    • Leaf と Composite を実装上どう扱うかに注意
    • ツリーの深さが1段しかないと仮定してしまうとおかしくなってしまう

7. コレクションを操作する: Iterator

  • 概略
    • 集約オブジェクトから子オブジェクトをその保持方法によらず操作する
      • 内部イテレータ: 集約オブジェクトにコードブロックを渡して、子オブジェクトをそれぞれに対してコードブロックを呼ぶ
        • Ruby の #each
        • 走査の詳細は内部に隠れている
      • 外部イテレータ: #has_next? などのメソッドによって外部で走査を制御するイテレータ
  • メリット
    • 実装の詳細を知ることなく、集約オブジェクトの子オブジェクトを走査し任意の走査を行うことができる
    • #each と #<=> を実装すれば Enumerable モジュールを include することで便利メソッドがたくさん手に入る
  • デメリット・注意点
    • コレクションの走査中にコレクションに変更を加えると網羅的に走査できないかも

8. 命令を実行する: Command

  • 概略
    • オブジェクトからある特定の動作を切り出したオブジェクトにする(Strategy に近い?)
    • Composite パターンと合わせて、1 つのタスク実行で一連の Command を実行するパターンも形成できる
    • Obsever パターンと多くの共通点を持つ
      • Command: 動作の方法を知っているだけで呼び出し元の状態には関心がない
      • Observer: 呼び出される対象の状態に応じて動作する
  • メリット
  • デメリット・注意点

9. ギャップを埋める: Adapter

  • 概略
  • メリット
    • 新たにインターフェイスを満たすクラスを 1 から作ることなく既存のクラスを再利用できる
  • デメリット・注意点
    • 実装方針の選定基準
      • オブジェクトの変更: 対象をよく理解していて、インターフェイスの変更が比較的少ない場合
      • アダプタクラスの実装: オブジェクトが複雑な場合やオブジェクトへの理解が不十分な場合

10. オブジェクトに代理を立てる: Proxy

  • 概略
    • クライアントと対象オブジェクトの間に対象オブジェクトと同じインターフェイスを持つオブジェクトを挟みいくつかの機能を提供する
      • Protection Proxy: 対象オブジェクトへのアクセス制御を行う
      • Remote Proxy: 実際にはネットワークを介した別サーバに存在する対象オブジェクトが実際に存在しているかのように振る舞う(RPC)
      • Virtual Proxy: 対象オブジェクトの生成を必要になるまで遅らせる(||= 演算子を使う)
    • https://github.com/wantedly/computed_model などは Proxy の例
  • メリット
    • Protection Proxy: 対象オブジェクトからアクセス制御の責務を分離できる
    • Remote Proxy: クライアントは対象オブジェクトがどこにあるかを意識しなくて良くなる
    • Virtual Proxy: オブジェクト生成コストを遅らせる、削減できる
  • デメリット・注意点

11. オブジェクトを改良する: Decorator

  • 概略
    • 基本となる機能をを実装する ConcreteComponent を副次的な機能を持つ Decorator でラップしチェーンして呼び出す
    • rack middleware は Decorator の例。middleware は #call のインターフェイスを持ち、次々に次の rack app を呼び出していく
  • メリット
    • 必要な機能を実行時に組み立てることができる
    • 基本機能と Decorator、Decorator 毎の関心を分離できる
  • デメリット・注意点
    • 必要な機能を実行毎に組み立てるのが煩雑になりうる
    • デコレータの連鎖によるパフォーマンスの低下。一つの大きなオブジェクトでいいかもしれない

Adapter と Proxy と Decorator

  • これら 3 パターンは別のオブジェクトの代理オブジェクトの役割を果たしている
    • Adapter: 不適切なインターフェイスを持つオブジェクトを正しいインターフェイスを持つオブジェクトでラップする
    • Proxy: アクセス制御・リモートのオブジェクトへのアクセスの隠蔽・オブジェクト生成の遅延といった機能を追加する
    • Decorator: 基本的なオブジェクトにレイヤ状の機能を追加する

12. 唯一を保証する: Singleton

  • 概略
    • インスタンスが 1 つしかなく、そのオブジェクトをグローバルにす
    • singleton モジュールを include する方法、モジュールで実装する、クラスメソッドを使って実装するなどいくつかの方法がある
  • メリット
    • 設定ファイルやロガーなど、1 つだけ存在すればいいオブジェクトを引き回さなくて良くなる
  • デメリット・注意点
    • 本当にグルーバル変数にする必要があるのか?
    • 1 つだけ存在しているというのは確かなのか?

13. 正しいクラスを選び出す: Factory

  • 概略
    • クラス選択をサブクラスに任せる
      • 親クラスが振る舞いを持ち、サブクラスがどのクラスを選択するかを持つ
    • Abstract Factory は、有効なクラス選択の組み合わせを決めておく
  • メリット
    • 共通の振る舞いを持つ別のクラスを簡単に作成できる
    • 振る舞いとクラス選択の責務を分離できる
  • デメリット・注意点

14. オブジェクトを組み立てやすくする: Builer

15. 専用の言語で組み立てる: Interpreter

  • 概略
    • 課題を解決するための専用の言語を作る
      • 専用の言語をパースして個別のノードが実行可能な抽象構文木(AST)にする
  • メリット
    • 専用の言語によってシンプルな記述によって課題を解決することができる
  • デメリット・注意点
    • パーサーを作ることが複雑になりうる
    • Interpreter パターンを利用することで課題をシンプルに解決できるにも関わらず適用しないことで複雑なまま課題に向かうことになる

Ruby のためのパターン

まとめ

冒頭の筆者の GoF 本に対するまとめの再掲

  • 変わるものを変わらないものから分離する
  • インターフェイスに対してプログラムし、実装に対して行わない
  • 継承より集約
  • 委譲、委譲、さらに委譲
  • 必要になるまで作るな(YAGNI = You Ain't Gonna Need It)

この辺りはオブジェクト指向実践設計ガイドでも大体同じことを言っていた。 原則としては TRUESOLID などがこのポイントに当てはまってくるのではないだろうか。

  • TRUE
    • Transparent
    • Reasonable
    • Usable
    • Exemplary
  • SOLID
    • Single Responsibility Principal
    • Open Closed Principal
    • Liskov Substitution Principle
    • Interface Segregation Principle
    • Dependency Inversion Principle

プロジェクトマネジメントのトリセツを読んだ

図解 これ以上やさしく書けない プロジェクトマネジメントのトリセツ

モチベーション

エンジニアの仕事はコードを書くだけではない。
事前調査したり、他チームの人と協力したり、作業見積もりを出したりと色んなことをやる必要がある。
この色んなことにはプロジェクトマネジメントの要素が多分に含まれているのでこの本を読んだ。

自分は自社サービスを持つ web 企業で働くエンジニアなので、自分に役立ちそうなことをまとめていく。
書いてある内容を省いたり置き換えたり変えてまとめている部分もある。

まえがき

プロジェクトマネジメントとは、先読み技術
いかに先読みして、手戻りが発生しない最前の手を打っていくか。
WBSガントチャートPERT・リスクマネジメントなどを駆使して手戻りの少ないプロジェクト進行を実現していく。

プロジェクトの基本

  • プロジェクトとは、'特定の目的'を達成するための'臨時組織'による活動で現状維持の業務とは一線を画すもの
    • 既存組織による部分最適では全体最適にはならないことが多いので、組織横断的な臨時組織によって全体最適を目指すプロジェクトが増えている
  • プロジェクトマネジメントの 5 原則
    1. プロジェクトフェーズとライフサイクルの原則
    2. 不確実性を回避するためにプロジェクトをフェーズに分割し、そのフェーズごとにライフサイクルを明確にする
      • 企画・設計・実施(実装)に分けるとよい
      • 基本方針の明確化や全体の方向付けをする企画フェーズを入れるとよい。しないと手戻りが多くなりがち
    3. ライフサイクルは、立ち上げ・計画・実行・コントロール終結のプロセスで構成される
      • 計画: スコープを明確にし、計画を立案する
      • コントロール: 進捗をモニタリングし、必要に応じて修正する
    4. プロジェクト・ステークホルダー明確化の原則
    5. プロジェクトの影響を受ける人に事前に承認を取り、協力してもらえるように進める
    6. 組織影響の原則
    7. ステークホルダーにも関連するが、影響範囲を明確にしておかなければ既存組織から反発もありうる
    8. プロジェクト運営知識・能力確保の原則
    9. 社会経済的影響の原則
  • あらかじめ作業計画・スケジュール、オーナー、ステークホルダーを明確化する
    • 作業計画・スケジュールの明確化
      • WBSガントチャートPERT(Program Evaluation and Review Technique)・TRM(Task Responsibility Matrix)
        • 作業の分解、全体進捗の管理、依存関係の把握を行う
        • WBSガントチャート・TRM を一覧にすることで全体の進行を把握しやすくする
  • リスクマネジメント
    • 損失を発生するかもしれない不確実な度合い
      • 対策が必要か看過できるものか事前に決めておく

プロジェクトのテーマ設定とフェーズ分割

  • 事業目標とプロジェクト目標の方向性を一致させる

プロジェクトの立ち上げと計画立案

  • プロジェクト計画は実行段階での航海図
    • 実行段階では PDCA を回して計画差異を埋める
  • プロジェクト計画の基本は 5W2H
    • Why(背景)・What(目的)・Where(スコープ)・When(納期)・Who・How・How Much
  • テーマ設定
    • 背景 + 目的 + 達成目標 + スコープ を明確にする
      • 背景: プロジェクトが必要な理由を説明する
        • 何か課題を解決したい、新たな挑戦をしたい
      • 目的: プロジェクトが目指すゴール
        • 00のために00する、など分かりやすいものにする
        • 迷った時に立ち返るプロジェクトの拠り所
      • 達成目標: 目的を数値化して詳細に定義したもの。QCD(Quality・Cost・Delivery)を含めるとよい
      • スコープ
        • 明確にしておかないとプロジェクトが進むにつれてやることが増えていく
  • 前提条件・制約条件の明確化
    • メンバー間の認識齟齬をなくす
    • 後から制約条件が分かってだめだったと分かると辛い
  • プレリサーチによって現状分析を行いメンバー間の認識を合わせる
  • 主要成果物を明確にする
  • 全体像から個別の詳細へと解像度をあげていく

プロジェクトの実行・運用

  • あらかじめプロジェクトリーダーに情報を統合する仕組みをつくる
    • ルールの明確化
    • 会議体の明確化
      • どのような会議をどのようなメンバーでどのような頻度で行うか
    • 情報共有と情報伝達ルート
  • キックオフミーティングでステークホルダーへの周知と参加者の参画意識を高める
  • トレードオフが発生した場合の優先順位を決めておく
  • プロジェクト完了後の導入・継続もプロジェクト計画にまで含める

チェックリスト

  • 事業目的とプロジェクト目的の整合性が明確になっている
  • プロジェクトオーナーを明確化する
  • オーナーやステークホルダーのプロジェクトへの理解
  • プロジェクトマネジメントの知識を身につける
  • 目的指向の風土をめざす
  • 計画を立てて PDCA を回す
  • 5W2H を明確にする
  • 計画不足・予算不足・準備不足の解消
  • マトリックス組織では、関与する割合を明確化する
  • 情報を共有し、フラットでオープンな組織に
  • コミュニケーションチャンネルの明確化
  • フォーマットや会議体の明確化
  • マイルストーン(中間報告、フィーズの完了報告)をおく
  • 集中討議の活用で結束力を高める
  • 全員が目標達成に向かう状態を作り上げる
  • プロジェクトでの活動を、人事考課へ正しく反映させる
  • トレードオフ、コンフリクトへの早期対応

JavaScript Primer を読んだ

jsprimer

最近 js をちょくちょく書くようになったけど、js についてちゃんと勉強したことなかった。
そこで https://jsprimer.net/ を読んだ。とても分かりやすかった。
非同期処理は分かった気がしているだけなので、自分で書いてみて Promise 本 も読みたい。
第一部では Javascript の言語自体についての説明。
第二部は実際に使ってみよう、という趣旨。
第二部の Todo アプリでの、リファクタ、Model・View の流れの方向を決めるのは勉強になった。
以下個人的なポイント列挙。

  • var は使っちゃダメ。
    • for 文でのスコープを作らなかったり、巻き上げをするのでびっくりする。
  • データ型は プリミティブ型 とオブジェクトがある。
    • プリミティブ型は本当にデータだけを持つんじゃなくて、いくつかメソッドを呼び出すことができる。
      • その時には暗黙的に型変換する。
  • 比較する時には == じゃなくて === を使おう。
    • == は暗黙的な型変換後の値比較。
    • === は型の同一性と値を比較する。
  • 分割代入便利。
  • 真偽値の判定をする時、暗黙的な型変換はされないようにしよう。
  • 関数
    • ファーストクラスなので、関数の引数に渡せる。
    • 同一名称の関数は引数の数が違っても上書きされる。
    • 関数定義と呼び出し時の引数は違っても良くて、少ない分は undefined になる(よくなさそう)
    • js の関数はクロージャー。
  • switch 文は break しないと該当 case 全て実行されちゃう。
  • Object の merge は Object.assign({}, objA, objB) が典型的な方法
  • javascript の clone は shallow copy
  • 全てのオブジェクトの祖先は Object
  • プロパティの存在確認で祖先まで見に行ってほしくない場合には、Object#hasOwnProperty
  • 配列
    • Array かどうかの判定は Array.isArray を使おう。
      • typeof arrayInstance は "object" を返す。
    • for 文は基本使わず forEachmapfilterreduce で処理しよう。
    • 分割代入できる。
  • 文字列
    • JavaScript文字コードUnicodeエンコード方式は UTF-16 を採用している。
    • Unicode で定義されている文字列の中には 16 ビットに収まらないものがあるので、絵文字を扱ったり文字数を数える場合には注意。
      • 文字列を配列に変化して処理するなどの工夫が必要なケースも
  • this
    • this の参照先は条件によって変わる
      • 実行コンテキストコンストラクタ関数とメソッドArrowFunction
      • いくつか使い方はあるけど、基本的なユースケースは関数内で使うこと
    • ArrowFunction 以外の関数では実行時に this が決定される。
      • 関数内の this はベースオブジェクトになる。
        • ベースオブジェクトとは、メソッドを呼ぶ際に、そのメソッドのドット演算子またはブラケット演算子のひとつ左にあるオブジェクト(書籍から引用)
          • 関数単体で呼び出した時ベースオブジェクトは存在しないので this は undefined を返す
      • これはいくつかのケースで問題になる
        • this を含むメソッドが変数に代入された場合 => call, apply, bind によって、this を指定してメソッドを実行する。
        • コールバック関数 => this を一時変数に代入する or Arrow Function
    • Arrow Function における this は実行時に決定される
      • Arrow Function自身の外側のスコープに定義されたもっとも近い関数のthisの値(書籍から引用)
    • コンストラクタにおける this はインスタンスオブジェクトになるので、プロパティ定義などできる
    • this はメソッド以外では使うべきではない
  • クラス
    • ES2015 から新しくできた。それ以前は同等のものが関数で表現されていた。
    • コンストラクタ内では return しない。インスタンス以外が返ってしまう
    • クラス内に定義されたメソッドはプロトタイプメソッドと呼ばれ、インスタンス間で共有される。
    • コンストラクタ内で定義されたメソッドはインスタンス固有のメソッドとなり、プロトタイプメソッドより優先して呼び出される(ruby だと 特異メソッドとインスタンスメソッド)
      • 同名のプロトタイプメソッドとインスタンスオブジェクトのメソッドがあっても上書きされない
    • アクセッサプロパティがある
    • static キーワードをプロパティの前につけることで、静的メソッド(クラスメソッド)になる
      • 静的メソッド内の this はクラス自身を参照するので、factory やクラス自身の処理に使われる
    • インスタンス(オブジェクト)は、インスタンスの生成元や継承元への参照を prototype に持っている
      • Javascript のクラスはプロトタイプベースと言われるのはこれか
      • メソッドやプロパティの探索は prototype が undefined になるまで行われる
    • extends でクラスを実行できる
      • 静的メソッドもプロトタイプメソッドも継承される
      • 継承した場合には最初に super() を呼び出して親クラスのコンストラクタを実行する
        • コンストラクタの処理順が親クラス -> 子クラスという順番で無ければならないので
  • try...catch...finallyruby でいうところの begin...rescue...ensure
    • catch は全ての例外を補足するので error の種類によって処理を変える場合は catch 内でやる
    • TypeError などのビルトインエラーがいくつかある
    • throw で自分で例外を発生させられる
  • 非同期処理
    • Javascript の非同期処理は基本的には並行処理。Web Worker API など一部は並列で実行される
    • 非同期処理の外からは非同期処理の中で発生した例外を知ることはできない。
    • 非同期処理を上手く扱うための主要な 3 つのパターン
      • エラーファーストコールバックPromiseAsync Function
    • エラーファーストコールバック
      • 非同期処理の中で例外が起きた場合に実行するコールバック関数を非同期処理の引数の最初に渡すというルール
        • 仕様ではなくルール
    • Promise
      • 非同期処理を扱うためのオブジェクトでありインターフェイス
      • Promise はインスタンス作成時に resolvereject の 2 つの引数を持つ関数を引数に取る
      • Promise の処理内で resolve が呼び出されると Prmise#then でチェーンされた関数が呼び出される
      • Promise の処理内で reject が呼び出される、または例外が発生すると Promise#catch でチェーンされた関数が呼び出される
      • Promise は内部的に 3 つの状態を持っている
        • Fullfilled: resolve したときの状態。Settled な状態で、ここから状態が変化することはない
        • Rejected: reject または例外が発生した状態。Settled な状態で、ここから状態が変化することはない
        • Pending: Fullfilled でも Rejected でもない状態。インスタンスを作成したときの状態
      • Promise の処理内では一度だけ resolve が呼べる。
      • Fullfilled の状態になった Promise インスタンスに対して、then メソッドでコールバック関数を登録できる
      • Promise#resolve、Promise#reject で Fullfilled、Rejected な状態の Promise オブジェクトを作れる
        • テストでよく使われる
      • Promise#thenPromise#catch は Fullfilled な状態の Promise オブジェクトを作成して返すのでチェーンすることができる
      • Promise.all には Promise インスタンスの配列を受け取って、新しい Promise オブジェクトを返す。
        • 引数で受け取った全ての Promise インスタンスの状態が Fullfilled になったら返り値も Fullfilled になる。
      • Promise.race は 1 つでも Settled になったらその Promise オブジェクトの状態と同じ状態の新しい Promise オブジェクトを返す
    • Async Function
      • 関数の前に async キーワードをつけることで常に Promise インスタンスを返すようになる(以下引用)
        • Async Function が値を return した場合、その返り値を持つ Fulfilled な Promise を返す
        • Async Function が Promise を return した場合、その返り値の Promise をそのまま返す
        • Async Function 内で例外が発生した場合は、そのエラーを持つ Rejected な Promise を返す
      • Async Function 内では await 式を利用できる
        • await 式は右辺の Promise インスタンスが Settled になるまで待つ
          • Fullfilled なら await 式は resolve された値を返す
          • Rejcted ならその場でエラーを throw するので、同じ try...catch 構文で例外処理をできる
      • Async Function は Promise API(Promise.all など)と組み合わせて使うと効果的
  • Map や Set があって、どちらも Iterable。Iterate ようの Map#keys などもメソッドもある
  • オブジェクトは JSON を使って文字列とオブジェクトを行き来させられる
  • Date オブジェクトは使いにくいので既存のライブラリと組み合わせて使ったほうがよさそう

次は MDN の Javascript を読むかな。(順序が逆かもしれない) https://developer.mozilla.org/ja/docs/Web/JavaScript

Ruby の throw と catch の実例

Ruby では大体のケースでは例外を begin-raise-rescue で実行箇所を移しているのでは。
なので throwcatch を使う機会はあんまりないように思う。
そんな中、実際に使われているところを見つけたので紹介。

throw と catch を上手く利用しているライブラリ

使われていたのは Rack ベースのアプリケーションで認証機能を提供している warden というライブラリ。
このライブラリはdevise の内部でも利用されている。

利用箇所

throw と catch が使われているのはそれぞれ下記のコード。

    def authenticate!(*args)
      user, opts = _perform_authentication(*args)
      throw(:warden, opts) unless user
      user
    end
    def call(env) # :nodoc:
      return @app.call(env) if env['warden'] && env['warden'].manager != self

      env['warden'] = Proxy.new(env, self)
      result = catch(:warden) do
        env['warden'].on_request
        @app.call(env)
      end

      result ||= {}
      case result
      when Array
        handle_chain_result(result.first, result, env)
      when Hash
        process_unauthenticated(env, result)
      when Rack::Response
        handle_chain_result(result.status, result, env)
      end
    end

コードの説明

throw は認証を行うコード中にあって、認証が失敗した場合に throw(:warden) が実行されている。
catch は rack ミドルウェアの内部で利用されているので、この warden よりも内側のミドルウェア・アプリケーションコード中で throw(:warden) が実行されたらここまでジャンプする。
なので、認証が失敗したら rack ミドルウェアまで戻ってきて認証失敗時の処理を行う。
認証が成功した場合には アプリケーションのコードが rack response を返すはずなので、そのレスポンスをそのまま外側のミドルウェアに渡す。

まとめ

ジャンプしたいコードが大きく離れている場合には非常に有効な手段だと思った。
特に今回の例のように、Rack ミドルウェアとアプリケーションコード間でのジャンプに使うとよさそう。

Rails で 有効なエンドポイントを出したい

Rails で有効なエンドポイントを列挙したい

叩かれなくなっているエンドポイントを見つけたい。 そのためにアクセスログとアプリケーションの有効なエンドポイントとを突き合わせるとよさそう。 そこで有効なエンドポイントの列挙方法を調べた。

実行するコードと出力

異なるアクセスログの形式に対応できるように 2 通りで出す。

verb path の配列を取得するケース

Rails.application.routes.routes.reject(&:internal).collect do |route|
  next unless route.requirements[:controller] && route.requirements[:action]

  "#{route.verb} #{route.path.spec.to_s.sub(/\(.*\)/, "")}"
end.compact

これを実行すると下記のような配列が取得できる。 path 中の : から始まっているところは正規表現に置き換えてあげればよい。

=> ["GET /users/sign_in",
 "POST /users/sign_in",
 "DELETE /users/sign_out",
 "GET /users/password/new",
 "GET /users/password/edit",
...
 "PATCH /rails/conductor/action_mailbox/inbound_emails/:id",
...
 "POST /rails/active_storage/direct_uploads"]

controller#action の配列を取得するケース

Rails.application.routes.routes.reject(&:internal).collect do |route|
  next unless route.requirements[:controller] && route.requirements[:action]

  "#{route.requirements[:controller]}##{route.requirements[:action]}"
end.compact

これを実行すると下記のような配列が取得できる

=> ["devise/sessions#new",
 "devise/sessions#create",
 "devise/sessions#destroy",
 "devise/passwords#new",
 "devise/passwords#edit",
...
 "active_storage/direct_uploads#create"]