食べチョク開発者ブログ

食べチョクエンジニアによるプロダクト開発ブログ

食べチョクの自動テスト改善活動 〜これまでとこれから〜

みなさん初めまして。 QAエンジニアのujeです。

食べチョクでは、2022年4月から正式に自動テスト改善チームが発足いたしました。 チームメンバーは機能開発との掛け持ちにはなりますが、Webエンジニア5名・QA1名で取り組んでいます。

自動テスト改善チームの発足に伴い、食べチョクのテストにまつわる振り返りと、改善チームが取り組んでいることをお話しいたします。

これまで

食べチョクはサービスリリースから5年がたつプロダクトです。 特にここ1年半は食べチョクに参加してくれる仲間が増えており、サービス開発のスピードも上がっています。

食べチョクは2020年まで、テストカバレッジが低く、またなかなか向上しない状態でした。 システム全体に影響がある改修をする際は、毎回全画面を一通り手作業で触りテストするなど労力のかかる状態でした。 この状態を脱するため、2020年末から少しずつ自動テスト改善の動きが始まりました。

20年11月-バックエンドのRSpec積極拡張方針が決まる

これまでは機能開発が優先されていて重要なところだけ自動テストが書かれている状況でした。 そのため、この時期から実装した機能のRSpecによるテスト実装を後回しにせず、並行して増やしていく方針に変わっていきました。

21年8月-カバレッジをデータ化して見える化が始まる

開発方針が変わったものの増やしていくだけとなっていたので、どの程度カバーされているかは把握できていませんでした。 そのためこの時期からデータ化して定期的に展開するようになりました。 数字が出たことで意欲が向上したためか、拡張速度が上がったと実感するのもこの時期でした。

21年9月-E2Eテストの本格導入が始まる

9月からWeb版のE2E導入、10月からAndroid版のE2E導入が始まりました。 現在はリリース前にユニットテストの他に、1日1回の定期実行とリリース直前にE2Eが成功することを確認する仕組みになっています。 なお、iOSについては今後、本格導入を検討しています。

22年1月からJestの本格導入が始まる

一時期フロントにまつわる不具合が増加しつつあったことに起因して、フロントエンドの単体テストの用途としてJest導入に踏み切りました。 E2Eはシステムテストの位置付けで外側から目的が達成できるか確認することとしています。 Jestはフロントエンドに特化した単体テストとしてパラメータ受け渡しなどのUI影響がある要素に影響がないかを重視して実装していっています。

これから

冒頭でお話した通り4月から正式に自動テスト改善活動チームが発足いたしました。 各メンバー機能開発との掛け持ちしながらになりますが、webエンジニア5名・QA1名という体制になっています。

発足理由

まだまだ古くからあるコードは1テストすらないことが多いです。 テストのない箇所が開発速度に影響が出たこともあり、解消を意識したのが1点目です。

またQCDバランスを意識したリリースするには自動テストが必要不可欠だと判断したことが2点目です。 現在、アプリに関わらないバックエンド・フロントエンド要素は1日1回リリースしています。 さらにメンバー拡大に伴い同時並行で進行する機能開発が増えてきました。 この2つから、壊れた際の検知を早めることが格段に大事になってきました。

これらの要素を考慮した結果、組織的な積極対策を目的としてチーム発足へと繋がりました。

主な活動

自動テストの速度向上・自動テストの充実化を目標に取り組んでいます。 ただし掛け持ちなので、やることはみんなで分担する&週1日など無理のない範囲で改善活動に時間を当てるような形になっています。

具体的には実行速度の監視・速度改善の対策案を相談したり、充実化でもデータ化・方針策定などを始めています。

チーム運営

QAである私は主にMTG進行の役割を担っており以下のようなことに注意を払って運営しています。(書いてあることは基本的なことばかりです)

  • 事前資料の準備&事前展開・MTG近づいたらリマインド・事前に資料記載のお願い
  • レビューと計画を1セットにして1時間以内のMTG(時短できるなら時短を目指す)
  • OODAループを利用してネクストアクションを決める
    • ※Observe、Orient、Decide、Actの4項目を軸にループを回す方法です。別手法にはPDCAサイクルがあります

まだまだこれから

今のところネクストアクションが無いなどの事態はなく、少しずつ前に進んでいるかなと感じています。 とはいえ、掛け持ちで割ける時間も決まっていることから成果を出していくのはこれからです。

メンバーと意見を出し合いより良いテスト体制作りを目指します。

最後に

食べチョクでは仲間を募集しています。 ご興味がある方は是非、RECRUITからご応募ください。

食べチョクのプロダクトチームとチームトポロジー

食べチョクのプロダクトチームとチームトポロジー

この記事はビビッドガーデン Advent Calendar 2021 最終日の記事です。

皆さんこんにちは、エンジニアの西尾です。

2019年、食べチョクのプロダクトチームは数名だけでした。 2020年から少しずつ、2021年頭からは一気にメンバーが増加し、12月現在は30名近いメンバーが所属しています。

プロダクトチームはもともと大きな1チームでしたが、2021年からチーム分割を検討し、6月頃から本格的に分割を始め、12月現在は7つのチームでプロダクト開発をすすめています。

組織設計、チーム分割にあたり参考にしたのが、チームトポロジーの概念です。 今回はチームトポロジーの一部を食べチョクでの実例を踏まえて紹介しつつ、運用してみての感想や今後の課題について紹介します。

本記事を書くにあたり、日本語版チームトポロジーを大いに参考にしています。

チームファースト

食べチョクのプロダクトチームは、もともと1つだけでした。 徐々にメンバーが増えていき、分割前には10〜15名ほどが所属する、大きな1チームとなっていました。

大きすぎるチームでは以下の問題が発生していました。

  • プロダクトが1つのチームで把握するには大きく、メンバーのプロダクトに対する認知負荷がとても高い状態になっていた
  • プロダクトの端から端まで把握できるメンバーは極めて少ない。そのため仕事がその分野に詳しい個人に張り付く状態となっていた
    • 例えば、定期便の担当はMさん、商品検索はKさん、注文画面はFさん担当といった具合になっていた
  • 仕事が個人に張り付くことにより、計画がメンバー個人に左右される状態になっていた
    • 今週中に最優先の施策、機能のデリバリーができるかどうかは、担当する個人の能力や状況に左右される
  • チームとしてサポートできず、大きなプレッシャーが個人にのしかかる状態であった
    • 今週中に最優先の施策、Aさんどうか体を壊さずに無理して実装してくれ!

