猫Rails

ねこー🐈

アジャイル/スクラムの知識の整理メモ(自分用)

  • 最近アジャイル/スクラムの本を何冊か読んだので、その知識を整理するためのメモです。
  • 理解が浅いのでたぶん色々間違ってます。完全に自分用です。参考にはなりません。

アジャイル

アジャイルマニフェスト

アジャイルマニフェストが重視する価値

  • ツール/プロセスより、人と人との相互作用
  • ドキュメントより、動作するソフトウェア
  • 契約書より、顧客との対話
  • 計画に従うより、変化への対応

アジャイルソフトウェア開発12の原則

原則

  • 開発中の要求の変更も歓迎
  • 顧客満足が最優先
  • 自己組織的な(自発的な)チーム
  • 動くソフトウェアを短期間でリリース
  • ビジネス側と開発側はチーム
  • チームメンバーを信用する
  • フェイストーフェイス
  • 動くソフトウェア自体が進捗
  • 進捗は継続的でなければならない
  • 技術力/設計が大事
  • シンプルさ(無駄を省く)事が最重要
  • 定期的なフィードバック

見積もり

  • 規模の見積もり期間の見積もりは分けて考えなきゃ駄目だよ
  • いきなり期間の見積もりを行うと失敗する
  • 規模の見積もりを行えば、期間の見積もりは自動的に計算される

流れ

  • 規模の見積もりを作成
  • 規模の見積もりから期間の見積もりを作成

規模の見積もり

ストーリーポイント vs 理想日

ストーリーポイント
  • 相対サイズ
  • ユーザーストーリーの相対的な大きさ
良い所
  • 絶対サイズよりも、うまく見積もれる
  • 絶対サイズよりも、速く見積もれる
  • 全体のサイズ感が間違っていた場合でも、再見積もりをしなくてよい
エピック
  • サイズ8とかの、大きなサイズのやつ
理想日
  • 絶対サイズ
  • 人間は相対サイズ使うのが下手
  • アジャイルでは相対サイズを使うのが主流
  • とはいえこっちを使ってもok
良い所
  • 導入が簡単 
  • 外部への説明が簡単
理想日 vs 現実日
# 理想日(理想時間)
  • アメフトの理想時間は60分
  • 15分のクォーター * 4回 = 60分
  • 割り込みを考慮しない
  • 実際には大量の割り込みがあるので、理想時間は現実時間から乖離する
  • 割り込みを考慮しないでいいので、現実日よりも使いやすい
# 現実日(現実時間)
  • アメフトの理想時間は180分

利用する数列*2

フィボナッチ数列
  • 1, 2, 3, 5, 8,....
  • こっち使うことが多い
倍々
  • 1, 2, 4, 8,...
  • こっちでもok

規模の見積もりの技法

プランニングポーカー
説明
  • ゲームしながら見積もる
ポイント
# 議論をする
  • プランニングポーカーは議論がポイント。ここで開発チームの認識のズレを減らせる
良い所
# 開発チームの認識のズレを減らせる
  • 議論をするので、認識のズレを減らせる
進め方
  • 開発チームにサイズのカードを配る(1,2,3,5,8)
  • タスクに対して、全員同時に、カードを公開する
  • 議論
  • 再見積もり
三角測量
  • 基準となるストーリーを決めて残りのストーリーのポイントを見積もる

見積もり時のポイント

見積もりを正確な予測だと思ってはいけない
  • あくまで『見積もり』
全てを詳細に見積もってはいけない
  • 時間をかけすぎない
  • 見積もり自体に時間がかかる
  • 見積もりは予測でしかない
  • 詳細に見積もるのは直近の数スプリント分くらいで、あとはざっくり見積もっておけば良い。
  • 後になるほど不確定情報が減るので、見積もりの精度が上がる
開発チームが見積もる
  • 作業をする本人が見積もるのもいいが、開発チームで見積もるのが一番いい

スパイク

  • 見積もりのための技術的な事前調査

ナレッジ

  • ナレッジが溜まっていくことで、より正確な見積もり/計画作りが行える
  • そのため見積もり/計画作りは最初に一気にやるべきでない
プロダクトナレッジ
  • プロダクトについての知識
プロジェクトナレッジ
  • プロジェクトについての知識
  • チーム/採用している技術/リスクなどについての知識
  • こっちも大事

期間の見積もり

ベロシティ

  • 1スプリントで終えられる、フィーチャの量
  • 個人の速度ではなく、チームの速度だよ。個人の速度を計測するのは間違い
  • ユーザストーリーを丸ごと終わらせていないと、ベロシティに加算してはいけない。部分的な加算はNG
良い所
ベロシティはあくまで予測
  • ベロシティは予測なので最初のイテレーションで計画を軌道修正する
  • 予定よりも早ければ前倒しをして、遅ければスコープを狭める
ベロシティの初期値の予測
  • 計算方法はない
  • チームメンバーに話を聞いて、だいだいで決める
  • たとえば
    • チームの力量は?
    • 顧客を巻き込めるか?
ベロシティの計算

ベロシティ = doneした合計ポイント / 合計イテレーション

イテレーション回数の計算(期間の見積もり)

計画作り

アジャイルな計画づくり

  • 計画は積極的に変更していく
  • 最初に全ての計画を決めて終わりではなく、全期間通して行う
  • 将来は予測できないし、変化はつきもの。その前提で計画づくりを行う

従来型の計画づくりの問題点

不確実性を無視している

  • あらゆることが不確実。それを認める

見積もりをコミットメントだと思ってしまう

  • 見積もりはあくまで予測

フィーチャではなく、作業を計画してしまう

  • 作業を基準にすることで、フィーチャを軽視してしまう
  • 作業を基準にすることで、スケジュールを守れなくなる

フィーチャの優先順位を開発チームが決めてしまう

  • そのため重要な機能が実装されない

優先順位付け

優先順位付けの指標*4

  • これら4つを考えて、優先順位を行う
金銭価値
  • ビジネス的な価値が大きいものは、優先順位が大きい
コスト
  • コストが小さいものは、優先順位が大きい
ナレッジの獲得
  • 獲得できるがナレッジ大きいものは、優先順位が大きい
  • ナレッジが多いほど、リスクが減る
低減できるリスク
  • 低減できるリスクが大きいものは、優先順位が大きい

フィーチャの種類*3

当たり前のフィーチャ
  • 必須フィーチャ
  • 優先順位は一番高い
線形のフィーチャ
  • あればあるほど良いフィーチャ
魅力的なフィーチャ
  • ユーザーが体験するまで魅力に気づかないフィーチャ
  • 優先順位は一番低い

優先順位付けは、スクラムチーム全員で決める

良い所

リスクを減らせる

  • リスクに関する情報を得られるので、その知見を生かしてリスクを減らせる

不確実性を減らせる

  • プロダクトだけでなく、チームに関する情報も得られる。そのため不確実性を減らせる

意思決定を支援する

  • 見積もり/計画があれば、意思決定を支援できる

バーンダウンチャート

  • 進捗を可視化できる
  • 期日までに全ての作業を完了させる事ができるかを見るためのグラフ
  • 傾きがチームのベロシティ(pts/回数)になる
  • 当然イテレーションが進めばベロシティは上がったり下がったりする
  • 新たなストーリーが追加されたり人員が減ったりプロジェクトの変化を可視化出来る
  • 縦軸: 残り作業量(pts)
  • 横軸: 残り時間(イテレーション回数)

良い所

進捗を可視化できる
  • 具体的には以下の4つ
    • タスクの完了数
    • タスクの残量数
    • いつごろ、全て完了するか
    • ベロシティ

使い方

  • 理想線(直線)と現実線(ふにゃふにゃ)の2つを比較する

バーンアップチャート

  • 縦軸と横軸が反対
  • 両方採用するっていう手もあるよ

期日を決める

方法*2

  • どっちがいいかは場合による
期日を借りぎめする
  • スコープを調整する
フィーチャセットを固定する
  • 期日を調整する

流れ

リリースとマスタストーリーリストを作成

規模を見積もる

優先順位を付ける

  • 大事な物ほどマスタストーリーリストの上に記述する

ベロシティを見積もる

完了期日を借り決めする

スケジュールを立てる

リリース計画

イテレーション計画

バッファの計画

  • 不確実なことは多いので、バッファの計画をしておく

フィーチャバッファ vs スケジュールバッファ

フィーチャバッファ
  • 必須フィーチャ + オプションフィーチャ
  • オプションフィーチャは余裕がある場合のみ開発する
スケジュールバッファ
  • スケジュール全体に対して、バッファを設ける
  • 面倒な公式で計算できる

大規模プロジェクト

  • 複数チームに分割する

インセプションデッキ

良い所

  • プロジェクト開始前に10の質問に答えて、全体像を共有する
  • プロジェクトの全体像を共有する
  • プロジェクトの全体像を明確にする

ポイント

  • ステークホルダーも対象
  • 壁に張り出して常に確認する
  • 途中でプロジェクトに変更があったらインセプションデッキを修正する
  • 数日~2週間かける(今後半年の予定を決める)
  • 10の質問は自由に改変していってね

10の質問

エレベーターピッチ

  • 『売り』が、わかる
