エンジニアリングマネージャーでいるのがつらくなったときは
そういうときがよくあります。
マネジメントを仕事にしたのは今の会社が初めてだけど、きっとこれから先も会社に所属している限りは、なくならない気持ちなんだと思います。
だから、そんなときに振り返れるようなものを残しておきます。
...時が経つのは早いもので、コードレビューを受けるのがつらかったわたしも、気がついたらチームリーダーになっていました。
今では、当時自分自身が書いた記事を読んで、レビュアーとして考える日々を送っています。
煽りタイトルで申し訳ないのですが、マネジメントを始めてまだ1ヶ月しか経っていません。
けれど、なんとなく、
- コードを書く時間が減ってしまったことに対する不安
- 自分は何も生産していないのではないか
- 今後のキャリアどうしよう?
- 職場の人間関係に対して常にアンテナを張っていなければいけない気がする不安
- Slackから目が離せない
- あそこで話している人たち、何を話しているのだろう?
こんな感じで、うっかり夜遅くまで残業することになってしまったり、やや寝不足ぎみになってしまったり、なんとなく胃腸の調子を崩したりしています。
それが自分だけならまだしも、どうやら他のリーダーも遅くまで残業したり、どこか顔色が悪そうだったりするのです。
もしかして、こうして悩んでいるのは自分だけではないのかもしれないと思い、約1年ぶりに「つらくなったときは」というタイトルで記事を書くに至りました。
特につらいこと
自分は幸いなことに、そういう機会が巡って来ることは今のところないのですが、周囲の話を聞いているとマネジメントのお仕事で一番つらいのがこちら。ネガティブメンバーのマネジメントだそうです。
現状
自分がチームリーダーになってからのスタンスは、以下の記事に近いです。ちなみにこの翌日のアドベントカレンダーがリアル上司ですw
社内の人に読まれていそうなのですごく書きづらいんですが、特に、なるべく「情報を筒抜けにする」のは、やってみてかなり良い感じです。メンバーとの距離も広がらないですし。
そして自分を取り巻く現状としては、以下の記事が近いです。たぶんお会いしたことのある方の記事です。
いかんせんコードを書くことの成果が見えやすいので比べてしまうのですが、ここで出てくる 「エンジニアの生産性」って、どうやって成果としてはかるものなんだろう? と最近は疑問に思ったりもしています。
よく言われる「プレイヤーとして優秀なエンジニアが昇給していくためには望んでないのにマネジメントにならないといけない」問題の対策として、マネジメント系のキャリアパス以外にもエキスパート系のキャリアパスを定義し、「それぞれのキャリアパスの各グレードってどういう役割が期待されているんだっけ」などと考えて設計しています。
ここは最近、自分も部署内で問題提起をしました。
だって、昇給したくてマネジメ(自主規制
というのはおいといて、わたしはこうあるけれど、チームメンバーにはエキスパート系のキャリアパスを早く見せたいな、と思っています。
悩みは「悩みを言えないこと」
先の記事の「意識低い系」スタンスで、けっこう悩みを言ってしまうことは多いです。でもやっぱりあんまりよくないなあ、と最近は思っていて、チームリーダーだけで話し合う場を設けたり、他の部署との交流を増やすなどして工夫しています。
あとは採用ですね。「一番大事なのは採用」と記事にもあるとおり、マネジメント難易度を下げるための採用、したいです。。
今の自分にはなかった考えがあった
この記事を書くにあたり、例によって「チームリーダー つらい」とか「エンジニアリングマネージャー つらい」とかいうググり方をしたわけですが(笑)、そんな中で新しい発見もいくつかありました。
上記の記事は2ページ目から「退職を伝える前に試してみること」がいくつか上がっているんですが、
- 仕事のやり方をパクる
- チームメンバーの個性を知る
- 準備をする
あたりは自分も意識している一方で 「助けを求める」 という案はなかったな、と思いました。
先述の通りわたし自身にも上司がいるので、あまり抱え込まずに頼ってもいいのかもしれません。
次に、以下の記事です。
この 「マネージャーになると起こる変化」 の中で自分が「おっ」と思ったのが以下です。
- 技術の習得範囲が広がる
- 非エンジニアとのコミュニケーションが増える
マネージャーなのに技術の習得範囲が広がるのはすごく不思議なんですが、マジです。
自分は最近インフラまわりを勉強しているんですが、たまたま自分のチームにインフラが得意なメンバーがいたり、自分と同じようにインフラ勉強したいって思っているメンバーがいるので、一緒に勉強している感じがして楽しいです。まあ、あっという間に自分だけ置いていかれちゃうんでしょうけどw
あと、チームメンバーのインフラの設計レビューに出ることもあるんですが、CTOが直々に出てきてハイレベルなやりとりが見れるのも(傍観者だからというのもあり)ちょっと楽しいです。傍観者だけど、事前に自分も必死で予習しました。笑
非エンジニアとのコミュニケーションが広がるというのは、コミュ力のある方ならマネージャーにならなくともできるかもしれないのですが、規模の大きな企業になってくるとなかなか難しかったりします。
リーダー同士での会話や、リーダーのみが対象になるイベントは、大きな企業の中でも比較的優秀なメンバーが集まってやることが多いので、お互い話してみると普通に面白かったりします。
わたしの理想
個人的には、こういう感じが理想です。タイトルがアレですがw
最初に出てくる、こういうリーダー、ほんとうによく見ます。
リーダーはただのスーパーマン。 要件からバックログのタスクにする。調整して、みんながタスクに集中できる環境をつくってあげられる。コード書いてタスクを燃やせる。なんでもできる。リーダーすごいよ。
なんでもできるのは確かに良いことなんですけど、この記事にあるような「リーダーに甘えていたボンクラコーダー」を生み出すような人はチームビルディングできない人なんだな、と思います。なので、そうならないように意識はしているつもりです。
「プロジェクトの目的のために行動できる人になる」のは、リーダーだけじゃいけない
こういう話は他の記事でもありまして、
上記のfreeeさんの記事ではCTOみずから新卒のメンバーに 「3年間でスモールチームのCTOになってほしい」 と伝えていますし、
上記の記事はエムスリーさんですが、実際にCTOを任せちゃってます。
マネジメントはやりたくなかったわけじゃない
わたし自身、本当に昇給のためにマネジメントを始めたのかというと、今のタイミングだけ見たらイエスなんですが(笑)、そのうち子供ができて、生まれて、子育てして、それを2人か3人くらいやって、本格的に復帰して現場にコミットできるようになった段階では、どのみちもう一度やろうと思っています。今のところ。
以下の記事にもあるんですが、
機械を直接相手にする必要はなくなりますが、チームの価値を最大化するという課題にたいしエンジニアリングすること自体にかわりがない
この通りでして、たしかEM.FMの第1回かな?同じような話を広木さんあたりがされていた記憶があります。
大学時代オーケストラサークルにいたときに、どうやったら効率よくメンバーに動いてもらえるんだろうって思って、マネジメントとか、心理学に関する本を読み漁って、トライアンドエラーを繰り返していた時期がありました。
そういうのもあって、どうやら自分は他人よりもその手のスキルがあるらしい?と思うこともあるのですが、単にエンジニアの中にいるからかもしれません。
ただ、今のタイミングではもう少し技術力を強化したい気持ちもあって、その間で揺れてしまうことも多いです。
エンジニアのためのマネジメントキャリアパスという本の5章でも、以下のようにあります。
いきおい管理者は経営と人的管理に関する職務に忙殺され、仮に技術的な作業に時間を割こうとしても夜間か週末しかない、ということになります。自分の会社がこういう企業なら、「コードの作成やシステムのデザインはもう十分マスターした」と得心するまでは技術力の強化に専念し、納得がいった時点で初めて経営系に舵を切るか否か判断するとよいでしょう。いったんコードを書く作業から遠ざかってしまうと遅れを取り戻すのが大変です。機が熟する前に離れてしまうと、中間管理職より上に昇るのに必要な技術力を十分身につけられずに終わってしまう恐れがあるのです。
いつだってプレイヤーに戻れる
先に紹介したエンジニアのためのマネジメントキャリアパスの3章には、こんな文章があります。
あなたが望めば進路変更も可能だ、ということを忘れてはなりません。ある時点で管理職に就いてはみたが、やはり自分には向いていないと判断し、技術畑に戻る人も少なくありません。どちらの選択肢も決して恒久的なものではないのです。いずれにしても常にしっかり目をあけていること。どちらの道にもメリット、デメリットはあり、どの仕事が一番自分に向いているかを見定めるのはあなた自身なのです。
わたし自身、この文章にすごく救われました。
今日も、他の部署の方と話している中で「戻ったって良いんだよ、そうしたいと思ったら伝えてごらん」と言ってもらうことがありました。ですので、つらくなってしまったときも過度に悲観的にはならないでいたいな、と思います。
また、以下の記事では、えふしんさんが「エンジニアリングマネージャは入れ替わっても良い」と述べています。
そして、ここからが大事なことですが、必ずしもエンジニアリングマネージャは永遠にその立場でいる必要もなく、一定期間で別の人に入れ替わっても良いと考えています。期待するのは大人としてチームの活躍を支えるチームディレクションとしての役割です。ディレクションかプレーヤーかというのは、その都度、役割を入れ替えることはあってもよいと思います。
なによりそうすることで、たくさんのエンジニアが人の成長を支える仕事の難しさを知ることができるし、一度、エンジニアリングマネージャを経験した人は、より広い視野での仕事を期待できるわけなので、改めて現場でコードを書く役割にコミットしながら、開発プロジェクトマネジメントやメンバーの育成においてもエンジニアリングマネジメント力を発揮することが期待できます。
そうこうしてるうちに新しい事業アイディアが出てきた時には、そういう人が率先してエンジニアリングマネージャとしてチームを作れるようになることで組織のスケーラビリティは向上します。
このようにエンジニア組織全体としては、立場を入れ替え可能であることと、マネージャという役割をエンジニアとしてのキャリアの幅や柔軟性を高める役割として定義することで、プログラマ35歳定年説に代表されるような、コードを書かなくなって、エンジニアとしての死を迎えるみたいな不安な世界を終わらせることができるのではないかと考えています。
まとめ
マネジメントを任せてもらえるというのは信頼されている証しで、成長している証しです。そしてきっとマネジメントという仕事を通して、エンジニアとして技術的にも人間的にも、もっと成長できることがあるはずです。いっぽうで、コードにコミットできる今だからこそ手を動かしてスキルを伸ばしたい!と思うこともあります。
まあ、マネジメントはやりたければいつでもできるという話でもないので、もうしばらく、この貴重な機会を味わっているつもりです。
余談
...という日々を過ごしている中で、そういう気持ちをもっともっといろんな人とお話ししたいので、サイゼリヤミートアップをしようと思いconnpassで下書きまで作ってあるんですが、予約できるサイゼリヤが見当たらなくてできないでいます...!
もう普通の居酒屋でやろうかなあ。
サイゼリヤMeetup in Gotanda 〜スペシャリストなの?マネージャーなの?どっちが好きなの?〜
— める@1/27は東関東アンコン (@c5meru) December 11, 2018
を雑にconnpassに登録するぞ。
今週は多忙なので、アンコンが終わったら…😇
夜は予約ダメって言われちゃった、かなしい
— める@1/27は東関東アンコン (@c5meru) December 18, 2018
わたしのオススメ技術書
今日会社で「なんかいい本ないですか?」と聞かれて、オススメ本リストを作りました。
前提としては Rails × Vue をメインに開発している会社なので、他のバックエンド・フロントエンドの情報はない感じです。。
オススメ本って人によって違うので、幅広いジャンルを心がけました。
未読のものもあるのでなんとも言えませんが😅、せっかくなので、ここに残しておこうと思います。
反省としては、Railsに関する本、Vanilla.jsに関する本、データベースに関する本、インフラに関する中級者以上向けの本を知らないんだなあ、わたし。と思いました。
特にDBとインフラは、もうちょっとがんばらねば。
あとから思い出したら黙って追加してるかもです。
Web全般
設計
インフラ
Ruby
デザイン
Vue.jsなど
外部サイトにリダイレクトするときは、オープンリダイレクトに注意しよう
すごく当たり前の話なんですけど、最近うっかりやってしまったので反省文として書きます。
まだリリースしていないサービスの初期実装で、現在のURLをパラメータにとって、ログインしたらリダイレクトで戻ってくる処理を作りました。
サービスにアクセスした時、ログインしていなければ、以下のアドレスにリダイレクトされます。
redirect_uri
パラメータの中身は、最初にアクセスしたURLになります。
https://www.example.com/login?redirect_uri=https://www.example2.com/
ここで、普段おとなしい同僚から、こっそり声をかけられて、危険ですよ、というアドバイスをいただきました。
実際、作っている時もなんか危ないな、という気はしていたのですが、具体的に何かは思い出せなかったのでした。
そう、URLにある redirect_uri
パラメータの中身って、なんにでも変えられちゃうんですよね。以下のように。
https://www.example.com/login?redirect_uri=https://www.cho-warui-site.com/
リダイレクト先をコントロールできてしまい、意図されていないページへのリダイレクトも許してしまいます。
これをオープンリダイレクトといいます。
オープンリダイレクトは、Webアプリケーション自体には問題がないように見えるのですが、たとえば、想定したリダイレクト先のサービスとデザインを似せたページを作って、ログイン情報を再度入力させることができれば、ログイン情報を盗み取れてしまいます。
こうしてフィッシング詐欺などに悪用される恐れがあるのです。
また、リダイレクト先を自由に設定できてしまうということは、HTTPヘッダインジェクションにもつながります。
なので、このようなリダイレクトをするときは
- なるべくURLパラメータを使わず、リダイレクト先URLのホワイトリストを作って、そこから選ぶようにする
- どうしてもURLパラメータを利用しなくてはならない場合
以上のことを心がけるのが大切です。
いやはや、リリース前でほんとうによかったです。
今度、会社で徳丸本の輪読会をするので、きっちり読み返します。
「設計やチームビルドについて相談し合う会」に参加しました
参加したきっかけ
旦那さま(@polidog)や上司が参加するということで、こちらのイベントに参加してみました。 ハッシュタグは#dev_sodanでした。
Presented by 日本Symfonyユーザー会、ということで、自分はたまに社内プロジェクトでSymfonyのコードを読んだりはしますし、結婚するにあたってSymfony2入門を読んで写経したことはありますが、業務での開発はほとんど(Web制作会社でEC-CUBE3くらいしか)したことないんですよね。
しかし、ユーザー会の旦那さまいわく
「今回はSymfonyの話に限らず、表題の通り、設計やチームビルドについて話をするので、来ればいいじゃん」
ということで、参加させていただきました。ありがたや。
会場とか雰囲気
会場は五反田駅から徒歩圏内にあるサイボウズスタートアップスさんでした。
サイボウズスタートアップさん、お寿司美味しい、ありがとうー #dev_sodan pic.twitter.com/t4jyBdRWRq
— sasezaki (@sasezaki) October 20, 2018
形式としては、15人ほどで輪になって座って、せたさん(@kseta19)の司会で、トピックひとつ30分程度を目安にディスカッションしていく感じでした。
最初は緊張しましたが、おそるおそる発言してもあたたかく受け入れていただける雰囲気だったので、とても楽しかったです。
ディスカッションの内容
会社を立ち上げたのだけれど、2人目のエンジニアが取れない
自分より技術力のある人を採りたい。
現在はツイッターのフォロワーを増やす活動をしており、勉強会の主催・会場提供もする予定。
- うちは月に40〜50面接やってて、それで1〜2人くらい採用している
- 2次では技術試験もやっている
- うちはそんなに面接していないから、書類でめっちゃ落としてるのかも
- ある程度ニッチな技術を前面に出して売り出した
- 事業として最終的にどれくらいの数のエンジニアが必要なのか考えてみて
- 最終的にあまりエンジニア組織を拡大しなくていいなら、ニッチを攻めたほうがいい
- 2人目のエンジニアは自分より下の人しか取れないと思ったほうがいい
- 自分たちの技術にふさわしいものはなにか?から考えたほうがいい
- エンジニアの半分はリファラル
- 勉強会に行って、出会ったエンジニアと飲みにいく
- 難しいけど、伸び代のある人をとったほうがいい
- 勉強会はスポンサーするのもアリだと思う
- 自社でもくもく会を開いて、よく来る人を口説くとかよさそう
- 業務委託の優秀な人に紹介してもらうのが良さそう
- エンジニアにとって働きやすい環境を整えて、認知してもらうとか
- うちが会社を立ち上げたときは、最初はたまたま成功報酬の媒体で取れた
- その後は勉強会で会った人を口説いていった
- 自分は経営とエンジニアの橋渡しを最高品質でやるCTOでいようと意識している
チームとプロダクトが増えたので、うまく情報共有ができなくなってきた
最近、急激にエンジニアが増えてきた。
各システムに分かれて開発をしているけれど、PM同士のつながりがなかったりして、連携している部分を共有できず見落としたりすることがある。
技術の知見や、開発手法もうまく共有できていない気がする。
- うちは全部のプロダクトを自分(CTO)が見るようにしている
- すぐに解決するのは難しいので、やっちゃいけないブラックリストを作ると良いかも
- 実際はシステムが疎結合になっていないのに、チームが疎結合がなってしまっているのでは
- うちではオフラインの会話が生まれやすい状況を作っている
- カンバンで、困っていることを話す
- 開発者がスタンドアップでみんな集まってやっている
- 月1で報告会をやるようにしていた
- 毎日6時になると立って、ビジネス側のメンバーも含めて夕会をしている
- 規模としては15人くらい
- また、みんなで社内システムを確認する会をしている
- そこで一番社内システムを知っている人から、歴史的経緯や知らない部分を教えてもらっている
- ドキュメントはメンテナンスコストがかかるからやめようっていう雰囲気になってる
- ペアプロやモブプロをすれば、開発手法は共有できそう
- 自分は、みんなで話すよりも1on1の方が話しやすいかも
- どちらがいいかは、人によるのでは
新人にOOPを効率的に学んでもらう方法について悩んでいる
これまで「OOP(オブジェクト指向プログラミング)ができること」を基準にエンジニアを採用していた。
エンジニアの採用が大変になってきたので、育成枠の採用を始めたいのだけれど、OOPを効率的に学んでもらう方法がないか悩んでいる。
- イケてる(すぐ育つ)ジュニアエンジニアは、何であれ、すぐにできるようになる
- うちは学生アルバイトの時給を高くしているので、イケてるアルバイトが取れていると思う
- そもそもハイレベルな人を取った方がいい
- 採用するときには、自分で勉強してるかどうかをまず聞くようにしている
- なんでOOPが必要なのかを、まず教えないといけないのでは
- うちは技術顧問などの強い人が、ペアプロでしっかりと見てあげている
- 新卒だとまだ真っ白で我流がないので、いい感じに染まってくれると思う
- ペアプロをして、ジュニアとシニアが二人三脚で、ひとつのものを開発するようにしている
- 早い人は3ヶ月くらいでできるようになる
- 新卒なら育てやすいと思うけれど、いい新卒は「すごいエンジニアと働きたい」と言って他社に取られてしまう
- 入社して少し経ってから、こういう本を読んだらいいよってすすめる
- まずは伸びる人を伸ばす、それが成功するとラクになってくる
レビュー地獄から解放されたい
レビュー地獄とは、自分がコードレビューを担当するエンジニアが多すぎるなどの原因により、レビューしなければならないPull Requestが多すぎて、コードレビューだけで1日をほとんど使い果たしてしまい、他の仕事ができなくなってしまうこと。
- 先ほどの「まずは伸びる人を伸ばす」に通ずるけど、自分はレビューできる人を増やして、チームを分割した
- 最近は楽になった
- うちはコードレビューを担当するリーダーはいなくて、レビュイーがコードを見てもらいたい人に依頼する
- うちは、くじびきでレビュアーを決めている
- レビューに時間がかかるのは、その前に設計について相談していないからでは
- うちは機能拡張するときに困らないように設計する、という考えがシェアされている
- 本当に欲しいものは何なのか、長い時間をかけてビジネス側にきくようにしている
- サービスレベルに影響するかどうか、影響するところは設計を含めてきっちり作っていく
- 他のところはスピード感重視でやっていくので、リファクタリングデーを別途設けている
フロントエンドの設計をどうしたらいいのか分からない
- props地獄がつらい
- フロントエンドの設計には3種類ある気がする
- そもそも、捨てるためのフロントエンドを作っていくべき
- どんなテストを書くのが適切なのか
- UIのテストはメンテが大変
- Vue.js入門の後半に設計手法が書かれていてとても良かった
- Atomic design
- デザイナーの傾向として、このようなプログラマブルな考え方を受け入れない人が多いかも?
- Atomic design
- 後悔しないためのVueコンポーネント設計も良かった
まとめ
上記の他にも、自分からは「女性エンジニア(がチームにいることについて)どう思いますか?」というテーマをあげました。
燃えやすい話題なので詳細は省きますが(笑)、参加者のみなさんからいい話がたくさん聞けました。
また、全体的に、せたさんを始めとしたスタッフの皆さんがいい感じにディスカッションを進めてくださったので、非常に有意義で楽しかったです。
エンジニアとしてのキャリアが浅くて、まだまだ手を動かすエンジニアとしてやることがたくさんあると思っている身としては、「設計」と「チームビルド」というのは非常にハードルが高いトピックです。
だって、設計といえばマサカリが絶えず飛び交っていて怖いイメージですし(ひどい偏見)、チームビルドのようなマネジメント的な話は、(マネジメントも問題解決なのに)どうしてもエンジニアリングな話と対立しがちですから。
けれど、自分のフェーズがちょうど
- 動くものは書けるけど、書いていて「この設計ではいけない」と思うことが増えたので設計の勉強をしている
- チーム内のコミュニケーションで先輩っぽい振る舞いをすることが増えてきたので、全体最適を考えている
というのもあったので、ある程度長い経験をお持ちの「CTO」や「マネージャー」、「シニアエンジニア」などの皆さんと「設計」や「チームビルド」について非常に濃いお話をすることができて、ほんとうによかったです。ありがとうございました!
別々のdocker-composeに属するコンテナ同士で疎通させる
もう3ヶ月くらい前になるのですが、Dockerを雰囲気でやっていた自分が、ローカル上でそんなことをすることになりました。
当時の自分がこのタイトルを見ても「ハァ???」みたいな気持ちになったと思います。。。
dockerを理解するために読んだもの、読みたかったもの
最初の時点で、インフラに詳しいメンバーから
「たぶん network
を設定するんだと思いますよー」
と言われていたんですが、とにかく自分はDockerについての知識が浅かったので、まずは docker-compose.yml が読めるようになるまで勉強してみました。
読んだもの
読みたかったもの
いろいろ勉強して解決した後に読んだけど、これを先に読みたかったw
docker-compose のnetworkについて
DockerとComposeの基本的なところを勉強したら、インフラに詳しいメンバーから教えてもらった network
について調べつつ、疎通させる方法も一緒に調べていきました。
以下のドキュメント・記事を参考にさせていただきました。
Compose のネットワーク機能 — Docker-docs-ja 17.06.Beta ドキュメント
疎通の方法について
ということで、上記の記事と同じような話にはなりますが、別々のdocker-composeに属するコンテナ同士で疎通させる方法についてです。
それぞれ docker-compose がdefaultで持っているnetworkに加えて、疎通するための新しいdocker networkを作成します。
docker network create common_network
と、自分だけが開発しているものであればこれでいいのですが、開発しているRailsアプリケーションは、docker-composeコマンドを含めた一連の立ち上げコマンドをMakefileでまとめています。
なので、コンテナとRailsサーバーを起動するmakeコマンドの中に、上記のnetwork作成コマンドを入れておきました。
次に、docker-compose.ymlにnetworkまわりの設定を追加します。
今回は両方のdocker-compose.ymlのservice nameが全く同じ構成だったので、aliasを追加しました。
ひとつめのdocker-compose.yml
version: '3' services: db: image: mysql:5.7 ports: - "3306" environment: MYSQL_ROOT_PASSWORD: "hogehoge" volumes: - db:/var/lib/mysql web: build: context: . dockerfile: docker/web/Dockerfile command: bundle exec rails server --port=3000 --binding='0.0.0.0' volumes: - .:/first_web - bundle:/usr/local/bundle - ./node_modules:/second_web/node_modules:delegated ports: - "3000" depends_on: - db tty: true stdin_open: true networks: default: common_network: aliases: - first_web volumes: bundle: db: networks: common_network: external: true
ふたつめのdocker-compose.yml
version: '3' services: db: image: mysql:5.7 ports: - "3306" environment: MYSQL_ROOT_PASSWORD: "hogehoge" volumes: - db:/var/lib/mysql:delegated web: build: context: . dockerfile: docker/web/Dockerfile command: bundle exec rails server --port=3000 --binding='0.0.0.0' volumes: - .:/second_web - bundle:/usr/local/bundle - ./node_modules:/second_web/node_modules:delegated ports: - "3000" depends_on: - db tty: true stdin_open: true networks: default: common_network: aliases: - second_web volumes: bundle: db: networks: common_network: external: true
RailsのAction CableとWebpackerとVue.jsを使ってチャットを作成してみる
もう2ヶ月くらい前になりますが、Action Cable
を用いたチャットの実装にチャレンジする機会がありました。
このときには、Railsの中でVue.jsをどう扱うのかについての一例を見ることもできたので、記念(?)にざっくりしたコードを残しておきます。
1. rails newと、webpackerでVueの初期化
rails new
したあと、Gemfileに gem "webpacker"
を追記し、bundle install
した後、以下のコマンドを実行します。
$ bundle exec rails webpacker:install $ bundle exec rails webpacker:install:vue
2. モデルを作る
$ bundle exec rails db:create $ bundle exec rails g model message content:text user_id:integer $ bundle exec rails g model user name:string email:string $ bundle exec rails db:migrate
class User < ApplicationRecord has_many :messages, dependent: :restrict_with_error end
class Message < ApplicationRecord belongs_to :user validates :content, presence: true after_create_commit { MessageBroadcastJob.perform_later self } end
3. コントローラの作成
class HomeController < ApplicationController def index @messages = Message.all end end
4. Actioncableのインストール
$ yarn add actioncable
5. Viewを作る(Vueコンポーネントの実装)
app/javascript/packs/application.js
import Vue from 'vue/dist/vue.esm' import UserChat from '../components/user-chat.vue' import ActionCable from 'actioncable'; const cable = ActionCable.createConsumer('ws:localhost:3000/cable'); Vue.prototype.$cable = cable; document.addEventListener('DOMContentLoaded', () => { const app = new Vue({ el: '#main-container', data: {}, components: { UserChat } }) })
app/javascript/packs/components/user-chat.vue
<template> <div> <div> <div v-for="item in messages" :key="item.message.id"> <div> <div>{{ item.message.content }}</div> </div> </div> </div> <div> <div> <input type="text" v-model="message" placeholder="入力してください ..."> <span> <button type="button" v-if="userMessageChannel" @click="speak">Send</button> </span> </div> </div> </div> </template> <script> export default { data() { return { message: "", messages: [], userMessageChannel: null, }; }, props: ['userId'], created() { this.userMessageChannel = this.$cable.subscriptions.create( "UserMessageChannel", { received: (data) => { this.messages.push(data) this.message = '' }, }) }, methods: { speak() { this.userMessageChannel.perform('speak', { message: this.message, user_id: this.userId, }); }, // 以下、今回のViewでは使っていませんが、LINEのように自分からのメッセージと他ユーザーからのメッセージでスタイルを分けるときなどに使います messageClass (user_id) { return { "right": user_id === Number(this.userId) } }, dataClass (user_id) { return { "float-right": user_id === Number(this.userId) } } }, }; </script>
app/views/layouts/application.html.erb
<!DOCTYPE html> <html> <head> <title>VueActioncable</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all' %> <%= javascript_pack_tag 'application' %> </head> <body> <div id="main-container"> <%= yield %> </div> </body> </html>
app/views/home/index.html.erb
<% @messages.each do |message| %> <div> <%= message.content %> </div> <% end %> <!-- current_userはdevise gemを入れるなどでログイン実装することによって使えます --> <user-chat user-id="current_user.id"></user-chat>
6. チャンネルを作る
$ bundle exec rails g channel user_message speak
class UserMessageChannel < ApplicationCable::Channel def subscribed stream_from "user_message_channel" end def unsubscribed # Any cleanup needed when channel is unsubscribed end def speak(data) Message.create!( user_id: data['user_id'], content: data['message'] ) end end
7. Jobの作成
$ bundle exec rails g job MessageBroadcast
class MessageBroadcastJob < ApplicationJob queue_as :default def perform(message) ActionCable.server.broadcast "user_message_channel", message: message end end
8. ルーティングを追加する
Rails.application.routes.draw do root to: 'home#index' mount ActionCable.server => '/cable' end
以上です。
参考にさせていただいた情報
Railsで、Action Mailerとletter_opener_webを初めて使いました
Action Mailer、すごくベーシックな実装なはずなのに、なんか、今まで縁がなかったんです。
Railsチュートリアルでやったのかな? そのへんも記憶にない...
ということで今回は、ローカル環境でletter_opener_web
を使ってメールを確認するところまで、やっていきます。
1. Gemfile
にletter_opener_web
を追加する
group :development do gem 'letter_opener_web', '~> 1.0' end
追加したら、コマンドラインでbundle install
します。
$ bundle install
メール確認用画面を、config/routes.rb
に追加します。
Rails.application.routes.draw do root to: "home#index" # 省略... mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development? end
2. config/environments/development.rb
に設定を追加
Rails.application.configure do # 省略... config.action_mailer.perform_caching = false config.action_mailer.default_url_options = { host: 'localhost:3000' } config.action_mailer.delivery_method = :letter_opener_web # 省略... end
production.rbにはメールの設定なんかを書いたりしますが、今回は割愛。
3. Mailer
を作成する
コマンドラインで生成します。
$ rails g mailer UserMailer
生成したメーラーを、必要に応じて調整します。
app/mailers/user_mailer.rb
class UserMailer < ActionMailer::Base default from: "hoge@gmail.com" def send_message_to_user(user) @user = user mail to: @user.email, subject: "メールの件名が入ります" end end
4. メール本文を作成する
app/views/user_mailer/send_message_to_user.text.erb
を作成します。
メソッド名.text.erb
という名前になるので注意しましょう。
ファイルの内容は以下のような感じです。
<%= @user.name %> さま いつもお世話になっております。 株式会社●●です。 この度は、キャンペーンにご応募いただきまして、ありがとうございました。 当選発表は、商品の発送をもってかえさせていただきます。 株式会社●●
5. メール送信処理
メール送信をしたいタイミングで、Mailerのメソッドを実行します。
class EntryController < ApplicationController def create @entry = Entry.new(entry_params) if @entry.save # ここでメールを送信する UserMailer.send_message_to_user(@entry.user).deliver_now redirect_to root_url else render 'new' end end end
6. メールが送られたのか確認する
最初に config/routes.rb
に追加した、メール確認用画面を開きます。
http://localhost:3000/letter_opener
を開くと、以下のようが画面が開き、送信処理があったメールを一覧できます。(実際に送信されているわけではありません)
レバテックキャリアさんに、前職在籍時に書いた記事を取り上げていただいた
レバテックキャリアさんの記事はこちらです。 また、取り上げていただいた記事そのものはこちらです。
2017年2月21日、ということで、ちょうど1年と5ヶ月前の記事になりますが、取り上げていただけるなんて、ありがたい話ですね。
Web制作会社と自社サービス会社の違いについて、少しでも知る手がかりになれば幸いです。
さて、偶然なことに、まさに今も「転職して4ヶ月くらい経った」タイミングだったりします。
今回は「自社サービス開発」どうしの転職ではあるのですが、「入社当時 社員5名未満の超スタートアップ」から「上場後で社員300名超のミドルベンチャー」の転職です(ただし某ブラックホールではありません、フロア違いです 笑)。
エンジニアの規模としては、3人から11人(インターン・アルバイト含む)に変わりました。
ですので、ついでに、今回の転職で変わったことを書いてみようと思います。
以前やっていたこと
上記の記事から抜粋。
- CSS芸人
- CoffeeScript(からES6にリプレース)
- Ruby on Rails実装(複雑なところをのぞく)
- UI改善ディレクション
現在やっていること
- CSSレビュアー
- Vue.js実装・レビュアー
- Ruby on Rails実装(初期実装ほぼ全体)
- インフラのお勉強
変わったこと
技術に集中できるようになった
現在の会社では「ディレクター」や「デザイナー」がいらっしゃるので、サービスの方針とか、デザインの方針とかを考えるのはお任せする感じになりました。もちろん、発言権がないわけではなく、意見があれば都度相談することもあります。
ですので以前よりは、技術のことを考える時間が増えました。あとは、コードレビューでレビュアーになることもグッと増えました。
プロジェクト内での役割について考えるようになった
以前はほとんど1人でフロントエンドもサーバーサイドも実装し、軽く上長に相談する程度で開発を進めていたのですが、上記に伴い、いろんな役割の人がいるので、たとえば
「これはディレクターにも相談するべきなんだっけ」
とか、
「ここはデザイナーに調整してもらった方がいいんだっけ」
とか、考えるようになりました。
なので、良くも悪くも「テキトーに、いい感じにやっとけばいいや」っていうふうに進められなくなったな、と思います。
専門家に相談できる強さがある
これも上記に関連するのですが、現在は、フロントエンドも、サーバーサイドも、インフラも、それぞれ専門家がいるので、相談相手に困らなくなりました。
なので、現在自分が関わっているプロジェクトには、ディレクターやデザイナー含め、たくさんの専門家がメンバーをサポートしてくださっています。ありがたい話です。
まとめ
やはり人数がいる強さってすごいな、に尽きます。
ただ、自分は現在ゼネラリストとしてやっているところがあるので、これ以上大きい組織になった時に、専門家でない自分が果たして役に立てるのかな...?なんて迷ったりすることもあります。
インフラお勉強中なので、ひと通り慣れてきたら、自分の道を決めるタイミングになるのかもしれません。
とはいえそれでも優秀なエンジニアさんは、自分の専門外の技術もある程度キャッチアップしていたりするので、そういうところを見習いながらやっていきたいな、と思っています。
PercelでVueを動かしてみた
職場で話題になったので動かしてみました。すごく簡単でした。
調べてみると、流行った頃の日本語の記事がいろいろ記事が出てきたんですが、Percel側もあれから開発が進んで、ますます簡単になっていたようです。
こんな感じなので、日本語の記事を参考にするより、公式ドキュメントを見た方がはるかに簡単でしたw
ということで、早速やってみます。
まずは、プロジェクトルートのディレクトリを作ります。
今後はその中で作業していきます。
公式ドキュメントに書いてある通り、まずは Vue
と parcel-bundler
をインストールします。
$ npm install --save vue $ npm install --save-dev parcel-bundler
そして、 package.json
に script
を追加します。
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "parcel index.html" },
初期設定はこれだけ。
あとは、プロジェクトルートにビルドしたいファイルを追加していきます。
index.html
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <title>parcel-vue</title> </head> <body> <div id="app"></div> <script src="app.js"></script> </body> </html>
app.js
import Vue from 'vue/dist/vue.esm.js'; import Hello from './vue/hello.vue' const app = new Vue(Hello); app.$mount('#app');
vue/hello.vue
<template> <p> {{ message }} </p> </template> <script> module.exports = { data: function() { return { message: 'Hello, Percel-Vue!!', } } } </script> <style scoped> p { font-size: 64px; text-align: center; color: #3eaf7c; } </style>
ビルドしたいファイルを追加したら、先ほど package.json
に追加した script
で動かしてみましょう。
$ npm start index.html
Server running at http://localhost:1234
の文字が出たら、 http://localhost:1234
から確認できます。
Webpackのconfigには自分も悩まされてきたので、Percelの今後に期待です。
RailsのModelで定義するlengthのvalidationについて、activemodelのコードを見てみる
今日はさっぱりめ。
Railsでmodelを書くときって、こんな感じに書くと思うんですが、
class User < ApplicationRecord validates :name, presence: true, length: { maximum: 30 } validates :email, presence: true end
昨日お仕事で length
のカスタムバリデーションを書きたくなり、このデフォルトの length
の実装ってどうなってるんだろう? と思ったので、調べてみました。
該当箇所はこちら。
7/7 10時現在のソースは以下。
# frozen_string_literal: true module ActiveModel module Validations class LengthValidator < EachValidator # :nodoc: MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :too_short, :too_long] def initialize(options) if range = (options.delete(:in) || options.delete(:within)) raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range) options[:minimum], options[:maximum] = range.min, range.max end if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil? options[:minimum] = 1 end super end def check_validity! keys = CHECKS.keys & options.keys if keys.empty? raise ArgumentError, "Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option." end keys.each do |key| value = options[key] unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY || value.is_a?(Symbol) || value.is_a?(Proc) raise ArgumentError, ":#{key} must be a nonnegative Integer, Infinity, Symbol, or Proc" end end end def validate_each(record, attribute, value) value_length = value.respond_to?(:length) ? value.length : value.to_s.length errors_options = options.except(*RESERVED_OPTIONS) CHECKS.each do |key, validity_check| next unless check_value = options[key] if !value.nil? || skip_nil_check?(key) case check_value when Proc check_value = check_value.call(record) when Symbol check_value = record.send(check_value) end next if value_length.send(validity_check, check_value) end errors_options[:count] = check_value default_message = options[MESSAGES[key]] errors_options[:message] ||= default_message if default_message record.errors.add(attribute, MESSAGES[key], errors_options) end end private def skip_nil_check?(key) key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil? end end module HelperMethods # Validates that the specified attributes match the length restrictions # supplied. Only one constraint option can be used at a time apart from # +:minimum+ and +:maximum+ that can be combined together: # # class Person < ActiveRecord::Base # validates_length_of :first_name, maximum: 30 # validates_length_of :last_name, maximum: 30, message: "less than 30 if you don't mind" # validates_length_of :fax, in: 7..32, allow_nil: true # validates_length_of :phone, in: 7..32, allow_blank: true # validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name' # validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters' # validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me." # validates_length_of :words_in_essay, minimum: 100, too_short: 'Your essay must be at least 100 words.' # # private # # def words_in_essay # essay.scan(/\w+/) # end # end # # Constraint options: # # * <tt>:minimum</tt> - The minimum size of the attribute. # * <tt>:maximum</tt> - The maximum size of the attribute. Allows +nil+ by # default if not used with +:minimum+. # * <tt>:is</tt> - The exact size of the attribute. # * <tt>:within</tt> - A range specifying the minimum and maximum size of # the attribute. # * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>. # # Other options: # # * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation. # * <tt>:allow_blank</tt> - Attribute may be blank; skip validation. # * <tt>:too_long</tt> - The error message if the attribute goes over the # maximum (default is: "is too long (maximum is %{count} characters)"). # * <tt>:too_short</tt> - The error message if the attribute goes under the # minimum (default is: "is too short (minimum is %{count} characters)"). # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> # method and the attribute is the wrong size (default is: "is the wrong # length (should be %{count} characters)"). # * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>, # <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate # <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message. # # There is also a list of default options supported by every validator: # +:if+, +:unless+, +:on+ and +:strict+. # See <tt>ActiveModel::Validations#validates</tt> for more information def validates_length_of(*attr_names) validates_with LengthValidator, _merge_attributes(attr_names) end alias_method :validates_size_of, :validates_length_of end end end
パッと見「ハテ?」という気持ちにもなりますが(笑)、自分たちが普段Modelで定義しているのは、最初の方に書いてある以下の部分ですね。
MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
この MESSAGES
と CHECKS
が使われているところから読んでみたらわかりやすかったです。
あとは、 validate_each
メソッドの中にある、以下の部分でデータの length
を出しているんだな、とか。
value_length = value.respond_to?(:length) ? value.length : value.to_s.length
そしてこの後に続いて、値のチェックをしたり、エラーを出したりしてるんだなー、とか。
CHECKS.each do |key, validity_check| next unless check_value = options[key] if !value.nil? || skip_nil_check?(key) case check_value when Proc check_value = check_value.call(record) when Symbol check_value = record.send(check_value) end next if value_length.send(validity_check, check_value) end errors_options[:count] = check_value default_message = options[MESSAGES[key]] errors_options[:message] ||= default_message if default_message record.errors.add(attribute, MESSAGES[key], errors_options) end
RailsにおけるModelのvalidationは日々当たり前に使っている部分ですが、より身近なものに感じることができました。