プロダクトチームのメンバーは続々と増えている状態でした。 増員したのだから、その分チームとしてのケイパビリティを高め成果を出していくことが求められます。 メンバー数名でうまくいっていたときの開発方法や構成のまま増員しても、チームのパフォーマンスはどんどん落ちていってしまいます。

プロダクトが大きく複雑化したことにともない、個人が気合でプロダクトを開発するフェーズは終わりを迎えました。 組織がスケールするために、より大きな成果をだしていくために、2021年はチームとして成果をだしていかなければならない、すなわち物事をすべてチームファーストで考えるフェーズに変わりました。

チーム分割のアンチパターン

チームとして成果を出していくためには、チームサイズは重要です。 効率よく動くためには、大きな1チームから複数の5〜9名が所属する小さなチームに分割する必要があります。

どう分割したらよいのでしょうか。 職能別に分ければよいのか、顧客別に分ければよいのか、機能ベースでわければよいのか、はたまたミッションベースでチームをわければよいのか。

いろいろなアプローチが浮かび、どれも一長一短だと思います。ただし、アンチパターンは存在します。

チームトポロジーでは、頻繁にメンバーが入れ替わるようなプロジェクトベースのチームは良くないと紹介しています。 機能A開発プロジェクトが立ち上がり、メンバーがプロジェクトごとに集められ、プロジェクトが終了したら解散するようなチームはよくありません。 それはチームに知識が蓄積されず結局は個人に作業が紐付いてしまいます。また、メンバーは頻繁にコンテクストスイッチを求められパフォーマンスが出せない恐れがあります。

場当たり的で無策のままチームを分割するのもよくありません。単に情報が分断されただけの開発のしにくい組織になってしまいます。

変更のフローに最適化した組織

チーム分割のアンチパターンはわかりました。ではどうやって分割すればよいのでしょうか。 チームトポロジーでは、変更のフローに最適化した組織を作るのがよいと紹介しています。

なかなか難しい言葉ですが、要はチーム間の引き継ぎを極力少なくし、自分たちのチームの中でできるだけ仕事が完結するチームにするのが良いとのことです。

エンジニアであれば、ときにはすべて自分ひとりで考えて作って運用したほうが速いのでは、と感じたことはないでしょうか。 なぜ速いと思うのか。それは仮説出し、機能検討、デザイン、設計、開発、テスト、デリバリー、運用保守とそこからのフィードバックがすべて自分だけで完結し、 他のメンバーとの調整が発生しなくて楽だからではないでしょうか。

チームにも同じことが言えます。 企画は企画チームが考えて、開発チームに引き継ぐ。開発チームは機能を作りテストチームに引き継ぐ。テストチームの許可がでたらデプロイし運用チームに引き継ぐ。 運用チームは気になる点がありフィードバックはしたいけど他のチームと調整するのは面倒だな。 このように引き継ぎが多いとプロダクトを素早く改善しチームとしてのパフォーマンスを出すことはできません。 重要なフィードバックや気付きも得にくく、良いプロダクトを作ることはできません。

ストリームアラインドチーム

チームトポロジーでは、モダンなソフトウェアの開発と運用に必要なのは、たった4つのチームタイプであると紹介しています。 ストリームアラインドチーム、イネイブリングチーム、コンプリケイテッド・サブシステムチーム、プラットフォームチームの4つです。

このうち、組織の根幹となるのがストリームアラインドチームです。

ストリームとはなんでしょうか。 チームトポロジーでは、「ビジネスドメインや組織の能力に沿った仕事の継続的な流れ」と呼んでいます。 またまた難しい言葉ですが、例えばプロタクト開発においては、顧客にどんな価値を届けるかを考え、 企画検討からデリバリー、その後のフィードバックをもとにした改善までの一連の仕事の流れのことをストリームと呼びます。

ストリームアラインドチームは、単一のストリームに沿って動くチームです。 例えば、「カート機能を通じて、顧客がほしいと思った複数の商品を送料を押さえつつ購入できる体験を提供する」といった価値ストリームに沿って動くチームです。 そもそも顧客の課題は何なのか、何をしたら改善するのか、どういう設計にしたらよいか、本番運用はどうしたらよいか、 ストリームアラインドチームは自分たちの中で作業が完結する能力を備えている必要があります。

モダンなソフトウェア組織では、ほとんどのチームがストリームアラインドチームとなるとのことです。 食べチョクのプロダクトチームでは、上記の考えを踏まえつつ、2021年12月現在は3つのストリームアラインドチームを作っています。

ストリームアラインドチームを支える3つのチーム

ストリームアラインドチームはすべての根幹となるチームです。 このチームタイプの他に、ストリームアラインドチームを支える以下3つのチームタイプが定義されています。 今回はチームタイプの紹介は割愛します。詳しくはチームトポロジーを参照ください。

食べチョクのプロダクトチームでは、2021年12月現在、以下3つのチームを設置しています。

  • イネイブリングチーム
    • 1つのチーム、5名のメンバーが所属しています。内訳は開発全般を担当するメンバーが2名、フロントエンドエンジニアが2名、QAエンジニアが1名です。
  • プラットフォームチーム
    • 1つのチーム、4名のメンバーが所属しています。
  • コンプリケイテッド・サブシステムチーム
    • 1つのチーム、3名のメンバーが所属しています。

チームインタラクションモード

チームトポロジーでは、チーム構成について述べているだけでなく、チーム間の関わり方、インタラクションについても紹介しています。 インタラクションモードは、コラボレーション、X-as-a-Service、ファシリテーションの3つを定義しています。この概念も詳しくはチームトポロジーを参照ください。

感想と今後の課題

ここまではチームトポロジーの基本的な概念を、プロダクトチームの実例を踏まえつつ紹介しました。 ここからは実際に採用してみて直面した問題や感想、今後の課題について紹介します。

チームインタラクションはまだまだこれから

インタラクションモードに関しては、食べチョクではうまく実践できてはいません。

コラボレーションについては、2021年12月現在は1つのストリームアラインドチームとコンプリケイテッド・サブシステムチームが行っています。 これはまあ2つのチームが密に協力して機能開発に取り組んでいるというだけです。 朝会や日々の開発、レトロスペクティブは2つのチームで合同実施していますが、プランニングはそれぞれのチームで行っています。

ファシリテーションに関してはイネイブリングチームが行ってはいますが、うまく機能しているのかと聞かれるとそんなことはなく、まだまだこれからです。 ファシリテーションという概念になじみがなく、そもそも何をすればよいのか、チーム内でもうまく認識はとれていないのが実情です。

X-as-a-Serviceでのインタラクションは今の所できていません。

X-as-a-Serviceモデルがうまく機能するのは、サービス境界が正しく選択、実装され、サービスを提供するチームが優れたサービスマネジメントを実践している場合に限られる