由来
テンプレート
  • [課題の解決]したい # 顧客が解決したい課題を説明する
  • [顧客名]向けの # だれのためのプロジェクトなのかを説明する
  • [プロダクト名]というプロダクトは # 名前をつける事で意識させる
  • [プロダクトのカテゴリー]です。 # 何をする物なのかを説明する
  • これは[最重要の良い所]ができ # 顧客がプロダクトに支払いたくなる理由
  • [代替手段の最右翼]とは違って # 既存のサービスとの違いを説明する
  • [このプロダクトだけの特徴]が備わっている # 既存のサービスとの違いを説明する
  • [建設現場の作業を把握]したい
  • [現場責任者]向けの
  • [CSWP]というプロダクトは
  • [危険作業許可証発行システム]です。
  • これは[作業許可書を追跡・管理]する事が出来る
  • [現行の紙ベースのシステム]とは違って
  • [web経由でどこからでも利用出来る機能]が備わっている

何を諦めるのか

  • 期間/予算/品質/スコープが、わかる
  • 期間/予算/品質/スコープのどれを諦める?
トレードオフスライダー
  • 予算/期間/品質はトレードオフの関係
  • 諦められるのはスコープだけ
ポイント
  • これらはトレードオフ。プロジェクトによって理想のバランスは変わる。
  • 下の4つは四天王。最重要(この4つ以外の要素も一応ある)
四天王(o)
スコープ
  • これだけ変更可能
  • (o)アジャイル開発では、変更できるのはスコープはだけ!
  • 他の3つ(期間/予算/品質)を変更してはいけない
期間
  • 固定
予算
  • 固定
品質
  • 固定

我々はなぜここにいるのか?

  • プロジェクトの目的が、わかる
目的
  • なぜこのプロジェクトをやるかがわかる
  • 顧客が誰か分かる
  • 建設現場での作業を安全に追跡監視するため

パッケージデザインを作る

  • 『売り』が、わかる
目的
  • パッケージデザインを作る事が出来る
  • 広告の内容(どうインパクトを与えるか)を知ることができる
ポイント
  • フィーチャ(ユーザ視点での機能)ではなくフィーチャの効能を伝える
  • 例: 245馬力(フィーチャ) -> 高速道路でも楽々追い越し(フィーチャの効能)
進め方
  • プロダクトの効能トップ3を洗い出す
  • キャッチコピーを決める
  • パッケージの全体像をデザインする

やらない事リスト

  • スコープが、分かる
目的
  • スコープを明確にできる
進め方
  • やる/やらない/後で決めるの3つに分けていく

ご近所さんを探せ

  • ステークホルダが、わかる
  • 保守/セキュリティ/..の人達の話を先に聞いておく
目的
  • ご近所さんと仲良くなる事が出来る
進め方
  • コアチーム以外のプロジェクト関係者をリストアップする
  • 彼らと実際に連絡を取る

解決案を描く

良い所
  • 技術的な課題が、わかる
  • 目に見える設計図にする事で、チームでイメージを共有出来る

夜も眠れない問題はなんだろう?

  • リスクが、わかる
  • 取り組む勝ちがあるリスクなら、プロジェクト開始前に潰せる
  • 早期にリスクを共有しておくことで、被害を減らせる

期間を見極める

  • 期間が、わかる
  • 3ヶ月/6ヶ月/9ヶ月どのあたり?
  • 短い方が良い
  • 6ヶ月だと危険

何がどれだけ必要か

  • 期間/予算/人材が、わかる

ストーリー収集ワークショップ

  • フィーチャを洗い出す

ポイント

  • 顧客+コアメンバーで行う
  • 目安としては1日かけて10~40程度のユーザーストーリーを作成する。これで3~6ヶ月分の計画づくりができる
  • 画面が1つか2つだったら機能をさらに分解して細かくする
  • ユーザストーリーをたくさん書く
  • モレ/重複/長過ぎない?短すぎない?/..のチェック

頭を使うといいよ

アジャイルを支える技術

テスト

基本

  • バグを修正する前に失敗するテストを書く。これで確実にバグを潰せる。
  • ユニットテストは自動化させる
  • カバレッジ(網羅率)は自由に決める(100%でなくてもいいよ)
  • テストを書く良い所
    • 素早いフィードバック
    • デバグ時間の削減
    • 自信を持ってデプロイできる
    • その他色々な問題を前もって潰してくれる

リグレッション(regression=回帰)テスト

  • コードを修正した際に、その修正が既存のコードに影響を与えていない事を確認するためのテスト

リファクタリング

基本

継続的インテグレーション

継続的インテグレーションとは

継続的インテグレーションでやる事

  • ビルドスクリプトの作成
  • ユニットテストコードの作成
  • インスペクションツールの設定
  • 自動デプロイの実行
  • フィードバック手段の設定

自動テスト

  • 普通は手動で1度でいいが(そっちのが速いし),大規模な場合は自動テストが必要

TDD(テスト駆動開発)

  • 別項参照

イテレーション

アジャイルプロセス

アジャイルプロセス

アジャイルプロセス

ラクティス

原則

価値

スクラム

  • 別項目

XP(エクストリーム・プログラミング)

小規模チーム

チームが小規模の場合、ドキュメントを減らしプログラム重視の開発を行う事が出来る

コード/テスト以外の成果物を要求しない

ドキュメント等は不要 あってもいいけど

4つの技術を重視する

  • コミュニケーション: 重視する
  • シンプルさ: 全てを可能な限りシンプルにする
  • フィードバック: 細かく
  • 勇気: ウォーターフォールを辞める勇気

オンサイト顧客

顧客は常に一人以上チームに滞在しなければならない

カンバン

スクラム

スプリントイベント

スプリント

  • 開発期間の1単位
  • 1スプリントで計画/設計/開発/テスト/リリースを、最後まで全てやりきる
  • 作業が残っていても、期間を延長してはいけない
  • スプリント中にフィーチャは変更しない
  • プロダクトオーナーの判断でスプリントを終了させることができる
  • 優先順位の高い物から選ぶ

期間

  • 1~4週間
  • 迷った場合はまずは最短の1週間にして、スプリントレトロスペクティブで見直すようにするといいよ
  • ただし、スプリント期間は頻繁には変えないほうがよい
ポイント
  • リリースまでの期間
  • フィードバックの得やすさ
  • ...

スプリント計画ミーティング

  • スプリントの始めに行う、計画
  • スプリントが1ヶ月の場合、8時間使える
  • スプリントが2週間の場合、4時間使える

スプリント・ゼロ

  • 開発環境整備用の特別なスプリント
  • プロジェクトの開始前に、開発の準備をするイテレーション
  • 開発環境の整備だけでなく、ビジネスモデルの仮説立てなども含むっぽい?
  • gitの設定とかの開発環境の整備

リリース・スプリント

  • リリース用の特別なスプリント
  • リリースは大変。そのためリリース用の特別なスプリントを用意する場合もある。
  • 用意せずに通常のスプリント内でリリースしてしまうのが理想

スプリントプランニング

  • スプリントの開始時の、スプリントバックログ作成
  • 顧客とプロダクトオーナーとの対話で、機能の確認
  • スプリントバックログを作成(プロダクトバックログから選ぶ)
  • フィーチャーをタスクにして細かくする
  • ベロシティの確認
  • ストーリーの整理
  • 進捗の確認(バーンダウンチャート)
  • スプリントの大目的を設定する
  • 実装前に漏れがない事を確認する

デイリースクラム

質問3つ

  • 昨日やった事
  • 今日やる事
  • 問題点(サーバが落ちる/技術がわからない/...)

目的

問題の発見
  • タスク漏れ/進捗遅れ/困ってることが分かる
  • 問題発見だけ。問題解決は別で時間を作ってやる。
進捗の共有
  • ただし進捗報告会ではないので注意。問題の発見が最重要

ポイント

  • 課題解決の場ではない(他に議論すべき事があればこの後すぐに続けて行う)
  • 15分固定で、延長はできない
  • 毎日//同じ場所/同じ時間
  • 他人も参加は出来るが、メンバーしか発言出来ない
  • 開発チームとスクラムマスターが参加
  • 立ったままでok
  • 輪になって
  • 質問3つ(+2)以外の話
  • 問題点は即座に解消する
  • ホワイトボードは大事(問題点/追加ストーリーはホワイトボードに書きとめ解決したら消す。)
下記の質問3つ(+2)以外の話は禁止

スクラムマスターが注意する

スプリントレビュー

  • スプリント終わりの、デモ

やること*2

進捗報告と、修正
  • プロダクトオーナーが、進捗説明(完成/未完成) + 現状のプロダクトバックログの説明
  • 開発チームが、うまくいったこと/問題点/解決方を議論
デモをして、フィードバックを得る
  • スプリントの終わりに、プロダクトオーナー/ステークスホルダにプロダクトをデモしてフィードバックを得る
  • 実際にデプロイしたプロダクトを見せる
    • スライドとかでは駄目だよ
    • 本番環境ではなくテスト環境でいいよ

インクリメント

  • リリース可能なプロダクト
  • ドキュメントやモックではなく、リリース可能なプロダクトでレビューを行う

スプリントレトロスペクティブ

  • スプリント終わりの、ふりかえり

目的

  • 振り返りを行い、速度を上げる

振り返ること

  • このスプリントは問題がなかったか?
  • もっと成果を出すためにできることはないか?

KPT(keep/Problem/try)(ケプト)

  • ふりかえり手法の1つ
  • スプリントレトロスペクティブで使うといいよ
keep
  • 続ける事
  • 良かったことや、続けたいこと
Problem
  • 問題点
  • 困ったことや、改善したほうがよいこと
try
  • 改善点
  • 良かったことを伸ばす案や、困ったことを解消する案

バックログ

プロダクトバックログ

  • 全部
  • フィーチャ・機能・要求・要望・修正の一覧
  • 全ての中心
  • タスクはここを見ればすべてわかる
  • プロダクトオーナーが責任を持つ
  • 誰が書き込んでもOK