とありますが、食べチョクではサービスの境界がまだまだ曖昧なために、運用はできていません。

イネイブリングチームは概念がわかりづらく、運用が難しい

イネイブリングチームは設置していますが、まだ名前だけという状態です。運用が難しい理由は2つあります。

  • ファシリテーションがそもそも難しい
  • イネイブリングチームとはどういうチームなのか? チーム内にもチーム外にもうまく理解されていない

ファシリテーションについては前述したとおり、まだまだこれからです。

イネイブリングチームとはそもそも何なのか、組織内で学習ができていないというのもあります。 2021年12月に日本語版チームトポロジーが出るまでは、英語の文献と一部の日本語サイトでの紹介しかされていなかったことも大きいと思っています。 今は日本語で読めるようになったため、12月からはチーム内でのTeam Topologies読み合わせ会をすすめて、少しずつ概念の学習をすすめています。

今はファシリテーション先のチームでは、手が回らない施策や機能実装、テストを変わりに担当する、便利なお助けチームになってしまっていることが多いのが実情です。

ストリームアラインドチームが複数のストリームを持っている

ストリームアラインドチームは、単一の仕事のストリームに沿って動くと定義されていますが、現状は複数のストリームを1つのチームが持ってしまっています。

これはストリームの境界がまだまだ曖昧であること、 すべてのストリーム分のチームが用意できるほどメンバーがいないこと、 やることが多くてフォーカスが絞れていないなどのプロダクトマネジメント上の課題など、さまざまな要因が重なっての結果です。

チームの認知負荷を下げるアーキテクチャになっていない

これからの技術課題です。 個人だけでなくチームの認知負荷に注意し、チームが扱うコードベース、アーキテクチャも扱いやすいよう適切に整理していく必要があります。

食べチョクは、今はまだ大きなモノリシックアプリケーションです。 「会員登録の機能を修正したら、なぜか注文画面が壊れた」といった問題が発生することもありました。 ドメインの境界が曖昧で、今は幅広くコードベースを把握していないと怖くて手を入れられない状態にあります。

チームがデリバリーしやすいように、認知負荷を下げて安全に手を加えられるようにするために、チームに合わせてアーキテクチャを整理していかなければなりません。

おわりに

わたしたちはまだチームトポロジーの概念を参考に、チームを整理しただけという段階です。 道半ば、チームとして組織としてスケールし成果を出していくためには、まだまだ改善の余地があります。

チームトポロジーは銀の弾丸ではないので、導入したから組織がうまくまわるというものではありません。 チームのあり方を変えるだけでなく、健全な組織文化の構築、技術やアーキテクチャの整理、プロダクトロードマップの整理やマネジメントの強化などもあわせて行っていく必要があります。

2022年も、よりよい価値のあるプロダクトを生産者・ユーザーに届けられるよう、そして食べチョクがさらなる成長を遂げられるよう、これからも精進していく次第です。

「あ、面白そう」と思ってもらいたい。カジュアル面談の中身をザクッと公開します

f:id:vividgarden-tech:20211206111602p:plain この記事はビビッドガーデン Advent Calendar 2021 の4日目の記事です。

こんにちは。ビビッドガーデンのプロダクト開発チームで採用と組織開発を担当している平野です。 簡単に自己紹介をすると、「webエンジニア歴8年」「ビビッドガーデンには3年前に入社してここ1年半は採用にコミット」「好きなものはビールとハロプロとパデル」という人です。

ビビッドガーデンではプロダクト開発に関わる人の採用において、候補者の方との相互理解というのを重視しています。 すなわち候補者の方にはビビッドガーデンの良いところと課題となっているところを「そんなとこまで?」というレベルまで知ってもらおうと思っており、その起点となる場がまさにカジュアル面談だと捉えています。

この記事ではプロダクト組織のすべてのカジュアル面談に出席している私がカジュアル面談の弊社における立ち位置実際にどんな話をしているのかという部分を書くことで、応募者の方にとってカジュアル面談がより価値のある時間になる手助けができれば・・・と思っています。

カジュアル面談にかけてる思い

我々としては応募者の方に「ビビッドガーデンで働くとどんな価値が得られるか」を理解してもらうこと、さらに砕けた言い方をすると「あ、面白そう」と思ってもらうことがカジュアル面談のゴールだと定義しています。

応募者の方に時間を割いてもらっているので基本は応募者の方が聞きたい話を丁寧にさせて頂くのですが、「そもそもどんな働き方をしたい方なのか」「どのくらいビビッドガーデンのことを知っているのか」ということ応募者の方にお聞きして、それに合わせて弊社からさまざまな情報をお伝えしています。

しかし今やプロダクト開発に関わるメンバーの採用はどんな職種でも「選考する側」「選考される側」というものはなく「お互いがお互いを選考している」という状況です。 その上選考フェーズにおいては候補者の方とお話できる時間は非常に限られています。

限られた時間の中で弊社で働くことに価値を感じて頂く為に、ついついポジティブな面だけを強調してしまいがちですが、キャリアの方向性などにミスマッチの懸念があればお互いのために必ずそれをお伝えするようにしています。 相手をリスペクトしながら飾ることなくストレートに話を伝えあう弊社の雰囲気を私自身が体現すべきですし、そのためにカジュアル面談は「あくまでカジュアルなので〜」という感じではなく本気で、誠意を持って取り組んでいます。

こう書いてしまうと堅苦しい印象もありますが、お話自体はカジュアルに楽しくさせていただいております。カジュアル面談への応募は本当に、本当にカジュアルにしてください〜!

カジュアル面談の実際の流れ

ごあいさつ

カジュアル面談は主に私と現場メンバーのペア体制でやっており、最初に我々の自己紹介をさせてもらっています。応募者の方には自己紹介は求めていません。

そのままアイスブレイクを兼ねて「お互いのカジュアル面談のゴール」「お聞きしたいこと」を確認させてもらいます。「転職を考えていて、開発体制などの内情を詳しく知りたい」「転職はあまり考えてないが興味はある会社なので話を聞いてみたかった」「実家が農家でビジネスモデルについて知りたかった」などのお答えをいただきます。なんでもござれです。 あわせて「これは選考ではないので我々から何かしら判断をするということはありません」ということをお伝えしています。

弊社について

応募者の方が「お聞きしたいこと」次第で順番は左右しますが、カジュアル面談が終わったタイミングではすべての応募者の方が弊社に関する理解度が一定レベルになるようにお話するよう心がけています。 基本セットとしては以下内容をお伝えしています。

  • 何をしたい会社なのか
    • 会社のミッションとビジョンについて
  • そのために何を作っているのか
    • 食べチョクというプロダクトの詳細説明
  • それをどう作っているのか
    • 開発組織の話(ユニット制、技術スタック)
  • どんな人が関わっているのか
    • メンバーの紹介
  • 今後どうしていきたいのか
    • 今期と中長期に何をしていきたいのか
  • そのためにどのような人を増やしたいのか
    • 職種に応じてお伝えします

ここでは専用のスライド資料(今月中に公開予定です!!)を使って説明しながら、所々止まって「気になったところありますか?」「もっと深く知りたいところはありますか?」「逆にこちらから聞いてもいいですか?」という形で対話形式で話を進めています。事業の話、開発の話、会社の話、なんでもござれです。

また、具体的に理解してもらうということに重点を置いていて、実際のプロダクトの画面、Slack(社内の雰囲気理解)、GitHub(プロダクトチーム以外も使っています)、ユーザーからの反響(Twitterなど)も画面共有して見てもらうようにしています。(機密情報は伏せています)

多くはこの過程で応募者の方の「聞きたかったこと」をカバーするのですが、必ず「気になっていた点はクリアになりましたか?」と確認させてもらっています。

締め

全体を通じて大体45分で終了となります。最後に聞き逃したことが無いかを確認させてもらいます。面談終了後でもいつでも連絡いただければすぐに回答しますともお伝えしています。

興味がある方には今後の選考フローに関する説明をしますが、この場で選考に進むかどうかの意向確認は行っておりません。持ち帰っていただいて一言「選考に進みたい」とご連絡いただければすぐに次の選考へとご案内しています。

しかしこの場で選考に進む意思を口頭で伝えていただく方、面談終盤に「今チャット送りました」と言ってくれる方、面談終了1分後にチャットを送ってくれる方も多いです。素直に大変うれしい気持ちになります。

お時間頂いたことに感謝して終了となります。

よく聞かれること

ビジネスモデルが農協とバッティングしてそうだけど、大丈夫なの?

提供しているサービスやターゲットには違いがあるものの、生産者の方への価値貢献という側面では同じ方向を向いている同志というお話をしています。

代表秋元のブログに詳しく記載があるので、気になる方は是非そちらも読んでみてください。一部引用します。

・提供しているサービス価値は異なり、すみ分けができていると認識しています。結果として農協さんに呼んでいただいて講演させていただくことも多々あります。

・皆さんが「一次産業を良くしていきたい」という共通の想いを持っています。 note.com

サービスの成長と共にプロダクト組織はどう変わりましたか?

1年半前まではwebエンジニア3-4人という体制でやっていましたが、今となっては以下の各職種のメンバーが在籍しており40名に迫る組織となっています。

  • webエンジニア
  • モバイルエンジニア
  • インフラエンジニア
  • QAエンジニア
  • データアナリスト
  • UIデザイナー
  • PdM

組織体制という観点では1つのチームがすべての開発を受け持っていた形から、プロダクトに対して定められたミッションを複数のユニットがそれぞれ自律して取り組むという体制に。

開発方針という観点では「作る、リリースする」ということが目的になりつつあった状態から、どのくらい価値提供ができたかという軸での分析を元に仮説検証を重ねて探求と学習を進める体制へと変わりました。

この部分は1年前から角谷さんに手伝ってもらっており、一緒に議論し続けて今の形になっています。現在も継続議論中です。 offers.jp

体制に関してはゴールは無いので、今後も常にアップデートし続ける、変化を歓迎するという姿勢を取っていきます。

エンジニアの評価制度はありますか?

昨年末に制定したものがあります。 簡単に説明するとにグレード毎に期待成果と年収が記載されており、メンバー各々がそれに沿った形で目標設定をしています。 半期ごとに評価が行われますが評価者との中間評価と毎月の1on1、私や各チームのリードメンバーとの毎週の1on1で軌道修正を図っています。

しかしこの評価制度はまだまだベータ版だと思っており、エンジニアが出す価値をちゃんと評価できているだろうかという観点ではまだまだ改善の余地があると思っています。 今月に評価制度のアップデートがありましたが、引き続きアップデートを重ねていく予定です。

どんな人がいますか?

プロダクト組織は全員中途入社で、出身企業はベンチャーからSIなどひとくくりにはできないくらい多様です。 しかしマインドという観点では皆下記2つの考えを持っています。

  • 自分の手でプロダクトを作り、それが誰かの課題を解決し、その様子が手にとるように分かる環境で働きたい
  • より良いプロダクトを作るために、慣れた体制や技術に固執せず、変化やアップデートすることを歓迎する

このマインドに加え「生産者の方への価値貢献」が全メンバー自分事化している点がチーム全体が一体感を持って前に進める秘訣だと感じています。

「実家が農家でした」などの農業バックグラウンドがあるメンバーは多くないのですが、 それでも生産者の方に関わるという事が自分事化するのは日常的に生産者さんの声やフィードバックが聞こえるプロダクトという特性のおかげだと思っています。

おわりに

ご覧いただきありがとうございました。 ビビッドガーデンは11月をもって6期目に突入しました。

1年前はエンジニア数人とデザイナーでがむしゃらに作っていた感じでしたが、1年で複数の少人数チームが自律的にデリバリーと仮説検証を進める体制へと大きく進化しました。 6期目はさらなる価値提供のために「より深くより広い領域で仮説検証をする」、「デリバリーを安定的に行う開発基盤を強固にする」という2点を実現させたいと思っています。

会社としても立ち上げ期から拡大期に一気に変わってきた状態です。ドラスティックに環境が変わり続ける現場にてより多くのメンバーと一緒にお仕事を楽しみたいと思っています。

カジュアル面談は以下媒体より申し込みが可能です。

Meetyでカジュアル面談

選考フローも必ず最初にカジュアル面談をはさみますのでこちらでエントリー頂いても大丈夫です。エントリーの時点では履歴書等必須ではありません。 herp.careers

それでは、カジュアル面談にてお会いしましょう!

監査ログをファイルに記録するためのGemを公開しました

こんにちは。
食べチョクの開発を副業でお手伝いしているプログラマーの花村です。

監査ログをJSONL(JSON Lines)のファイルに記録するためのGemのAuditLoggableを作成してrubygems.orgで公開しました。
ソースコードもGitHubで公開しています。 ​ rubygems.org github.com

なぜ開発したのか

食べチョクでは監査ログを記録するためにAuditedを利用していました。
AuditedはActiveRecordのコールバックを利用してモデルの変更を手軽にRDBに記録してくれる大変便利なGemです。