リファインメント

  • プロダクトバックログを手入れすること
  • 見積もりや優先順位の変更など
  • プロダクトオーナーと開発チームが協力して、常に更新する

プロダクトバックログアイテム

  • プロダクトバックログの各項目
  • たとえばオンラインでの書籍販売サービスを開発する場合、「読者として本を買いたい」とか
  • フィーチャと同義かな

ユーザストーリー

  • ユーザー目線で書いたプロダクトバックログ
  • プロダクトバックログの書き方の1つ
  • プロダクトバックログアイテムには、ユーザーストリーを使う事が多い
  • ユーザー目線というのがポイント
  • Who(誰のために)、What(何を作るか)、Why(必要とされる背景や理由)を書く
フォーマット + 例
  • <ユーザ>として、<達成したいゴール>したい。なぜなら<理由>だからだ
  • <ECサイトの利用者>として、<サイトにログイン>したい。なぜなら<ログインしないと買い物できない>からだ
良い所
  • ユーザー目線で書くことで、ユーザーにとっての価値に重きを置ける
ユーザストーリーの分割
  • 分割したほうが、色々いいよ
分割すると良いケース*2
  • ユーザストーリーが1回のイテレーションに収まらない場合
  • 正確に見積もりたい場合(小さい方が正確に見積もれる)
分割する方法*3
  • データ毎に分割
  • 機能毎に分割
  • 横断的な機能に分割(ロギング等)
分割時のポイント
# パフォーマンス等は、個別のストーリーにする
# ユーザストーリーをタスクに分解してはいけない
  • フィーチャをタスクへ分解するのはOK(というより当たり前にやっている)。それとはわけが違う。
  • 例えばUIを実装するはNG

フィーチャ

  • ユーザから見た、ソフトウェアの機能
顧客が書くのが理想

なぜなら本当に必要な機能を知っているのは顧客だけだから だが、実際にはコアメンバーが書く

顧客視点 vs プログラマ視点
顧客視点
  • 10秒かかるページの読み込み速度を2秒に縮める
プログラマ視点
  • DBにコネクションプールを導入する
非機能要件も含まれる
  • セキュリティや性能目標などの非機能要件も含まれる

テーマ

  • 複数のユーザストーリーをまとめたもの
  • 見積もりで使う
  • ユーザストーリーだと小さすぎて見積もりにくい。なのでテーマにまとめて見積もりを行う
  • 特に"ビジネス的な価値"といのはテーマとしてみないと見積もりにくい

すべての個人記録を保存し、選手は記録を参照できる

スプリントバックログ

  • 今回のスプリント分

プロダクトバックログから選び、フィーチャーを細かくタスクにする

  • 具体的には1日以内に終わる粒度にする
  • 下記の場合3つに分割している
プロダクトバックログ
  • ECサイトの利用者として、サイトにログインしたい。なぜならログインしないと買い物できないからだ
スプリントバックログ
  • ユーザのログイン機能を作成する
  • ログインページのデザインとHTMLのコーディングをする
  • ログインしている場合のみ買い物を可能とする制限を導入する

スプリントバックログは開発チームが管理する

カンバンを利用する

作業者がどのタスクを行うか自ら選択する

ロール

ロールとは?

ロールとは?

  • その人の役割。全員が何かしらのロールを1つ以上持つ

ロールの兼任について

  • ロールは複数兼任してもOK
  • ただし混乱することが多いので、可能なら1つにする
  • 特にスクラムマスターとプロダクトオーナーを兼任してはいけない

全部で3つ

  • プロダクトオーナー
  • スクラムマスター
  • 開発チーム

スクラムチーム

  • プロダクトオーナー + スクラムマスター + 開発チーム
  • 全員合わせてスクラムチームと呼ぶ

2つのリーダーシップ

  • プロダクトオーナーとスクラムマスターの2つのリーダーシップが存在する
  • プロダクトオーナーは課題達成に責任を持ち、スクラムマスターは人間関係に責任を持つ、と考えられる

プロダクトオーナー

仕事

プロダクトの価値の最大化、責任を持つ
プロダクトバックログの管理
  • チームで話し合うが、最終決定権はプロダクトオーナーが持つ
優先順位を決める
フィーチャにポイントを付ける
イテレーションの作業を決める
開発チームに、実現したいことを伝える
  • なので上手に伝える能力が必要
顧客と話し合う
  • 可能なら顧客自身がプロダクトオーナーになるのが良い
  • 顧客と話し合い、プロダクトバックログを増強する
他のステークホルダとも話し合う
スプリントを中止させる
  • スプリントを中止させられるのはプロダクトオーナーだけ
開発チームには干渉できない
  • 相談はできるが、干渉できない
  • これにより開発チームのスピードを上げることができる(?)
リリース計画を決める
予算を管理する

向いている人

アンチパターン

開発チームの一員
  • プロダクトを妥協してしまうので、NG
開発チームとコミュニケーションが少ない
  • 優先順位に対する意思決定の不確実性が高まる
権限がない

プロジェクトに1人

  • 何を作るべきかを確実にできる

スクラムマスター

仕事

スクラムをうまく回すことに責任を持つ
開発者を支援する
スクラムを指導する
開発者に奉仕する
  • マネジメントと言うより、支援
  • サーヴァント型のリーダーだね
  • 指示するのは絶対NG。これをやってしまうとメンバから主体性がなくなってしまう
  • メンバーが外部から邪魔されないようにする
POを支援する

プロダクトオーナーが未熟だと、優先順位の管理がうまくいかなかったりする。それをサポートする

向いている人

スクラム/アジャイルを熟知している人
  • 指導する側なので

開発チーム

  • 3~9人

職能横断的なチーム

  • 個々人が、色々やる
  • 職業に縛られない。コーダーでもテストや設計をやる
  • ただし個人で全てできる必要な無い。チームとしてできればOK

自己組織化されたチーム

  • 個々人が、主体的にやる
  • 具体的には自分がやるがタスクを自発的に決める
  • 上下関係があったり、上から指示されてやるのではない

その他メモ

ウォーターフォール vs アジャイル

ウォーターフォール

  • プロセス/ツール
  • ドキュメント
  • 顧客との契約交渉
  • 計画に従う
  • 計画重視
  • 文章重視
  • スペシャリスト

アジャイル

  • 個人/相互作用
  • 動くソフトウェア
  • 顧客との協調
  • 計画を変更
  • 協調性重視(人重視)
  • フェイストゥーフェイス重視
  • スペシャリスト(+ゼネラリスト)

完成の定義

  • 完成 = done
  • チームで定義する。そのためチームによって変わる
  • たとえば『テストコードを書いていること』を含めないチームも存在する

未来は不確実

複雑な問題に経験主義で対処する

1つのチームとして働く

  • 上から仕様が降ってくるのはNG。同じチームとして働く

学んだ事はチームで共有する

  • 小さな勉強会もおすすめ。使用する本は固めの定番本がいいよ

最初は実際に同じ場所に集まる

  • 後はスカイプ等でも大丈夫
  • フルリモートもあるし、ケースバイケースかな

定額契約

細かくデモ/フィードバック

  • 要件は細かく変化するのでそれに対応する必要がある。顧客が嫌がるかもしれないがその場合はちゃんと説明する

デイリースタンドアップ/デイリースクラムを重視

  • 立ってやる
  • 手早く(10~20分)
  • 質問は3つ(+2)

アーキテクト(設計者)もコードを書くべき

  • アーキテクト(設計者)はコードをかけない人が多いらしい
  • ○実はコレプログラマが設計すべきだと言っている

デプロイを自動化する 

TDD(テスト駆動開発)を重視

  • ○TDDにおいてテストは設計である

テスト/CI(自動ビルド)を重視

成果を重視する 

  • 人を責めない。人には優しくする

応急処置はNG

チームが間違った開発方法をとっていたら勇気を出して指摘する

常に学ぶ 

時が来たら技術/習慣を捨てる 

  • JavaからRubyへ。いつかRubyを捨てる日も来るかも

わかるまで質問する

イテレーションをタイムボックス化(期日の固定)する

戦略的設計

  • 設計は大雑把に=戦略的設計を(開発者に実装の裁量を持たせる)(要件は途中で変わるから柔軟に)。ただし戦略的設計は大事(UMLを利用する事もある)
  • 戦略的設計:概観。必要
  • 戦術的設計:実装の設計。いらない(プログラマが自分で決める)

新しい技術の導入には明確な理由を 

細かく統合(インテグレーション)

  • 開発の最終段階になってからの統合ではバグ等の大きな手戻りが発生する。リスクはどんどん大きくなっていくので細かく潰す。1日に数回が理想(1日1回でも少ない)。
  • モックオブジェクト:本物の替わりのオブジェクト。コレを使えばunitテストが可能

短いイテレーション/インクリメンタルにリリース

  • 集中力が上がる/フィードバックを得られる/柔軟な変更が可能
  • インクリメンタル: イテレーション毎に少しづつ機能を追加していく

全てのプラットフォームでテストする

  • ○CI(自動テスト)を使う

受け入れテスト(顧客が行うテスト)を自動化する

顧客には進捗としてバックログを見せる

  • これなら偽の数字出し用がない。

顧客重視(顧客が素人なのは当然)

  • 顧客が理解出来なければそれは開発者の責任

読みやすいコードを意識する

  • 1年後も理解出来るコードを書く

コメントをできるだけ使うな

  • コードの識別子がコメントだ!

シンプルな(洗練された)コード

  • 削る部分がないコードを目指す

クラスをうまくコンポーネント(Rubyならモジュール)にまとめる