しかしRDBに記録するためテーブルサイズが肥大化しパフォーマンスに影響を与える場合があるというデメリットもあります。
食べチョクでは注文数の増加に伴ってこの問題に直面しました。

これを解決するにあたり以下のようなアプローチが思いつきます。

  1. 監査ログを定期的に消す
  2. 監査ログを記録するDBを分ける
  3. 監査ログをRDBとは別のストレージに記録する

1では過去分のデータが必要になった際に困りますし、2では延命措置にしかなりません。
ということで今回は3を採用しました。

記録したログファイルの扱い

AuditLoggableで記録したログファイルはCloudWatch Agentを利用してCloudWatch Logsに連携しています。
CloudWatch Logsには1か月分ほど保持する設定にしており、毎日CloudWatch LogsからS3へバックアップしています。
このときにCloudWatch LogsのExportTask機能を使ってエクスポートするとJSONのメッセージに余分な情報(CloudWatch Logsに取り込んだ日時の情報)がついてしまうので、S3のイベント通知を使いLambdaで整形処理を走らせています。

CloudWatch Logsに保持し続けずにS3に移しているのはコスト圧縮のためです。 1カ月より過去分を参照したいときはAthenaで検索できるようにしてあります。

なぜログファイルに記録するのか

監査ログの記録先としてRDB以外を使うのであればDynamoDBなどのNoSQLなDBに記録することも考えられます。
しかし以下の理由で記録先はファイルとしました。

  1. 記録の失敗しにくさ
  2. リアルタイムに参照できる必要はない​

Web API経由での記録では失敗することも多いのでそのためのハンドリングが煩雑になることが予想されます。
また監査ログを記録された瞬間から参照したいことはないので収集にラグがあっても構わないという判断がありました。

使い方

config/initializers/audit_loggable.rb など初期化ファイルで出力先のファイル名などを設定します。

AuditLoggable.configure do |config|
  if Rails.env.test?
    config.auditing_enabled = false
  end
  config.audit_log_path = Rails.root.join("log", "audits.log")
end

ApplicationRecord など必要なモデルで AuditLoggable::Extension でActiveRecodのモデルに機能を追加します。

class ApplicationRecord < ActiveRecord::Base
  extend AuditLoggable::Extension
end

監査ログを記録したいモデルで log_audit を呼び出して記録対象に設定します。

class Post < ApplicationRecord
  log_audit
end

ApplicatoinController などで以下のように情報集する機能を有効化することで「操作したユーザーID(とクラス名)」、「リモートIPアドレス」、「リクエストID」を監査ログに記録できるようになります。

class ApplicationController < ActionController::Base
  around_action AuditLoggable::Sweeper.new(current_user_methods: %i[current_user])
end

Auditedとの違い

Auditedと似た挙動をしますが主に以下の違いがあります。

  • 記録先はJSONLのログファイル
    • RDBではなくJSONLのログファイルに記録します
  • ActiveRecordのモデルへのメソッド追加は最小限
    • Auditedでは audit_xxx のようなインスタンスメソッドが複数追加されますが、AuditLoggableでは1組のaccessorの追加のみです
    • またクラスメソッドは機能の有効化用メソッド1つを追加するのみです
  • Associated Auditsは未サポート
    • 食べチョクでは不要であったことと複雑になってしまうので未サポートとしました
  • 条件付き記録は未サポート
    • 食べチョクでは不要であったことと複雑になってしまうので未サポートとしました

おわりに

似たような課題を抱えている方の解決の一助になれば幸いです。 もしバグを見つけたり機能を追加したいなどありましたらGitHubでPull RequestやIssueをお待ちしています。

github.com

map,filter,reduce関数内で状態を書き換えてはいけないのは、なぜですか

皆さんこんにちは、エンジニアの西尾です。

あなたは今、コードレビューをしています。

以下コードに直面したとき、何を指摘しますか。 修正してほしい点を、どのようにレビュイーに伝えますか。

// これはJavaScriptのコードです。
// 商品の在庫を1つ減らし、売り切れになったものを抽出したい、と思っています。

const soldOutProducts = products.filter(product => {
  product.quantity -= 1;

  return product.quantity <= 0;
});

よくないコードレビューの例

問題は表題の通り、filterの中で状態を書き換えているのが、よくありません。

関数型言語を学んだことがある方なら、このコードの違和感に気がつきます。 filterは純粋関数であるべきだ、副作用を起こしてはいけない。そう認識しているからです。

しかし、それをコードレビューで指摘したとして、相手に伝わるでしょうか。 書いている言語は関数型ではないし、副作用とか言われても意味がわからないし、複雑なコードでもないし、動くからいいんじゃないの。

map,filter,reduce内で状態を書き換えるコードは、思いの外よく見かけます。 そのたびに、私は以下のような指摘をしてしまっていました。

f:id:vividgarden-tech:20210123215718p:plain

f:id:vividgarden-tech:20210123215721p:plain

f:id:vividgarden-tech:20210123215725p:plain

問題は何なのか?

filter内で状態を書き換えてはいけません、と伝えるだけでは、レビューを受けた側も意味がわかりません。 このコードが良くない理由を説明しないと、相手には伝わりません。

なぜ状態を書き換えてはいけないのか。 言われてみると、私自身もすんなり理由を説明できませんでした。

そこで、自分なりにこのコードの問題点を、今一度考えてみました。

複数の目的をもったコードは、わかりづらい

UNIXの基本的な考えに、「ひとつのプログラムには、ひとつのことをうまくやらせる」というものがあります。 目的を最小限に抑えた小さなプログラムは、誰にとってもわかりやすく、保守も容易です。

プログラムに限らず、この考えは1つの関数・1つのブロック・1行のコードにも当てはまります。 複数の目的を持った処理は理解しづらいものです。

今回の例では、「在庫を減らす」操作と「売り切れの商品を抽出する」操作が同時に行われているため、 複雑なコードに見えます。

本来の目的と違う使い方をしているから、わかりづらい

filter関数は、与えられた条件に当てはまるデータを抽出(フィルタリング)するために利用します1。 「フィルターをする」処理をお願いしたのに、在庫を減らす処理も同時にされてしまうのは、驚きがあるロジックです。

mapやreduceも同様で、「渡されたリストを別のものにマッピングする」「渡されたリストを畳み込む」以外の挙動をさせるのは、理解しづらいコードです。

広い範囲に影響を及ぼすロジックは、わかりづらい

呼び出すたびに中身が書き変えられるロジックは、わかりにくいです。

例えば、次のようなメソッド呼び出しで中身も書き換えられてしまったら、直感に反する理解しづらいコードではないでしょうか。