Tell,Don't Ask(委譲を利用する)

チェックイン(コミット)するコードは必ずユニットテストを通過させる

コードレビュー(他人による目視チェック)を重視する

計画が合わなくなってきたら計画自体を柔軟に変更する

現実と計画の乖離が激しい場合は、計画の方を変更する

役割分担が流動的に変わる

  • 役割に人を当てはめるのではなく,人に役割を当てはめる。

主体的に自らで役割を探す事が重要

  • そのためには個々の開発者に権限/責任を持たせる。開発者にデモをやらせる事で自覚させる事が出来る

コードの共同所有

  • コードはだれが変更しても構わない

プロジェクトで使う言葉を共有する(それをDBでも使用する)

  • DDD

集めた要求も途中で変わる

  • 計画自体を柔軟に変更する

プロジェクトの開始時点に全ての要求を集める事はできない

  • 要求が出揃わなくても開発を始める

アジャイルな人の特徴

  • ゼネラリスト
  • チームプレーできる人
  • 柔軟な人

大事なことに集中する

プロダクトに集中する

フィーチャを追加する場合の対処法

  • 以下の2択
    • 同等のフィーチャを外す
    • 期日を伸ばす

遅れている時の対処法

  • 顧客に正直に話す
  • スコープを削る/人員追加/期日の延長

分析は必要になってから行う

  • 未来は予測できない。直近であるほど分析が正確になる。

Vue.js入門用の資料 まとめ

Vue.jsに入門して1ヶ月ほど経ったので、良かった資料をまとめておきますー😺

入門用の資料のみ、日本語の資料のみで、易しい順です。

やわらかVue.js

実際にVue.jsの勉強を始める前に読むと良さそうです。

漫画とかも使っていて、やさしくVue.jsの全体像を理解できます。

Vue.jsはデータバインディングの機能だけなら小さくて学びやすいのですが、エコシステムを含めると勉強すべきことが大量にあり、先に全体像を把握しておかないと迷子になります。(なりました。)

Vue.jsの学び方と、書籍のページが特におすすめです。

Vue JS入門決定版!jQueryを使わないWeb開発 - 導入からアプリケーション開発まで体系的に動画で学ぶ

自分的にはイチオシです。

Udemyの有料のスクリーンキャストです。公式ドキュメントから特に大事な部分を選別して、初心者向けに丁寧に説明してくれる感じです。選別したと言っても8時間分のボリュームがあり、これだけで基礎を網羅的に学べます。演習も十分用意されています。フロントエンドに慣れていない人に配慮してかES2015を使わずにES5を使っているので、const=>がわからない人でも安心です。 

自分はいきなり公式ドキュメントやって辛かったのでこの動画を併用したのですが、かなりスムーズに進みました。演習も含めて公式ドキュメントに即した形になっているので、公式ドキュメントを読むための前学習としても最適です。

自分はセールで1,300円で買ったのですが、今見たら定価の7,200円になってました。素晴らしい動画なので7,200円の価値はあるとは思うのですが、Vue.jsは他にも良い資料がたくさんあるので買うべきかは悩ましいところです。

またセールするかもしれないので、その時は買いです。

基礎から学ぶ Vue.js

本を使って勉強したい方におすすめです。

通称猫本と呼ばれているそうです。こちらもVue JS入門決定版と同じくらいわかりやすいです。Vue JS入門決定版と比べると、少しだけレベルが高く、情報量が多く、より網羅的です。Vue JS入門決定版では扱っていないvuexまで扱っています。サポートページも充実しています。

個人的には動画が好きなのでVue JS入門決定版がおすすめですが、こちらも素晴らしいです。

ただ、epub版を買ったのですが固定レイアウトでした。コピペできない。悲しみです...😹

公式ドキュメント

当たり前ですが、一番おすすめです。

わかりやすいし、情報が充実してるし、翻訳されてるし、最高です。公式ドキュメント書いてる方達、翻訳してる方達に感謝です🙇

勉強する時は公式ドキュメントを中心に考えるのが良さそうです。

ただフロントエンドの知識がない場合、いきなり公式ドキュメントをやるとちょいツラかもです。というか、自分がそうでした。 なので、まずはここより上の資料で公式ドキュメントを読めるレベルになって、次に公式ドキュメントを読んで、そしてここより下の資料で実務レベルの知識を補強していくのがいいのかなーと思います。

Vue.js入門 基礎から実践アプリケーション開発まで

入門とありますが応用的な内容が多く、公式ドキュメントを一読した後でやるのがよさそうです。

jQueryからの移行や、コンポーネント設計の技法等、実際のプロダクト開発で必要になるであろう実践的な知識をまとめて獲得できます。

特に8章~10章の中規模・大規模向けのアプリケーション開発は知見の塊で、唯一無二な感じです。

公式ドキュメントと同じく、今後何度も読み返すことになりそうです。

すごくおすすめです。

Examples

公式さんがいくつもお手本となるアプリを用意してくれてるようです。写経してると、なんか自分でもできる気がしてきます。(気がするだけです)

web

Vue.jsは大人気みたいで、web上に大量の知見があります(感謝)。自分は休憩時にはてなブックマークの検索で新着順で遡って、気になった記事を読んでました。

実際の現場でどんな感じで使われているのか勉強になります。

結局どれをやればいいの?

プログラミングに慣れてる人は公式ドキュメントだけでいいと思いますー。
プログラミングに慣れていない人はVueJS入門決定版 OR 基礎から学ぶVue.js -> 公式ドキュメントの順でやるのがいいと思います。
自分みたいにフロントエンドの知識がなくて、かつ時間がある人は、全てやるのもありだと思います。重複は多いですが、いろんな視点で学べるので理解が深まる気がします。

ではー。

ポリモーフィック関連のコントローラー

まだまだRails勉強中の身なので、間違いもあるかと思います💦間違いを見つけた場合は、コメントいただけると嬉しいです🙇

お世話になっているFJORD BOOT CAMP(フィヨルドブートキャンプ)さんでこのような課題が出ました。

ポリモーフィック関連を使い、BookとReportにコメント機能をつけよ。(要約)

シンプルな課題ですが、難しいです。 ポリモーフィック関連の実装方法を知っているだけではだめで、以下のような知識も必要になります。

Commentのコントローラーを実装しながら、解説に挑戦してみます!

シンプルなコントローラー

まずはCommentのCRUDが欲しいので、CommentのScaffoldを作成します。

$ rails g scaffold Comment body

ルーティングとコントローラーはこんな感じです。

# config/routes.rb
resources :comments
# app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  before_action :set_comment, only: [:show, :edit, :update, :destroy]

  # GET /comments
  def index
    @comments = Comment.all
  end

  # GET /comments/1
  def show
  end

  # GET /comments/new
  def new
    @comment = Comment.new
  end

  # GET /comments/1/edit
  def edit
  end

  # POST /comments
  def create
    @comment = Comment.new(comment_params)

    if @comment.save
      redirect_to @comment, notice: 'Comment was successfully created.'
    else
      render :new
    end
  end

  # PATCH/PUT /comments/1
  def update
    if @comment.update(comment_params)
      redirect_to @comment, notice: 'Comment was successfully updated.'
    else
      render :edit
    end
  end

  # DELETE /comments/1
  def destroy
    @comment.destroy
    redirect_to comments_url, notice: 'Comment was successfully destroyed.'
  end

  private
    def set_comment
      @comment = Comment.find(params[:id])
    end

    def comment_params
      params.require(:comment).permit(:body)
    end
end

よく見るコードですが、大事なコードです。 Scaffoldで作成されるコントローラーは、コントローラーの理想形だと思います。 できるだけこの形を壊さないように、修正していきます。

ネストしたリソースのコントローラー

少し遠回りになりますが、ポリモーフィック関連を実装する前にネストしたリソースを実装します。

ネストしたリソースというのはこれのことです。

resources :reports do
  resources :comments
end

通常のresources :commentsだとコメント一覧はGET /commentsです。

一方ネストしたリソースだとGET /reports/1/commentsになります。

URLでReportとCommentの親子関係を表現することで、id=1のreportに紐づくcomment一覧を取得できるようになります。

ポリモーフィック関連のリソースは必然的にネストしたリソースになります(たぶん)。 ポリモーフィック関連とネストしたリソースの2つを同時に実装すると、問題が起きた時に切り分けが難しくなるので、慣れないうちは分けて考えたほうがいいかと思います。

以下、ネストしたリソースのコードです。

モデル

# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :report
end

# app/models/report.rb
class Report < ApplicationRecord
  has_many :comments
end

ルーティング

resources :reports do
  resources :comments
end

コントローラー

class CommentsController < ApplicationController
  before_action :set_report
  before_action :set_comment, only: [:show, :edit, :update, :destroy]

  # GET /reports/1/comments
  def index
    @comments = @report.comments
  end

  # GET /reports/1/comments/1
  def show
  end

  # GET /reports/1/comments/new
  def new
    @comment = @report.comments.build
  end

  # GET /reports/1/comments/1/edit
  def edit
  end

  # POST /reports/1/comments
  def create
    @comment = @report.comments.build(comment_params)

    if @comment.save
      redirect_to [@report, @comment], notice: 'Comment was successfully created.'
    else
      render :new
    end
  end

  # PATCH/PUT /reports/1/comments/1
  def update
    if @comment.update(comment_params)
      redirect_to [@report, @comment], notice: 'Comment was successfully updated.'
    else
      render :edit
    end
  end

  # DELETE /reports/1/comments/1
  def destroy
    @comment.destroy
    redirect_to [@report, :comments], notice: 'Comment was successfully destroyed.'
  end

  private
    def set_report
      @report = Report.find(params[:report_id])
    end

    def set_comment
      @comment = Comment.find(params[:id])
    end

    def comment_params
      params.require(:comment).permit(:body)
    end
end

参考: https://github.com/amatsuda/nested_scaffold/blob/master/lib/generators/scaffold_controller/templates/controller.rb

ポリモーフィックなルーティング

上記のコードでは、createアクションのリダイレクト処理はこうなっています。

redirect_to [@report, @comment], notice: 'Comment was successfully created.'

[@report, @comment]の部分は、Rails内部でurl_for([@report, @comment])となり、/reports/1/comments/1というurlになります。

[@report, @comment]の部分はreport_comment_path(@report, @comment)という書き方もできます。 こっちの方がよく見る気がします。 しかしこっちの書き方だと、ポリモーフィック関連を使う際に問題が出ます。

後でポリモーフィック関連を実装する際に、reportだけでなくbookも扱えるようにするため、[@report, @comment]@reportの部分を@commentableに抽象化して[@commentable, @comment]にします。 この時@commentableの参照先はreportオブジェクトbookオブジェクトです。

Rails@commentableをいい感じに解釈して、@commentablereportオブジェクトの場合は/reports/1/comments/1というURLを作り、@commentablebookオブジェクトの場合は/books/1/comments/1というURLを作ってくれます。

このコード1つで2つのURLに対応してくれます。

ここでreport_comment_path(@report, @comment)と書いてしまうと、後に分岐処理を書くことになり、ポリモーフィック関連の強みを活かせません。 そのため[@report, @comment]という書き方を利用しています。

ちなみに[@report, @comment]のような書き方は、redirect_toだけでなくlink_toform_withでも利用できます。 この知識はビューを書く際に必要になります。

参考

ポリモーフィック関連のモデル

ポリモーフィック関連ポリモーフィック(polymorphic)とはオブジェクト指向の本などに出てくるポリモーフィズム(polymorphism)のことです。

Rubyの場合はダックタイピングの文脈で語られます。

そのためポリモーフィック関連を正しく使うためには、ポリモーフィズムダックタイピングの知識が必要になります。

自分はオブジェクト指向の知識が全くない状態でポリモーフィック関連を使い、悲惨な目にあいました😂

以下の2つの記事が素晴らしいのでおすすめです。

それではポリモーフィック関連を実装していきます。

素朴に実装するとこうなります。

# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :commentable, polymorphic: true
end

# app/models/report.rb
class Report < ApplicationRecord
  has_many :comments, as: :commentable
end

参考: https://railsguides.jp/association_basics.html#%E3%83%9D%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%95%E3%82%A3%E3%83%83%E3%82%AF%E9%96%A2%E9%80%A3%E4%BB%98%E3%81%91

これでも良いのですが、Commentableとしての責務をmoduleに切り出すとさらに良いです。 moduleに切り出すことで名前と境界が明確になり、ReportがCommentableとして振る舞えることを意識しやすくなります。

# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :commentable, polymorphic: true
end

# app/models/report.rb
class Report < ApplicationRecord
  include Commentable
end

# app/models/concerns/commentable.rb
module Commentable
  extend ActiveSupport::Concern

  included do
    has_many :comments, as: :commentable
  end
end

moduleに切り出す際にActiveSupportのconcernという機能を使っています。

参考: https://qiita.com/castaneai/items/6dc121ce6ff100614f42

これでreport - commentsだった関連がcommentable - commentsとなり、抽象化できました。 Bookでinclude Commentableすることで、bookreportを同じcommentableとして扱えるようになります。

ポリモーフィック関連のコントローラー

ネストしたリソースのコントローラーを修正してポリモーフィック関連のコントローラーにします。

ルーティングはそのままです。

resources :reports do
  resources :comments
end

コントローラーはこうなります。

class CommentsController < ApplicationController
  before_action :set_commentable
  before_action :set_comment, only: [:show, :edit, :update, :destroy]

  # GET /reports/1/comments
  # reportsと同じようにbooksをルーティングに追加すれば、
  # GET /books/1/commentsも可能になります。
  def index
    @comments = @commentable.comments
  end

  # GET /reports/1/comments/1
  def show
  end

  # GET /reports/1/comments/new
  def new
    @comment = @commentable.comments.build
  end

  # GET /reports/1/comments/1/edit
  def edit
  end

  # POST /reports/1/comments
  def create
    @comment = @commentable.comments.build(comment_params)

    if @comment.save
      redirect_to [@commentable, @comment], notice: 'Comment was successfully created.'
    else
      render :new
    end
  end

  # PATCH/PUT /reports/1/comments/1
  def update
    if @comment.update(comment_params)
      redirect_to [@commentable, @comment], notice: 'Comment was successfully updated.'
    else
      render :edit
    end
  end

  # DELETE /reports/1/comments/1
  def destroy
    @comment.destroy
    redirect_to [@commentable, :comments], notice: 'Comment was successfully destroyed.'
  end

  private
    def set_commentable
      resource, id = request.path.split('/')[1,2]
      @commentable = resource.singularize.classify.constantize.find(id)
    end

    def set_comment
      @comment = Comment.find(params[:id])
    end

    def comment_params
      params.require(:comment).permit(:body)
    end
end

基本的には@report@commentableに変えるだけです。

ただ、1つ難しい点があります。@commentableの取得方法です。

調てみるといくつか方法があるようですが、今回は実装が簡単なPolymorphic Association in Rails 5 の方法を使います。

request情報から強引に取得します。

    def set_commentable
      resource, id = request.path.split('/')[1,2]
      @commentable = resource.singularize.classify.constantize.find(id)
    end

ちなみにこちらの方法を使ってコントローラーを分けると、より綺麗な実装になります。

以上でポリモーフィック関連のコントローラーの完成です。

あとはBookReportと同じように実装して、ビューを修正すれば課題クリアになります。

お付き合いいただき、ありがとうございました🙇

フィヨルドブートキャンプ - 黒い画面入門をやってみた

# ディレクトリ移動
(env1) (✿╹◡╹✿)  ~ % cd /bin

# カレントディレクトリ
(env1) (✿╹◡╹✿)  /bin % pwd
/bin