console.log(product); // { name: "商品1", quantity: 3 }

const quantity = getQuantity(product);

console.log(product); // { name: "商品1", quantity: 2 } !? まさか中身が書き換わっているとは
function getQuantity(product) {
  product.quantity -= 1; // 渡された引数の中身を破壊している

  return product.quantity;
}

これは極端な例ですが、今回の例にあるfilterの使い方も、同じようなわかりづらさがあります。

じゃあ、どうすればいいの?

どのように修正すればよいでしょうか。 コード断片だけを見て、適切なアドバイスをするのは難しいです。 在庫を減らすという処理は、別のところで、あらかじめしておくべきでしょうか。設計から見直す必要があるかもしれません。

それでも直すとしたら、以下のようになるでしょうか。

products.forEach(product => {
  // どうしても値を書き換えたかったら、eachを利用する
  product.quantity -= 1;
});

const soldOutProducts = products.filter(product => product.quantity <= 0);

値を書き換える必要があるのなら、forやforEachを使います。 もちろん、書き換えずにすむなら、それに越したことはありません。

おわりに

map,filter,reduce関数内で値を書き換えてしまう違和感を、言語化してみました。 これらの関数をfor(each)と同じ感覚で使ってしまう方が、案外多いように思えます。

今回紹介したメソッドは、あくまで一例です。 例えば、RubyのEnumerableにあるようなメソッドで値を書き換えるのは、避けるべき実装です。

この問題は、コードレビューの時点で、私自身もすんなり理由を回答できませんでした。 あたりまえだと思っていることを見直すことで、自分自身の理解もより一層深めることができました。

www.wantedly.com

www.wantedly.com


  1. 余談ですが、この記事を書くためにリーダブルコードを読み返していました。第3章に、filterという名前は「選択する」のか「除外する」のかあいまいだから避けるべき、との指摘をみつけて、確かに!と納得しました。ただ、JavaScriptにselectはないので、しょうがない。

会社の支給PCがMacBook Pro M1なので、新しく開発環境を構築した話

こんにちは。

今年の年始からジョインした遠藤です。

さて、入社したところ会社支給のMacBook ProがM1チップのものでした。

はい、現状は開発環境で苦労するとか色々噂を聞くやつです。

実際に試したのですが、

  • 現状の開発環境構築スクリプト、手順書が一切使えない
  • VitualBox, Vagrantは利用不可
  • Dockerは利用可能ではあるが、一部イメージが対応されてない
  • 古いパッケージは動かす手段がない

などなど、通常ではぶつからない問題にぶつかります。

食べチョクでは、

  • Ruby
  • Node.js
  • MySQL
  • Redis
  • ElasticSearch
  • Kibana

を利用しています。

この辺りをメインに話つつ、Intel版とこんな風に違うのかっていう辺りの雰囲気を感じ取っていただければと思います。

どこに開発環境を構築するか

まず、どこで開発環境を構築するかを考えてみたいと思います。

  • ローカルで開発環境を作る
  • 仮想化ソフトウェア上で作る
    • VirtualBox🙅‍♂️
    • Docker
  • サーバー立ててその中に開発環境を作成して、リモートで作業する

この3種類になると思いました。

支給されたPCだとローカルに開発環境を構築される方が多いのかなって印象が多いのですが、どうなのでしょうか。

私は支給されたPCだとローカルに開発環境を構築したい派なので、ローカルでの開発環境構築をひとまず目標にしました。

今回は「ローカルで開発環境を作る」「仮想化ソフトウェア上で作る」で試しました。

では、開発環境の構築をしていきます。

ローカルで開発環境を作る

ここから実際に行ったことを記述していきたいと思います。

homebrew

みんな大好きのhomebrewですが、きちんと公式のドキュメントを読みましょう。

インストールする場所がm1 macの場合は別になっています。

mkdir /opt/homebrew && curl -L https://github.com/Homebrew/brew/tarball/master | tar xz --strip 1 -C homebrew
echo "export PATH=/opt/homebrew/bin:$PATH" >> ~/.zshrc
source .zshrc

However do yourself a favour and install to /usr/local on macOS Intel, /opt/homebrew on macOS ARM, and /home/linuxbrew/.linuxbrew on Linux. Some things may not build when installed elsewhere. One of the reasons Homebrew just works relative to the competition is because we recommend installing here. Pick another prefix at your peril!

Installation — Homebrew Documentation

Intel版ならディレクトリを明示するとかなかったので、いつもと違うなっていうことを、最初から感じさせてもらえます。

rbenv

Rubyのバージョン管理ツールのrbenvです。

こちらがないとRubyのバージョン管理で困るので、導入しましょう。

GitHub - rbenv/rbenv: Groom your app’s Ruby environment

brew install rbenv
rbenv init
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash

ここまでは問題なくいきます。

さて、待望のインストールをします!?

rbenv install 2.5.7
Downloading openssl-1.1.1i.tar.gz...
-> https://dqw8nmjcqpjn7.cloudfront.net/e8be6a35fe41d10603c3cc635e93289ed00bf34b79671a3a4de64fcee00d5242
Installing openssl-1.1.1i...
Installed openssl-1.1.1i to /Users/xxx/.rbenv/versions/2.5.7

Downloading ruby-2.5.7.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.7.tar.bz2
Installing ruby-2.5.7...

WARNING: ruby-2.5.7 is nearing its end of life.
It only receives critical security updates, no bug fixes.

ruby-build: using readline from homebrew

BUILD FAILED (macOS 11.0.1 using ruby-build 20201225)

Inspect or clean up the working tree at /var/folders/np/m8mjm0q52njgyqc6kp0b_zw80000gn/T/ruby-build.20210110220451.39283.BzZl54
Results logged to /var/folders/np/m8mjm0q52njgyqc6kp0b_zw80000gn/T/ruby-build.20210110220451.39283.log

Last 10 log lines:
compiling ../.././ext/psych/yaml/reader.c
3 warnings generated.
compiling ../.././ext/psych/yaml/emitter.c
compiling ../.././ext/psych/yaml/parser.c
linking shared-object date_core.bundle
5 warnings generated.
linking shared-object zlib.bundle
1 warning generated.
linking shared-object psych.bundle
make: *** [build-ext] Error 2

ログファイルを確認します。

compiling qsort.c
linking shared-object -test-/vm/at_exit.bundle
linking shared-object -test-/wait_for_single_fd.bundle
compiling closure.c
compiling nofree.c
compiling conversions.c
compiling zlib.c
installing default libraries
compiling fiddle.c
compiling psych_to_ruby.c
closure.c:264:14: error: implicit declaration of function 'ffi_prep_closure' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
    result = ffi_prep_closure(pcl, cif, callback, (void *)self);
             ^
1 error generated.
make[2]: *** [closure.o] Error 1
make[2]: *** Waiting for unfinished jobs....

なんか今までにあまり起こったことのないエラーに遭遇します。

ffiでエラーが起こっているのですが、libffiを除外すればインストールできます。

Building Ruby on arm64 macOS

この問題が2020/6/30に解決されていることに感動を受けつつ、除外しましょう。

RUBY_CFLAGS=-DUSE_FFI_CLOSURE_ALLOC rbenv install 2.5.7

コマンド自体はissueを参考にさせていただきました。(先人に感謝)

2.6.6 on ARM64 · Issue #1699 · rbenv/ruby-build · GitHub

Ruby3.0の場合はなんの問題もなくインストールできます。

MySQL

brew install mysql@5.7
echo 'export PATH="/opt/homebrew/opt/mysql@5.7/bin:$PATH"' >> ~/.zshrc
brew services start mysql@5.7

正直ここは何も問題がないので、休憩ゾーンになります。

Redis

brew install redis
brew services start redis

これも特に問題はありません。

Node.js

Node.jsは今のところversion 15以上じゃないと対応されてない状態なので、ここは素直にversion 15を使いました。

webpackのコンパイルでしか利用しないので、まぁ、いいかという気持ちもあり。。。

Nodejs 14.x doesn't compile on ARM OSX (M1) · Issue #36161 · nodejs/node · GitHub

弊社ではnodebrewを利用していたので、下記を参考にさせていただきました。

brew install nodebrew
echo "export PATH=$HOME/.nodebrew/current/bin:$PATH" >> ~/.zshrc
vim $(which nodebrew)