# ファイル一覧
(env1) (✿╹◡╹✿)  /bin % ls
[          cat        cp         date       df         echo       expr       kill       launchctl  ln         mkdir      pax        pwd        rmdir      sleep      sync       test       wait4path
bash       chmod      csh        dd         domainname ed         hostname   ksh        link       ls         mv         ps         rm         sh         stty       tcsh       unlink     zsh

# lsコマンドは実際には/bin/lsを実行している
(env1) (✿╹◡╹✿)  /bin % /bin/ls
[               chmod           date            domainname      expr            ksh             ln              mv              pwd             sh              sync            unlink
bash            cp              dd              echo            hostname        launchctl       ls              pax             rm              sleep           tcsh            wait4path
cat             csh             df              ed              kill            link            mkdir           ps              rmdir           stty            test            zsh

# 自分の場合は少し違った。`ls -G`へのaliasが貼ってあるようだ
(env1) (✿╹◡╹✿)  /bin % which ls
ls: aliased to ls -G

(env1) (✿╹◡╹✿)  /bin % /bin/ls -G
[          cat        cp         date       df         echo       expr       kill       launchctl  ln         mkdir      pax        pwd        rmdir      sleep      sync       test       wait4path
bash       chmod      csh        dd         domainname ed         hostname   ksh        link       ls         mv         ps         rm         sh         stty       tcsh       unlink     zsh

# コマンドにはオプションが渡せる
# -aオプションは隠しファイルも表示する
(env1) (✿╹◡╹✿)  /bin % ls -a
.          [          cat        cp         date       df         echo       expr       kill       launchctl  ln         mkdir      pax        pwd        rmdir      sleep      sync       test       wait4path
..         bash       chmod      csh        dd         domainname ed         hostname   ksh        link       ls         mv         ps         rm         sh         stty       tcsh       unlink     zsh

# echoは引数を複数取れる
(env1) (✿╹◡╹✿)  /bin % echo hello my friend
hello my friend

# コマンドのマニュアル表示
(env1) (✿╹◡╹✿)  /bin % man ls

# curlでGET
(env1) (✿╹◡╹✿)  /bin % ~
(env1) (✿╹◡╹✿)  ~ % curl http://example.com -o foo.html
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1270  100  1270    0     0   5018      0 --:--:-- --:--:-- --:--:--  5000

# Macで関連付けられたソフトで開く
# この場合chromeが開く
(env1) (✿╹◡╹✿)  ~ % open foo.html

# コマンド置換
# ls /bin 同義
(env1) (✿╹◡╹✿)  ~ % cd /bin
(env1) (✿╹◡╹✿)  /bin % ls `pwd`
[          cat        cp         date       df         echo       expr       kill       launchctl  ln         mkdir      pax        pwd        rmdir      sleep      sync       test       wait4path
bash       chmod      csh        dd         domainname ed         hostname   ksh        link       ls         mv         ps         rm         sh         stty       tcsh       unlink     zsh


# Homebrewでパッケージ管理ができる
(env1) (✿╹◡╹✿)  /bin % brew search tree
# 省略

(env1) (✿╹◡╹✿)  /bin % brew install tree
# 省略

(env1) (✿╹◡╹✿)  /bin % brew uninstall tree
Uninstalling /usr/local/Cellar/tree/1.7.0... (7 files, 113.3KB)

Wardenの使い方 まとめ

  • 自分用のメモを公開したものです。ドキュメント/ソースコード等をまとめただけで試していないコードも多いので、信頼性は低いです。

Wardenとは?

  • 認証のフレームワーク。実際の認証は自分で実装する
  • 認証はRackミドルウェアで行う
  • Wardenをアプリで直接使うことはなさそう。deviseのような認証ライブラリのためのライブラリと考えたほうが良さそう
  • Deviseで使われてる。Wardenのstrategy/scope/callback等の知識がないとDeviseのコード読むのつらそう

導入

1. インストール

# Gemfile
gem 'warden'
gem 'bcrypt' # has_secure_passwordのため
$ bundle

2. ベースとなるRailsアプリ

  • routes
# config/routes.rb
Rails.application.routes.draw do
  get 'welcome/index'.
  root 'welcome#index'
end
  • コントローラー
# app/controllers/welcome_controller.rb
class WelcomeController < ApplicationController
  def index
    render text: "Welcome guest, it's #{Time.now}"
  end
end
  • Userのmigration
class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :username
      t.string :password_digest
      t.string :authentication_token # 認証にはこれを使う

      t.timestamps null: false

      t.index :authentication_token, unique: true
    end
  end
end
  • Userモデル
# app/models/user.rb
class User < ActiveRecord::Base
  after_create :generate_authentication_token!

  has_secure_password

  private

  # user生成後に一意となるauthentication_tokenを作成する
  def generate_authentication_token!
    self.authentication_token = Digest::SHA1.hexdigest("#{Time.now}-#{self.id}-#{self.updated_at}")
    self.save
  end
end

3. Strategyを定義する

  • strategyは認証の実装。これらを切り替えることで、認証方法を切り替えられる(Strategyパターン)
  • ここで定義するのは認証トークンを利用した認証
  • Railsの場合はlib/strategiesを用意してそこに置くのが良さげ
# lib/strategies/authentication_token_strategy.rb
class AuthenticationTokenStrategy < ::Warden::Strategies::Base
  # ガード
  # params['authentication_token']がある場合のみ、authenticate!を実行する
  def valid?
    params['authentication_token']
  end

  # params['authentication_token']を使い認証
  # userが存在すれば、認証成功
  # コントローラーでenv["warden"].authenticate!の形で利用する
  def authenticate!
    user = User.find_by_authentication_token(params['authentication_token'])

    if user
      # 認証成功
      # ここで渡したuserは、コントローラーからenv['warden'].userで取り出せる
      success!(user)
    else
      # 認証失敗
      # ここで渡したメッセージは、コントローラーからenv['warden'].messageで取り出せる
      fail!('strategies.authentication_token.failed')
    end
  end
end

4. Strategyをwardenに追加する

# config/initializers/warden.rb

require Rails.root.join('lib/strategies/authentication_token_strategy')

# `AuthenticationTokenStrategy`を`:authentication_token`で参照できるようになる
Warden::Strategies.add(:authentication_token, AuthenticationTokenStrategy)

5. wardenをRackミドルウェアスタックに追加

  • session(cookie)ミドルウェアの後に追加すべき。ここではflashの後に追加しておく。
# config/application.rb
config.middleware.insert_after ActionDispatch::Flash, Warden::Manager do |manager|
  # 先程定義したstrategyをデフォルトのstrategyとする
  manager.default_strategies :authentication_token
end

6. 認証情報をコントローラーから利用する

  • current_user等のメソッドをenv['warden']を使い定義する
  • authenticate!before_filterでリクエストのたびに実行されるようにする
# app/controllers/concerns/warden_helper.rb
module WardenHelper
  extend ActiveSupport::Concern

  included do
    # ビューでも使えるようにする
    helper_method :warden, :signed_in?, :current_user

    # before_filterで認証チェック
    prepend_before_filter :authenticate!
  end

  def signed_in?
    !current_user.nil?
  end

  def current_user
    warden.user
  end

  def warden
    request.env['warden']
  end

  def authenticate!
    warden.authenticate!
  end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
  include WardenHelper
end

strategy

strategyとは?

  • strategyは認証の実装。これらを切り替えることで、認証方法を切り替えられる(Strategyパターン)

Warden::Strategies::Baseとは?

  • 各strategyの親クラス
  • これのサブクラスを定義することで、独自のstrategyを定義できる

使い方1. Warden::Strategies::Baseを継承する場合

  • authentiate!は必須
  • valid?はなくてもOK。ない場合はtrueで、必ず実行される
# lib/strategies/authentication_token_strategy.rb
class AuthenticationTokenStrategy < ::Warden::Strategies::Base
  # ガード
  # params['authentication_token']がある場合のみ、authenticate!を実行する
  def valid?
    params['authentication_token']
  end

  # params['authentication_token']を使い認証
  # userが存在すれば、認証成功
  # コントローラーでenv["warden"].authenticate!の形で利用する
  def authenticate!
    user = User.find_by_authentication_token(params['authentication_token'])

    if user
      # 認証成功
      # ここで渡したuserは、コントローラーからenv['warden'].userで取り出せる
      success!(user)
    else
      # 認証失敗
      # ここで渡したメッセージは、コントローラーからenv['warden'].messageで取り出せる
      fail!('strategies.authentication_token.failed')
    end
  end
end

# 追加
Warden::Strategies.add(:authentication_token, AuthenticationTokenStrategy)

使い方2. 定義 + 追加

  • addする際に、同時に定義することも可能
  • 内部でWarden::Strategies::Baseのサブクラスを定義してる
Warden::Strategies.add(:authentication_token) do
  def valid?
    ...
  end

  def authenticate!
    ...
  end
end

使い方3. 復数strategyを使う場合

  • sample1のトークン認証strategyに加えて、Basic認証strategyも使う
# lib/strategies/basic_auth_strategy.rb
class BasicAuthStrategy < ::Warden::Strategies::Base
  def auth
    @auth ||= Rack::Auth::Basic::Request.new(env)
  end

  def valid?
    auth.provided? && auth.basic? && auth.credentials
  end

  def authenticate!
    user = User.find_by_username(auth.credentials[0])
    if user && user.authenticate(auth.credentials[1])
      success!(user)
    else
      fail!('strategies.basic_auth.failed')
    end
  end
end
  • WardenのStrategyに追加
# config/initializers/warden.rb
require Rails.root.join('lib/strategies/authentication_token_strategy')
require Rails.root.join('lib/strategies/basic_auth_strategy')

Warden::Strategies.add(:authentication_token, AuthenticationTokenStrategy)
Warden::Strategies.add(:basic_auth, BasicAuthStrategy)
  • デフォルトのstrategyに指定。デフォルトstrategyは復数指定可能
# config/application.rb
config.middleware.insert_after ActionDispatch::Flash, Warden::Manager do |manager|
  manager.default_strategies :authentication_token, :basic_auth
end
  • 順番に実行されて、halt!されるまでcascadeされる
env['warden'].authenticate

scope

scopeとは?

  • :user以外にも、:adminのようなユーザーを作ることができる。それぞれにstrategyを適用することができる
  • デフォルトのscopeは:default。scope指定がない場合は常に:default
  • deviseでも使われてる。current_userとかをcurrent_adminとして扱えるやつ

使い方

scopeを設定する

use Warden::Manager do |config|
  # default_stragetiesの代わりに、default_scopeを指定する
  config.default_scope = :user

  # 各scopeに、strategiesを指定する
  config.scope_defaults :user,        :strategies => [:password]
  config.scope_defaults :account,     :store => false,  :strategies => [:account_by_subdomain]
  config.scope_defaults :membership,  :store => false,  :strategies => [:membership, :account_owner]
  config.scope_defaults :api,         :store => false  ,:strategies => [:api_token], :action => "unauthenticated_api"
end

scopeを使用する

# 認証
env['warden'].authenticate(:scope => :admin)

# 認証済みか?
env['warden'].authenticated?(:admin)

# userへのアクセス
env['warden'].user(:admin)

# ログアウト
env['warden'].logout(:admin)

callback

callbackとは?

  • env['warden'].user等にcallbackを仕掛けられる

使い方

  • callbackは宣言順に実行される
Warden::Manager.after_set_user do |user, warden, opts|
  unless user.active?
    warden.logout
    throw(:warden, :message => "User not active")
  end
end

種類

Warden::Manager.after_set_user: userをset後

基本

  • trigger
    • env['warden'].userを初めて呼ぶ時
    • env['warden'].authenticate
    • env['warden'].set_uesr(user)
Warden::Manager.after_set_user do |user, warden, opts|
  unless user.active?
    warden.logout
    throw(:warden, :message => "User not active")
  end
end

only: 指定のtriggerだけ実行

# triggerは以下の3つ
env['warden'].user           # fetch
env['warden'].authenticate   # authentication
env['warden'].set_uesr(user) # set_user

# fetch時だけ実行
Warden::Manager.after_set_user only: :fetch

except: 指定のtriggerでは実行しない

Warden::Manager.after_authentication: 認証後

  • Warden::Manager.after_set_user only: :authentication のalias
Warden::Manager.after_authentication do |user,warden,opts|
  user.last_login = Time.now
end

Warden::Manager.after_fetch: user取得後

  • Warden::Manager.after_set_user only: :fetchのalias

Warden::Manager.before_failure: failure_app呼び出し前

Warden::Manager.before_failure do |env, opts|
  request = Rack::Request.new(env)
  env['SCRIPT_INFO'] =~ /\/(.*)/
  request.params[:action] = $1
end

Warden::Manager.after_failed_fetch: ?

  • trigger
    • env['warden'].user

Warden::Manager.before_logout: ログアウト前

  • trigger
    • env['warden'].logout
Warden::Manager.before_logout do |user,warden,opts|
  user.forget_me!
  warden.response.delete_cookie "remember_token"
end

Warden::Manager.on_request: リクエスト毎(wardenインスタンス生成時)

Warden::Manager.on_request do |warden|
  # do_something
end

Warden::Manager.prepend_xxxxx: prepend

  • prependしたい場合は、prepend_xxxxとする
  • prepend_before_failureとか

failure_app

  • 認証失敗時の処理をRackアプリとして実装できる
  • failure_appとしてセットされたRackアプリは、user.authenticate! で全ての認証が失敗した時に起動される

使い方1. lambdaを使う場合

  • RackアプリならなんでもOK。lambdaでもOK
config.middleware.use Warden::Manager do |manager|
  manager.default_strategies :authentication_token
  manager.failure_app = ->(env) { ['401', {'Content-Type' => 'application/json'}, { error: 'Unauthorized', code: 401 }] }
end

使い方2. クラスを使う場合

# app/controllers/unauthorized_controller.rb
class FailureApp
  def call(env)
    # `fail!`のメッセージを取得できる
    error_message = env["warden"].message

    status = 401
    headers = { "Content-Type" => "application/json"}
    body = [error_message]

    [stauts, headers, body]
  end
end
# config/application.rb
config.middleware.insert_after ActionDispatch::Flash, Warden::Manager do |manager|
  manager.default_strategies :authentication_token
  manager.failure_app = FailureApp
end

使い方3. Railsのコントローラーを使う場合

# config/initializers/warden.rb
Rails.application.config.middleware.use Warden::Manager do |manager|
  manager.default_strategies :authentication_token

  # アクションを直接指定してもよいが開発環境でキャッシュされてしまうようなので、lambdaにしておくのが良さそう
  manager.failure_app = ->(env) { SessionsController.action(:new).call(env) }
end

# app/controllers/sessions_controller.rb
def new
  flash.now.alert = warden.message if warden.message.present?
end

userをsessionに保存する

  • userインスタンスをそのままsessionに突っ込むのではなく、user.idだけ突っ込む
# /config/initializers/warden.rb
# sessionにはuser.idを保存する
Warden::Manager.serialize_into_session do |user|
  user.id
end

# sessionからuser.idを取り出して、userを取得する
Warden::Manager.serialize_from_session do |id|
  User.find(id)
end

未ログイン時のルーティングを指定する

  • constraintとwardenを利用することで、未ログイン時のルーティングを指定できる
# config/routes.rb
# 未ログインの場合は、このルーティングが適用される
scope constraints: ->(request) { request.env['warden'].user.nil? } do
  get "signup", to: "users#new", as: "signup"
  get "login", to: "sessions#new", as: "login"
end

resources :users
resources :sessions

テスト

ヘルパーを追加

  • login_aslogoutを追加してくれる
include Warden::Test::Helpers

login_as: ログイン

# "A User"として、ログインする
login_as "A User"

# "An Admin"として、ログインする
login_as "An Admin", :scope => :admin

# 両方で、ログインする
login_as "A User"

ログアウト

# ログアウトする
logout

# adminとして、ログアウトする
logout :admin

使いそうなメソッドまとめ

Warden::Strategies::Base

request: Rack::Requestオブジェクト

session: sessionオブジェクト

params: パラメータオブジェクト

env: Rackのenv

succes!: 認証成功 + halt!

  • halt!でstrategyのcascadingを止める
# このuserはenv['warden'].userでアプリから取り出せる
success! user

fail: 認証失敗 + halt!

  • halt!でstrategyのcascadingを止める
  • !なしだとhalt!しないっぽい。次のstrategyに移り実行する
# このメッセージはenv['warden'].messageでアプリから取り出せる
fail "Invalid email or password"

redirect!: リダイレクト + halt!

custom!: カスタムのRackアプリ配列 + halt!

halt!: strategyのcascadingを止める

  • 後続のstrategyは実行されない

pass: このstrategyを飛ばす

Warden::Manager

manager.default_strategies: デフォルトのstrategy

# passwordとbasicの2つのstrategyをデフォルトとしてセット
manager.default_strategies :password, :basicp

manager.failure_app=: failure_app設定

manager.failure_app = FailureApp

manager.default_scope: デフォルトのscope

use Warden::Manager do |manager|
  manager.failure_app = Public::SessionsController.action(:new)

  # default_stragetiesの代わりに、default_scopeを指定する
  manager.default_scope = :user

  # 各scope*4に、strategiesを指定する
  manager.scope_defaults :user,        :strategies => [:password]
  manager.scope_defaults :account,     :store => false,  :strategies => [:account_by_subdomain]
  manager.scope_defaults :membership,  :store => false,  :strategies => [:membership, :account_owner]
  manager.scope_defaults :api,         :store => false  ,:strategies => [:api_token], :action => "unauthenticated_api"

end

manager.scope_defaults: デフォルトのstrategy(scope)

Warden::Manager.serialize_into_session: sessionに保存する

Warden::Manager.serialize_into_session do |user|
  user.id
end

Warden::Manager.serialize_from_session: sessionから取り出す

Warden::Manager.serialize_from_session do |id|
  User.find(id)
end

Railsコントローラー

env['warden']: wardenを取得

env['warden'].user: 認証成功時にsetしたuser

env['warden'].user # デフォルトscope
env['warden'].user(:api) # scope指定

env['warden'].message: 認証失敗時にsetしたエラーメッセージ

env['warden'].authenticated?: 認証済みなら、true

env['warden'].authenticated? # デフォルトscope
env['warden'].authenticated?(:foo) # scope指定

env['warden'].authenticate!: 認証する

env['warden'].authenticate # 認証(失敗時に、処理を継続する)
env['warden'].authenticate! # 認証(失敗時に、例外を投げる)
env['warden'].authenticate(:password) # passwordストラテジーを利用する
env['warden'].authenticate(:password, :basic) # strategyは復数指定可能
env['warden'].authenticate!(:scope => :api) # scopeを指定する

env['warden'].valid?: ガード

env['warden'].set_user(@user): userを自分でセットする

env['warden'].set_user(@user) # デフォルトscope
env['warden'].set_user(@user, :scope => :admin) # scope指定
env['warden'].set_user(@user, :store => false) # 今回のリクエストだけで、sessionには保存しない

env['warden'].logout: ログアウト

env['warden'].logout # デフォルトscope
env['warden'].logout(:sudo) # scope指定

ざっくりコードリーディング

  • v1.2.8
  • 難しい...あんまりわからんかった

warden.gemspec

  • 依存gemはrackのみ

lib/warden/proxy.rb

  • env['warden']Warden::Proxy.new
  • env['warden']のメソッド群はここに定義されてる

lib/warden/strategies

  • Warden::Strategies.addが定義されてる

lib/warden/strategies/base.rb

  • Warden::Strageties::Baseは全てのStrategyの親クラス
  • Warden::Strategies.addを使えば、WArden::Strageties::Baseを継承した自前のstrategyを簡単に定義できる
  • succsess!等のメソッドはここに定義されてる

参考URL

RequestStoreの使い方 まとめ

  • 自分用のメモを公開したものです。ドキュメント/ソースコード等をまとめただけで試していないコードも多いので、信頼性は低いです。

request_storeとは?

  • リクエスト毎にグローバルな変数を使える
  • ☆975

導入

1. インストール

gem 'request_store'
$ bundle

2. 使ってみる

# ApplicationController
before_action :set_current_user
def set_current_user
  RequestStore.store[:current_user] = current_user
end

# モデル
RequestStore.store[:current_user] #=> current_user

類似機能との比較

Thread.current

  • スレッドローカル変数。つまりスレッド単位でグローバルな変数。(ドキュメントにはnot thread-local but fiber-localと書いてあったが違いがわからんかった。fiber使わんし難しい...)
  • request_storeは内部でコレ使ってる
# Thread.current: 現在のスレッド
# Thread.current[:foo] 現在のスレッドにおいて、グローバルな変数
Thread.current[:foo] = 0
Thread.current[:foo] #=> 0

問題点

  • アプリサーバはリクエスト1回分でスレッドが終了せず、次のリクエストも同じスレッドで処理する。その際に値がリセットされていないので、前回の値を持ち越してしまう
# 毎回0にリセットされず、1,2,3,...と増えていく
def index
  Thread.current[:counter] ||= 0
  Thread.current[:counter] += 1

  render :text => Thread.current[:counter]
end

Webrickでは問題ないらしい

ActiveSupport::CurrentAttributes

  • リクエスト毎にリセットされるスレッドローカルな属性を定義できる
  • こちらも内部でThread.currentを利用してる
  • ユースケースとしてはrequest_id等を想定してるっぽい
  • Rails5.2で追加
  • 参考: https://github.com/rails/rails/pull/29180

使い方

  • たぶんこんな感じ?自信なし
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
  # 属性定義。この属性はリクエスト事にリセットされる
  attribute :user
end


# before_actionで`Current.user`をセット。これでどこからでも`Current.user`でcurrent_userにアクセスできるようになる
class ApplicationController < ActionController::Base
  before_action :set_current_user

  def set_current_user
    Current.user = currrent_user
  end
end

# モデル
Current.user #=> currrent_user

使う際の注意点

グルーバルにアクセスできちゃう

  • グルーバルにアクセスできちゃうので、
    • MVCが壊れてカオスになる
    • テストが難しくなる
  • コレが必要になる場合は設計が間違っている可能性があるので、まずは設計を見直す
  • CurrentAttributesの記事だけど、同じことが当てはまりそう: https://techracho.bpsinc.jp/hachi8833/2017_08_01/43810

リクエスト毎にマルチスレッド使うのはNG

テスト

  • ミドルウェアを追加する必要あり
# spec_helper.rb
def app
  Rack::Builder.new do
    use RequestStore::Middleware
    run MyApp
  end
end

Rails以外で使う

  • ミドルウェアを追加すればOK
use RequestStore::Middleware

ざっくりコードリーディング

  • コードベースはかなり小さい

request_store.gemspec

  • 依存gem
    • rack: Rackミドルウェアを使うため

lib/request_store.rb

  • RequestStore
  • Thread.current[:request_store]を便利に扱えるようにしたラッパーのようなクラス
module RequestStore
  # `RequestStore.store[:foo]`は`Thread.current[:request_store][:foo]`に相当
  def self.store
    Thread.current[:request_store] ||= {}
  end

  ...
end

lib/request_store/middleware.rb

  • RequestStore::Middleware
  • レスポンス時にThread.current[:request_store]をクリアするRackミドルウェア
    # callはRackミドルウェアの規約
    def call(env)
      # リクエストの処理
      # フラグ立てる
      RequestStore.begin!

      # Rackアプリ本体の処理
      response = @app.call(env)

      # リクエスト時の処理
      # フラグ折る + データをクリア
      returned = response << Rack::BodyProxy.new(response.pop) do
        RequestStore.end!
        RequestStore.clear!
      end
    ensure
      unless returned
        RequestStore.end!
        RequestStore.clear!
      end
    end

lib/request_store/railtie.rb

  • ミドルウェアを追加したり

参考URL

Gonの使い方 まとめ

  • 自分用のメモを公開したものです。ドキュメント/ソースコード等をまとめただけで試していないコードも多いので、信頼性は低いです。

Gonとは

  • Railsの変数をJSで使えるようになる

導入

1. インストール

# Gemfile
gem 'gon'
$ bundle

2. セットアップ

# application.html.erb

<head>
  ...
  # application.jsの前に読み込めばロード前にgon変数にアクセスできる
  #
  # 以下のようなHTMLを生成してくれる
  # <script>
  # //<![CDATA[
  # window.gon={};gon.hoge=1;
  # //]]>
  # </script>
  <%= Gon::Base.render_data %>
  <%= javascript_include_tag "application" %>
  ...
</head>

3. 使ってみる

class ProductsController < ApplicationController
  def index
    # Rails側で変数をセット
    gon.hoge = 1

    ...
  end
end
# coffee

# JS側で使用できる
# 型キャストやエスケープもいい感じにやってくれる
gon.hoge // => 1

設定

使い方

  • Gon::Base.render_dataのオプションとして指定する
Gon::Base.render_data(watch: true)

watch: watch機能を有効化する

  • watch機能 を参照
  • 実際にはfalseでもwatch機能は使える。明示的に宣言するだけ
  • デフォルト: false

camel_case: キャメルケースに変換する

  • デフォルト: false
# 設定
Gon::Base.render_data(camel_case: true)

# ruby
gon.int_cased = 1

# js
gon.intCased #=> 1

camel_depth: キャメルケースを適用する深さ

  • デフォルト: 1
# 設定
Gon::Base.render_data(:camel_case => true, :camel_depth => 2)

# js
gon.testHash.testDepthOne.test_depth_two

namespace: gonという名前空間を変える

  • デフォルト: 'gon'
# 設定
Gon::Base.render_data(:namespace => 'serverExports')

# js
serverExports.your_int

init: window.gon = {}で初期化する

  • デフォルト: true
# js
# 初期化されてるので、データがない場合でもエラーにならない
window.gon // => {}

type: <script>type="text/javascript"を追加する

  • false
Gon::Base.render_data(type: true) #=> <script type="text/javascript">window.gon=...</script>

nonce: <script>nonce=...を追加する

  • CSP対応
  • デフォルト: nil
Gon::Base.render_data(nonce: 'test') #=> "<script nonce=\"test">...

need_tag: <script>タグあり

  • デフォルト: true
Gon::Base.render_data(need_tag: true)
#=> <script>
#=> //<![CDATA[
#=> window.gon={};gon.hoge="piyo";
#=> //]]>
#=> </script>
Gon::Base.render_data(need_tag: false)
#=> window.gon={};gon.hoge="piyo";

cdata: CDATAあり

  • デフォルト: true

global_root: globalという名前空間を変える

  • デフォルト: 'global'

amd: AMD対応

  • デフォルト: false
  • include_gon_amdと同じ

<script>タグのrenderメソッド*3

Gon::Base.render_data

  • 基本コレを使えばok

include_gon

  • Rails3の場合はこっちらしい
  • 内部でGon::Base.render_dataを利用してる
<%= include_gon %>

include_gon_amd

  • AMDの場合はこっち
  • 内部でGon::Base.render_dataを利用してる
<%= include_gon_amd %>

メソッド

gon.hoge=: set

gon.hoge = 1

gon.push: set

gon.push(hoge: 1, piyo: 2)

gon.hoge: get

gon.hoge #=> 1

gon.all_variables: setした全ての値

gon.hoge = 1
gon.piyo = 2
gon.all_variables #=> {"hoge"=>1, "piyo"=>2}

gon.clear: setした値を消す

gon.hoge = 1
gon.piyo = 2
gon.all_variables #=> {"hoge"=>1, "piyo"=>2}
gon.clear
gon.all_variables #=> {}

watch機能

  • Ajaxでポーリングして、gonのデータをリアルタイムで取得する

導入

1. watchオプションをtrueにする

  • どうもwatchオプションは明示的にする意味しかなく、指定しなくてもOKっぽい
# app/views/layouts/application.html.erb
<%= include_gon(watch: true) %>

2. アプリ作成

  • コントローラーでgon.watchを利用するのがポイント
# app/controllers/home_controller.rb
# ここにAjaxリクエストが送られる。そのたびにusers_countの値がレスポンスして更新される
def index
  @users_count = User.count
  gon.watch.users_count = @users_count
end
# app/views/home/index.html.erb
# Ajax成功時に、コールバックでここの表示が変わる
<div id='users-counter'></div>

3. JS

  • gon.watch()は一定間隔でリクエストを送り、コントローラーでgonにセットした変数をコールバックで利用する
# app/assets/javascripts/home.js.coffee
# コールバック
#   uesrs_count: Ajaxのレスポンス値
renewUsers = (uesrs_count) ->
  $('#users-counter').text(users_count)

# gon.watchの使い方
# gon.watchを使うとポーリングできる
# ここでは1秒ごとに`/home`にAjaxリクエストを送って、`#users-counter`の表示を変更している
#
# gon.watch(name_of_variable, options, callback)
#   name_of_variable: 変数名(コントローラーで`gon.watch.users_count = @users_count`とすれば、'users_count'となる)
#   options
#     interval: Ajaxリクエストする間隔。ms
#     method: HTTPメソッド。デフォルトはGET
#     url: 陸ストを送るURL
#   callback: Ajaxのコールバック
gon.watch('users_count', interval: 1000, renewUsers)

止める

  • gon.unwatchで止める
# ビュー
<a href='#' id='stop-renewing'>
  Stop renewing
</a>
# coffee
$('#stop-renewing').click ->
  # 止める
  gon.unwatch('users_count', renewUsers)
  return false

グローバルに使う

  • Gon.globalを使う
# config/initializers/some_initializer.rb
Gon.global.variable = 1
# coffee
gon.global.variable // => 1

RSpecでコントローラーテスト

# spec/support/shared_contexts/gon.rb
shared_context :gon do

  # Gonは内部でRequestStore(リクエストグローバルな変数。gem)を利用している
  # RequestStoreはRackミドルウェアで変数を削除する
  # しかしテストではRackミドルウェアを経由しない
  # そのためテストケース毎にGonの変数を手動でクリアする必要がある
  # 参考: https://tech.misoca.jp/entry/2015/06/15/151419
  let(:gon) { RequestStore.store[:gon].gon }
  before { Gon.clear }
end

# spec/controllers/thingies_controller_spec.rb
RSpec.describe ThingiesController do
  include_context :gon

  describe 'GET #new' do
    it 'gonifies as expected' do
      get :new, {}, valid_session

      # Gonに変数がsetされていることをテスト
      expect(gon['key']).to eq :value
    end
  end
end

ざっくりコードリーディング

gon.gemspec

  • 依存gem
    • actionpack: Railsのcontroller/routing
    • request_store: リクエストグローバルな変数
    • multi_json: 主要なJSON Engineに対応したJSONパーサ

lib/gon.rb

  • メインとなるGonクラス
  • コントローラーのgonの正体はGon
  • gon.hoge = 1gon.hogeにはmethod_missingを利用している。

lib/gon/base.rb

  • Gon::Base.render_data

-Gon::Base.render_dataのオプションのデフォルト値

    VALID_OPTION_DEFAULTS = {
        namespace: 'gon',
        camel_case: false,
        camel_depth: 1,
        watch: false,
        need_tag: true,
        type: false,
        cdata: true,
        global_root: 'global',
        namespace_check: false,
        amd: false,
        nonce: nil
    }

lib/gon/global.rb

  • Gon::GlobalはGonのサブクラス
  • Gon::Globalはgon.globalに対応
  • Gonとの違いはあんまりないっぽい

lib/gon/watch.rb

  • Gon::WatchはGonのサブクラス
  • Gon::Watchはgon.watchに対応

coffee/watch.coffee

  • gon.watchgon.unwatchgon.unwatchAllが定義
  • jQuery依存っぽい

js/watch.js

  • coffee/watch.coffeeをコンパイルしたファイル

参考URL