sub system_info {
    my $arch;
    my ($sysname, $machine) = (POSIX::uname)[0, 4];

    if  ($machine =~ m/x86_64|arm64/) {
        $arch = 'arm64';
    } elsif ($machine =~ m/i\d86/) {

...

nodebrew compile v15.5.0

M1 Mac を購入して arm64 縛りでインストールしたもの (更新中) - アルパカ三銃士

ElasticSearchのインストール

弊社では検索エンジンにElasticSearchを利用しているので、ElasticSearchをインストールします。

brew install --build-from-source elasticsearch
...

==> Installing dependencies for elasticsearch: openjdk and gradle
==> Installing elasticsearch dependency: openjdk
==> Patching
==> Applying f80a6066e45c3d53a61715abfe71abc3b2e162a1.patch
patching file src/hotspot/share/runtime/sharedRuntime.cpp
Hunk #1 succeeded at 2850 (offset -6 lines).
==> Applying 4622a18a72c30c4fc72c166bee7de42903e1d036.patch
patching file src/java.desktop/macosx/native/libawt_lwawt/awt/CSystemColors.m
==> ./configure --without-version-pre --without-version-opt --with-version-build=9 --with-toolchain-path=/usr/bin --with-sysroot=/Library/Developer/Com
Last 15 lines from /Users/endo/Library/Logs/Homebrew/openjdk/01.configure:
checking for stdlib.h... yes
checking for string.h... yes
checking for memory.h... yes
checking for strings.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for unistd.h... yes
checking stdio.h usability... yes
checking stdio.h presence... yes
checking for stdio.h... yes
checking size of int *... 8
configure: The tested number of bits in the target (64) differs from the number of bits expected to be found in the target (32)
configure: error: Cannot continue.
/private/tmp/openjdk-20210110-26180-4xtn4y/jdk15u-jdk-15.0.1-ga/build/.configure-support/generated-configure.sh: line 82: 5: Bad file descriptor
configure exiting with result code 1

Do not report this issue to Homebrew/brew or Homebrew/core!

These open issues may also help:
Cassandra 3.11.9_1 crashes in openjdk@8 JVM https://github.com/Homebrew/homebrew-core/issues/66462
openjdk: Add support for Apple silicon https://github.com/Homebrew/homebrew-core/pull/65670
OpenJDK is somewhat broken on newer MacOS instances, console is flooded with errors when using JMeter, AdoptOpenJDK has no issues https://github.com/Homebrew/homebrew-core/issues/66953

案の定エラーになります。

原因はOpenJDKがサポートされてないので、利用できません。

https://github.com/Homebrew/homebrew-core/pull/65670

このPR早くマージしてくれ!という気持ちもありつつ、OpenJDKはZuluがM1対応しているので、そちらを利用します。

Support of Java Builds of OpenJDK for Apple Silicon | Azul

Java Download | Java 8, Java 11, Java 13 - Linux, Windows & macOS

※記事執筆中にマージされていましたので、この問題は解決していましたが、https://github.com/Homebrew/homebrew-core/pull/69029で別の問題が発生しています

ElasticSearchはソースコードからビルドするように変更します。

wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.10.1-darwin-x86_64.tar.gz
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.10.1-darwin-x86_64.tar.gz.sha512
shasum -a 512 -c elasticsearch-7.10.1-darwin-x86_64.tar.gz.sha512 
tar -xzf elasticsearch-7.10.1-darwin-x86_64.tar.gz
cd elasticsearch-7.10.1/ 

Javaの参照先をZuluに追加します。

Set up Elasticsearch | Elasticsearch Reference [7.10] | Elastic

echo "export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-15.jdk/Contents/Home" >> ~/.zshrc

これでbin/elastic searchで動くのですが、xpackがサポートされないので、コンフィグから外しましょう。

org.elasticsearch.ElasticsearchException: X-Pack is not supported and Machine Learning is not available for [darwin-aarch64]; you can use the other X-Pack features (unsupported) by setting xpack.ml.enabled: false in elasticsearch.yml

Kibana

ElasticSearchを入れるので、Kibanaも入れましょう。

homebrew経由でインストールしようとすると、案の定エラーになるのでソースコードからビルドします。

curl -O https://artifacts.elastic.co/downloads/kibana/kibana-7.10.1-darwin-x86_64.tar.gz
curl https://artifacts.elastic.co/downloads/kibana/kibana-7.10.1-darwin-x86_64.tar.gz.sha512 | shasum -a 512 -c - 
tar -xzf kibana-7.10.1-darwin-x86_64.tar.gz
cd kibana-7.10.1-darwin-x86_64/

bin/kibana

ここまで揃うと、なんとかbundle exec rails serverまでは辿りつけると思います。

ここまでがローカル開発環境編です。

仮想化ソフトウェア上で作る

現状はVirtual Boxが対応していないので、Docker Previewが頼みの綱になります。

Docker Previewはrosettaが現状は必要になるので、rosettaをインストールします。

softwareupdate --install-rosetta

Docker Previewのダウンロードは下記にリンクがあるので、そちらかダウンロードします。

Download and Try the Tech Preview of Docker Desktop for M1 - Docker Blog

ここではうまくいきづらいものだけ紹介します。

docker-composeを利用することを前提にしていきます。

MySQL

設定ファイルです。

services:

  mysql:
    image: mysql:5.7
docker-compose up
Pulling mysql (mysql:5.7)...
5.7: Pulling from library/mysql
ERROR: no matching manifest for linux/arm64/v8 in the manifest list entries

ということで、イメージとして対応していないです。

no matching manifest for linux/arm64/v8 in the manifest list entries · Issue #5142 · docker/for-mac · GitHub

が、issueを見るとMariaDBを使うか、特定のIntelのやつを使えばいけるということで、変更します。

services:

  mysql:
    image: mysql:5.7@sha256:b3b2703de646600b008cbb2de36b70b21e51e7e93a7fca450d2b08151658b2dd

Kibana

arm64のイメージがないので、利用できませんでした。

docker-compose up
WARNING: Found orphan containers (tabechoku_elasticsearch, tabechoku_redis, tabechoku_mysql) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up.
Pulling kibana (kibana:7.10.1)...
7.10.1: Pulling from library/kibana
ERROR: no matching manifest for linux/arm64/v8 in the manifest list entries

Docker Hub

ElasticSearch/Redisに関しては問題なかったので、割愛させていただきます。

結論

正直なんとか開発状態までは漕ぎ着けれるのですが、どっちかというと現場で作業を行う場合に最新のバージョンになっていないことが多いので、そこをどうするかというのが、ポイントになるかと思います。

明らかに対応外なら、割り切って最新のを使うようにするしかないです。

Dockerの場合は、利用できない可能性もあるのでそこも割り切るしかないのかなと思います。

この辺りで本番環境と乖離が起こる可能性が高いので、どこまでを許容とするか、あるいは本番環境を合わせるかになるのかなと思います。

また、今回は先人の知恵があり、なんとかなりました。

  • 解決方法を書いてくれている方
  • M1に対応しようとOSSで対応している方

など、実際に対応されている方には、感謝しかありません。

よく言われていることですが、開発環境、ライブラリは常日頃から最新化しておかないと苦労する を痛感します。

こういった問題に解決するためにも今はチームとして頑張っています。

※この記事を書いている間にもRuby 2.6.6にアップデートし、今はRuby 2.7系にアップデートに取り組んでいます。

弊社ではM1 Macbook Proで開発をするエンジニアを募集しております。

www.wantedly.com

Railsバリデーションメッセージのリソース名を特定するパッチの話

こんにちは。食べチョクの開発をお手伝いしているフリーランスエンジニアのもぎゃ です。
ぼくも記事書いていいそうなので、最近食べチョクの開発をやっている時に発見したRailsテクニックの話を書きます。

バリデーションメッセージのつらみ

Railsでバリデーションをつかって入力をチェックしている場合、@model.errors.full_message をエラーメッセージとして出すことができます。

Active Record バリデーション - Railsガイド

ただ、ぼくらが作ったモデルの日本語名までRailsが知っていてくれるわけではないので、"User Messageを入力してください"みたいな分かりづらい文字列になってしまうことがあって、この場合i18nのリソースを適切に書いてあげることでエラーメッセージを改善することが出来ます。

Rails 国際化 (i18n) API - Railsガイド

ということで、エラーメッセージのカスタマイズについては公式ドキュメントに書いていただいているのですが、現場のRailsのモデルは"User"みたいにシンプルなものではなくて、"Users/address"みたいにnamespaceがついていたり、modelではなくformオブジェクトのバリデーションだったり、accepts_nested_attributes_forを使っていたりすると、「え、これどこに名前を書けばいいの?」となることもしばしば。

この場合、エラーメッセージは「リソースが適切な位置にあれば使われる」というヒントしかないので、うまく行かない場合、「これか?」「ここなのか?」「これもちがうの?」という"当て物ゲーム"を繰り返すしかないのが辛いところです。
「もう良いからif文書いて自前のエラーメッセージにしようぜ」みたいなごまかした実装をするたびに、ControllerがFat化していく...

そもそも、「リソースが適切な位置にあれば使われる」というルールである以上、i18nのライブラリは"正解"のリソースを読みに行っているはずです。そのリソース名を教えてくれたら一発じゃない!?

リソース名を教えてくれるモンキーパッチ

細かい実装はさておき、最終的にはi18n.tを呼び出しているはずです。i18n.tのエイリアス元であるtranslateメソッドの実装はこちら。

  def translate(key = nil, *, throw: false, raise: false, locale: nil, **options) 

i18n/i18n.rb at master · ruby-i18n/i18n · GitHub

この最初のkeyが目的のリソース名です。じゃあ最初からこれを教えてよ。ということでパッチを書きました。

ファイル名は何でも良いので、config/initializerの下におきます。

  module I18nMonkeyPatch
    def translate(key = nil, *, throw: false, raise: false, locale: nil, **options)
      Rails.logger.warn "***************************************************"
      Rails.logger.warn "i18nMonkeyPatch key: #{key}"
      Rails.logger.warn "***************************************************"
      super
    end
  end

  I18n.singleton_class.prepend(I18nMonkeyPatch)

そうすると、i18nの変換が行われるたびにログにリソース名が出力されるので、該当箇所を実行してみればこんな感じでリソース名が分かるようになります

 ***************************************************
 i18nMonkeyPatch key: activerecord.attributes.order_inquiry_thread/lines.message
 ***************************************************

ということで、こういうリソースファイルを作ればよいようです

  ja:
    activerecord:
      attributes:
        'order_inquiry_thread/lines':
          message: "メッセージ"

答えを知ってしまえば、"i18n モデル スラッシュ"とかでググってそれらしい記述を見つけ出せるのですが、そもそもなぜ駄目なのかがわからないと検索キーワードすらわからないので、一発で答えを得られるパッチはそれなりに使い所があるのではないかなと思います。

最後に

食べチョクでは、開発者の方を募集しております。
ぼくみたいにフリーランスの立場から開発に参加してissueをガシガシ消化していけるエンジニアも必要だし、急成長する会社の屋台骨として開発を推進できる社員さんはもっともっと必要そうです。

この記事を見て、「おおそんなやり方が」と思った方、ぜひ一緒にやりましょう!
「その程度かよ」と思った人、野菜の新しい流通経路を作るためにぜひその腕前を貸してください!

www.wantedly.com
www.wantedly.com