for Startups Tech blog

このブログのデザインを刷新しました。(2023/12/26)

アジャイルチームのコアバリュー創りを紹介!あなたの「当たり前」は誰かにとっては「有り難きもの」

こんにちは、フォースタートアップス株式会社のエンジニアの八巻(@hachimaki37)です。主にタレントエージェンシー支援システム(SFA/CRM)のシステム開発を担当し、フルスタックに開発を行なっております。

ここ最近は、エンジニアリング以外にもスクラムや開発生産性、チームビルディングといったキーワードに興味があり、試行錯誤しながらさまざまな施策を練って進めております。今回は、所属チーム(以下、チーム)のエンジニアが中心となり、チームのコアバリューを創りました。 チームのデザイナー @Minmi303 さんに作成頂きました!

背景をはじめ、なぜコアバリューなのか、どんな課題があり、どのように計画してコアバリュー創りを進めていったのかなどについて紹介していきたいと思います。

本題に入る前に少しインスピレーションを掻き立てていきます。以下、3つの画像が登場します。あなたは、これら画像を見て何を思いどのようなことを感じますか。

お付き合い頂きありがとうございます。まず最初にお伝えしたいことは、これら画像を見て一人ひとりの感じ方は違うかもしれません。その所感そのものは、一人ひとりが持つ価値観であり、十人十色で素敵なものだと考えています。

一方で、多様な価値観があることも事実です。

中国の一部の地域では、食べ残すことで「満腹・満足」を表現すると言われております。綺麗に残さず食べるのではなく、一口分程度残すことで「食べきれないほど十分に料理を提供して貰い、満足だった」という気持ちを表します。

英国では、日本円でいう1万円札のような額が大きいお札を出すと怪訝な顔をされることがあります。場合によっては、断られることもあります。

サハラ砂漠のような乾燥した地域では、雨がふれば恵みの雨。つまり、「いい天気」を指します。

極端な例かもしれませんが、我々は多種多様な価値観と共にプロダクト開発を行う必要があると考えております。

お待たせしました。それでは、本題に入ります。

目次

チーム

  • PdM: 1名
  • Engineer: 3名
  • SRE: 1名
  • Designer: 1名

計6名のチームです。小規模なチームのため、PdMが開発Issueを取ったり、エンジニアがプロジェクトをリードしたり、SREがフロントエンドのIssueを取ったりと、ポジションはあれど横断的に開発を進めています。

背景

価値観の向き先が一人ひとり異なる中、チームは力を合わせてプロダクトやプロジェクト、ビジョンに向かって突き進まなければなりません。なかなか酷じゃありませんか?

なぜなら、

  • 人によって善意、良し悪しが異なる
  • 人によって優先順位やベクトル、志が異なる
  • 尚且つ、培ってきた経験が異なる

そんな十人十色の価値観の状況下で、チームはプロダクト開発をしていかなければなりません。だいたいあるのは、全社のMission Vision Valueです。少なからず弊社はそうでした。

プロダクト開発のチームに属する者として、どんな志を持ち、どのようなふるまいを行い、日々どのような意思決定をしていけば、目指す先へ少しでも近づくことができるのだろうか。そんなことを考えながら個人ではなく、チームとしての「明確でぶれない、共通認識の価値観」を創り、皆で同じ方向に向かっていくことが必要なのではないかと考え、推進して参りました。

コアバリューとは

コアバリューと言えば、Zapposではないでしょうか。Zapposは、カスタマーサービス神対応が大きな話題を生み、他社が真似出来ないような独自の企業文化を築いていることでも良く知られています。その独自の企業文化の軸となっているのがコアバリューです。 https://www.shikumikeiei.com/zappos-shikumi1/ https://hrnote.jp/contents/b-contents-editorial-zapposkai-180227/

では、コアバリューとは一旦どういった意味を持つのでしょうか。少し調べてみました。

  • 「中核となる価値観」。人が日々生きるうえで、判断を下したり、優先順位を定めたりするうえでその「ものさし」になるものを指します。
  • 企業あるいは個人が重要視する価値観のこと。個人や組織がものごとを判断するときの「ものさし」ともいえます。
  • 企業が最も重要であると考える価値観であり、日々、社員が考え、行動するうえでの指標として定めるものです。

https://www.corevalue.or.jp/core-values https://www.kaonavi.jp/dictionary/core-value/ https://www.dyna-search.com/jp/core-values

「価値観」と「ものさし」がキーワードになりそうです。つまりは、ものごとの「価値基準」ではないかと私は解釈しました。

どんな課題がチームにあったのか

全社のValueがチームの価値基準になることがベストだとは思います。しかしながら、プロダクト開発との結び付きが難しいというのが現状でした。実際にあった一例を紹介します。

エラーハンドリングにて

クリティカルな問題に成り難い箇所を、事前にどこまで考慮しておくべきかといった議論で意見が分かれました。

  1. 必要になったら考え、実装する
  2. 想定できることを事前に考え、実装する

結論、着地は1でした。確か最終的なジャッジは、多数決だったような気がします。方法論の良否ではなく、どちらを選択するべきかを、どんな価値基準で議論されたかが重要だと考えます。

採用活動にて

エンジニアの一次面接(技術面中心)にて、以下特徴を持ったAさんとBさんが一次面接を通過。二次面接では、チームへのフィット感を見極め評価して欲しいといった要望を受けました。採用枠は1枠です。

▼Aさんの特徴

変化への適応とスキルアップが重要です! なんせこの業界は技術変化が多いので、変化に適応してスキルアップしていかなければいけません。そのためには、スキルアップのみならず、やり切る力を重要視しています。その結果、チーム活性化に繋がりますし全てを還元できるわけではないですが、技術面で貢献できるのではないかと考えます。

▼Bさんの特徴

貢献をはじめ、チームでより良いサービス価値を創出していくことが重要です! 変化多様なご時世では、サービス価値をいかに早くユーザーへお届けできるかが重要です。サービス価値を上げていくために必要なことを考え、自分自身が変化へ適応しスキルアップしていくことが重要だと考えます。

あなたはどんな価値基準で、どちらの方を選択し、どちらの方と一緒に働きたいですか。

なぜコアバリューがチームに必要なのか

上記ケースのみならず、日々決断と意思決定が必要です。

プロダクト開発を行う中で、議論や意見を交わすことは非常にポジティブだと考え、むしろ逆に議論がなかったり、トップダウンで意思決定されていく方がネガティブだと考えております。

「共通認識の価値基準」がない場合、これらケースは個人の価値基準 vs 個人の価値基準のぶつかり合いになります。一方が他方を理解すれば良いという問題でもありません。価値基準が自身の思想になるため、どちらかが納得する or どちらかが折れる or 折衷案の着地になることが多いと考えます。

実現したい何か(目的)がチームにあるならば、もっと「チームの中核となる価値基準」に沿って議論した方が、その何かの実現可能性は高くなっていくのではないでしょうか。

つまり、これら課題を解消する「チームの中核となる価値基準」こそがコアバリューです。個人の価値基準からチームの価値基準に置き換えることで、決断や意思決定、行動する際の価値基準を作ることができると考えています。

コアバリュー創りの進め方

どう計画し、どのように進めたのかについて紹介します。

まず重要だと考えたことは、トップダウンをはじめとする特定他者の意見、意思が詰まったコアバリューにしないことです。コアバリュー創りに対する一人ひとりの興味関心や当事者意識、チームで生み出す過程をどう設計するかなど、非常に悩み考えました。検討結果としては、ワークショップを中心に行い、共通言語としてすでに存在している「プロダクトビジョン(業務プロセスを、シンプルかつ安全に進められる仕組みを提供します)」を活用することを軸に、計画を進めていきました。

※キャプチャーを添付しておりますが、あくまでイメージを掴むために添付しております。

キックオフ

推進していく上で、実施の意義と動機付けが非常に重要です。この土台をしっかり構築することで、軌道修正を容易に可能とします。そのため、概要についての説明機会をはじめ、チームが持つ疑問の解消に努め慎重に事を進めました。

  • テーマ:
    • 主観からカルチャーへ。我々チームのコアバリューはこれだ!
  • When
    • 週一回1時間 × 4回(結果、10回に渡りました..)
  • Where
    • オフライン
  • Who
    • PdM, エンジニア中心
  • What
    • チームのコアバリューを定義する
  • Why
    • チームの価値最大化のため。プロダクトビジョン実現に向けて、チームの良し悪しおよび一体感、決断や意思決定における共通認識の価値基準を創り、皆で同じ方角を指せるようにする
  • How

第一回 :個人が考える価値観に向き合う会

バリューズカードを活用しながら価値観に向き合いました。

  1. あくまで個人が考える価値観についてです。具体的には、「1エンジニアとして、個人が大事にしている価値観」に向き合いました。

  2. あくまで個人が考える理想的なチーム像に対する価値観についてです。ここでは、「1エンジニアとして、どういうチームが理想的かという価値観」に向き合いました。

最終的に1と2を照らし合わせながら、「チームにとって必要な価値観」を考える個人ワークを行いました。

第二回:プロダクトの価値観に向き合う会

第一回は、あくまで個人としての価値観を考えました。少しずつ個人の価値観からの脱却を目指します。ここでも、バリューズカードを活用しながら「プロダクト自身に成り切って、プロダクト自身に必要な価値観」に向き合いました。

最終的に3枚のカードを残し、なぜそう思うのかといった具体理由を記入する個人ワークを行いました。

第三回:コアバリューの原型創り

どのような価値基準を持つことが出来れば、プロダクトビジョンを実現できるのか。プロダクトビジョン実現を軸に、第一回と第二回の価値感をマージするグループワークです。ここでの重要観点は、自分達の「想い」です。私達は人間であり、感情が入り組むチームです。自分達がやっていて「楽しいのか?ワクワクできるのか?やりたいと思えるのか?」を自問自答することが重要だと考えました。

重要な価値基準をチームに創るため、第二回で行った複数の抽象的概念をより具体的にチームで議論し、類似の概念をグルーピングしました。最終的にチームの想いが集結した3つのコアバリューの原型が創られました。

第四回〜第七回:グルーピングの抽象化と命名

第三回で、コアバリューの原型を創りました。私達は、そのグルーピングした一つひとつの概念で「何を一番に伝えたいのか」。概念をもっと抽象的に捉え、一つひとつの命名を決めるグループワークです。ここで重要なことは、参加者全員で議論し決めることです。また、プロダクトビジョン実現という観点も忘れてはいけません(私は忘れかけました)。

※第5回目で、コアバリューの立ち位置はどこか?といった質問があり、「全社のMission Vision Value > プロダクトビジョン > コアバリュー」と定義しました。

チームで一番悩み抜いたことは、グルーピングの抽象化だと思います。価値基準の違いを感じながら、一つひとつの意味を考えていくことは非常に大変でした。抽象的な概念をできる限り具体的に考え、チームの共通概念を探り見つけていきました。

第八回〜第十回:コアバリューを定義する

最後のグループワークです。一つひとつのコアバリューの意図を伝えるため、注釈を考えました。今までの議論を踏まえ、チームには事前に注釈を考えて頂きスタートしました。

ここでもさまざまな議論が繰り広げられましたが、無事にチームの意思が詰まったコアバリューが完成しました🎉

最終的にmiroは何がなんだかわからなくなりましたが、チームで歩んできた軌跡です。

やってみての感想

当初の想定よりも遥かに時間を要しました。時間はかかりましたが、純粋にチームメンバーの考える価値基準に触れることができ、新たな考えを得ることができたと思っております。

自分自身の価値基準、信念を持つことは非常に重要だと考えています。まずは自分自身の価値基準に気づくこと、多種多様な価値基準があることを知ること、そして身近なチームメンバーの価値基準を受け入れていくこと、正解不正解、甲乙ではなく、そんなチームの日々の行動の積み重ねが、良いプロダクトを創るチームになっていくのではないでしょうか。

現場の声は実際どうなのか

コアバリューを創ってから約半年間が経過します。最後にコアバリューに関する意見をチームメンバーに伺いました。生の声を紹介します。

肌感ですが、コアバリューを体現できる限界数は3〜5つぐらいと考えており、多からず少なからずを計画当初に思っておりました。ポジティブな意見もあれば、私を含めまだまだ行動とのギャップには課題がありそうです。

コアバリューがチームの価値基準になり、決断や意思決定、自らが判断し行動ができるようになれば、もっとイキイキとしたプロダクト開発ができるのではないでしょうか。

次期アクションに向かってチームで推進していきます。これからがスタートです。

あとがき

最後に採用情報です。

こんな価値基準を持ったチームで一緒に成長していきませんか。ご興味ありましたらぜひ一度カジュアルにお話できたらと思います。

採用ページはこちら。(冒頭のTwitterにDM頂いてもOKです!)

認定スクラムマスター(CSM)研修に行ってきました!

初めまして!フォースタートアップス株式会社でエンジニアをしている平野と申します。STARTUP DBというプロダクトの開発を担当しています。

今回は2泊3日で株式会社アトラクタさんの認定スクラムマスター研修に参加させていただきましたので、その話を書きたいと思います。

イントロダクション

僕達のチームでは開発手法にスクラムを採用しています。しかし、スクラムで開発をしていくなかで「僕たちが行っているスクラムって本当にこれでいいんだっけ?」と違和感を覚えました。そこで、スクラムの本を読むなどしてスクラムの勉強を始めましたが、僕の中の違和感は消えませんでした。そんな中、認定スクラムマスター研修を見つけました。エンジニアリングマネージャーに相談したところ、会社での研修扱いにして参加してよいとのことだったので思い切って参加してきました。

スクラムとは

複雑な問題に対応する適応型のソリューションを通じて、人々、チーム、組織が価値を生み出すための軽量級フレームワークである。

スクラムガイドより引用:https://scrumguides.org/docs/scrumguide/v2020/2020-Scrum-Guide-Japanese.pdf

ロールや作成物、イベント等が定められていますが、あくまでフレームワークなので、中身はチーム自身で決めていくことになります。

スクラムマスターとは

スクラムマスターは、スクラムガイドで定義されたスクラムを確⽴させることの結果に責任を持つ。スクラムマスターは、スクラムチームと組織において、スクラムの理論とプラクティスを全員に理解してもらえるよう⽀援することで、その責任を果たす。

スクラムマスターは、スクラムチームの有効性に責任を持つ。スクラムマスターは、スクラムチームがスクラムフレームワーク内でプラクティスを改善できるようにすることで、その責任を果たす。

クラムマスターは、スクラムチームと、より⼤きな組織に奉仕する真のリーダーである。

スクラムマスターは、さまざまな形でスクラムチームを⽀援する。

スクラムガイドより引用:https://scrumguides.org/docs/scrumguide/v2020/2020-Scrum-Guide-Japanese.pdf

スクラムマスターはスクラムを確立し自分のチームと組織にスクラムの理論を伝え、 チームが自己組織化するように導いていく存在です。

認定スクラムマスターとは

認定スクラムマスター(CSM / Certified ScrumMaster)とはScrum Alliance®が提供する認定資格です。認定スクラムマスター資格取得の試験を受ける為には、計16時間の認定スクラムマスター研修が必須となっています。

研修の流れ

僕が参加した研修は2泊3日の合宿形式でした。僕はオフライン形式の研修を選択しましたが、オンライン形式の研修もあるようです。6名で1チームを作り、3日間そのチームで行動します。

研修は座学とワークショップで行われ、全日、両方の形式を行いますが、

  • 1日目: 座学中心
  • 2日目: ワークショップ中心
  • 3日目: 座学中心

で行われました。

16時間の研修後、講師の方が、認定資格試験受験可能な方をScrum Alliance®に登録することで試験を受ける権利を得ます。試験は後日自分のタイミングで試験を受けることになります。

研修に参加するメリット 〜オフラインの場合〜

今回の研修に参加して2つのメリットを感じました。

  1. いろいろな立場・知識の人たちと話せる
  2. 講師陣の方とたくさん話すことができる
1. いろいろな立場・知識の人たちと話せる

この研修に参加する方達の立場・知識はさまざまです。

  • すでにスクラムマスターとして働いている方
  • これからスクラムマスターになろうとしている方
  • ほぼ知識がなく、新たにアジャイル開発を導入する方

この「いろいろな立場・知識の方がオフラインで一箇所に集まる」というのが1つ目のメリットです。なぜなら、朝食や昼食、夕食の時間など研修の時間以外に他の参加者と話す機会があるからです。

参加者は全員がチームに関して何かしらの悩みを抱えているようです。その悩みを共有し対応策を話たり、研修ではこう言っていたからチームに帰ったらこれやってみようかな、みたいな話ができます。すでにスクラムマスターとして働いている方からはスクラムの知識を得たり、リアルな現場の悩みを聞けます。スクラムの知識がない方からはスクラムに関していろいろな角度の質問が飛んできます。このようにいろいろな立場の方と話せることで自分に足りないスクラムの知識を認識できました。

2. 講師陣の方とたくさん話すことができる

ワークショップや座学の時間に講師と話すことができます。それに加えて、講師の方も朝食や昼食、夕食を一緒に食べますし、夕食後の歓談の時も同じテーブルで研修に参加している方との話に混じってくれます。この、講師の方と近い距離で話すことができる、話す時間がたくさん取れるのが2つ目のメリットです。「こういう時はどうしたら良いですか?」「うちのチームは今こんな状況で…」などの相談を直接講師の方にすることができます。講師の方はアジャイル開発やスクラムに関してかなりの知識と経験を持っています。たいていの質問には答えてくれました。

自分たちのチームが抱えている悩みを伝え、講師の方の経験からどう解決したかを聞いたり、アドバイスをもらったり、意見交換、講義の内容に関して質問するなど、かなり濃密な時間を過ごせました。

研修に参加して学んだこと

ワークショップの時間がたくさん取られていて、実践形式でスクラムを学べました。あくまで研修の内容はスクラムフレームワークスクラムマスターの役割等基礎的な事だけです。ですが、今まで間違っていた僕のスクラムの概念が正しい形で自分の中に落とし込めました。例えば、僕はスプリントレビューというものを重要視していませんでしたが、スプリントレビューはユーザーにとって不必要な機能のリリースなどを防ぐという大切なイベントである事を学びました。

結論と今後

1. なぜ認定スクラムマスター研修がおすすめなのか

認定スクラムマスター研修は座学だけでなく実践形式でスクラムについて学ぶ事ができる場です。これからスクラムマスターになる方や既にスクラムマスターの方、日々チーム開発に悩んでいる方にとって間違った知識や足りない知識が何か知る事ができ、正しい知識を身につけることができます。また、認定スクラムマスター研修に参加することでいろいろな立場・知識の人たちと話せますし、講師陣の方ともたくさん話す事ができるので現場のリアルな経験を共有できます。体系的にスクラムの実践方法を学びたい方におすすめです。

2. 研修に参加して学んだこと
  • スクラムの正しい知識
    今までスクラムに関して多少知識があると思っていましたが、僕の所属する開発チームが実践していたスクラムは足りないイベントがあったりスクラムの定められたルールに則っていないことに気がつきました。

  • スクラムイベントの実践方法
    2日目のワークショップでは、スクラムのルールに則って実際にチームでスクラムを実践します。このワークショップでスクラムの実践方法がわかります。

  • スクラムのイベントを全て実施する必要がある
    現状、僕の所属する開発チームはスクラムで定められた全てのイベントを行っているわけではありません。スクラムをやるのであれば全てのイベントを取り入れられるようにスクラムマスターが動いた方がいいなと思いました。

  • それでもスクラムは難しい
    合宿に参加したからスクラムの全てを理解したわけではないです。冒頭で言っていた「行っているスクラムってこれでいいんだっけ?」という違和感は完全に消えたわけではありませんので、これから更に学習と経験を積むことが大事だと思います。

3. 認定スクラムマスター研修後に実施したこと・チャレンジしたいこと
  • 1歩下がってチームをみてみる
    今まではチームの先頭に立ち自ら引っ張り、プロセスを変えることばかりに目を向けがちでした。ですが、全体を俯瞰し小さい変化に気づく事も大事だと気づきました。例えば、チームの発言で気づかぬうちに誰か個人を攻撃していないか、ミーティングにちゃんと参加しているか(PC開いて作業してしまってる人がいたり...)こういう事に気づいてもらえるように仕向けていくのもスクラムマスターの1つの仕事です。

  • コミュニティに参加する。
    これは今、参加できるイベントやコミュニティを探しているところです。悩みを共有しお互いに助け合えるコミュニティを見つけ、そのコミュニティで他の企業の話を聞くことで知識や足りない経験を補っていけるようにしていければと思っています。

これからもチーム開発に悩み続け、できる限りの改善をおこない、高いパフォーマンスが出せるチームにみんなでしていきます!

採用

弊社ではエンジニアを募集中です!ぜひ一緒に成長していきませんか?ご興味ありましたら一度カジュアルにお話をできたらと思います。

採用ページはこちら

Active Record Arel使いこなし術

こんにちは。フォースタートアップス株式会社、エンジニアの石田です。

前々回前回Rubyの話をしました。今回もRubyの話です。Active RecordのArelを使い尽くす、という内容です。お付き合いください。

注意

この記事は「Arelを使わないといけなくなったときに参考になるように」という目的で書きました。「Arelを積極的に使っていこう」という趣旨ではないのでご留意ください。詳しくは次章にて。

そもそもArelを使うことについて

ArelRailsの内部APIです。内部APIであるがゆえにRailsユーザーがこれを使うことは公式で推奨されていません*1 *2SQLRDBのデータ操作を行うための言語なので、RDBのデータ操作を行うのはRubyよりSQLのほうが得意であることは明らかです。Railsも「データ操作が複雑になったときは素直に生SQLを使え」というスタンスです。

ちなみにRails 6までは公式ドキュメントにArelは登場しなかったと思うのですが、Rails 7では公式ドキュメントにArelが登場します。ただし、「複雑なSQLArel.sqlを使って生SQLを書いてね。」という内容なので、スタンスは変わっていないようです。Arel.sql以外を使ってSQLを構築することは相変わらず非推奨だと思われます。

じゃあ、なぜArelを使うのか。それは「しかたなく」です。生SQLをつかうと実行結果がActiveRecord::Relationにならずscopeのチェーンができないことがあります。そんな場合でもいくらでもやりようはあるのですが(特にRails 7ではArel.sqlで全部解決すると思われる)、時には「Arelを使ったほうが速く実装できる」「Arelを使ったほうが既存のコードへの影響範囲が少ない」ということがあります。そんなときに「しかたなく」使います。

「しかたなく」使うものなので、使うときは以下の事項を覚悟しておいたほうがいいです。なんなら使うたびに心を痛めるべきでしょう。

  • 内部APIなのでRails/Active Recordのバージョンアップで動かなくなることがある
  • 内部APIなのでRails/Active Recordのバージョンアップで挙動が変更されても、パッチノートなどには書かれない
  • 可読性が低くなりがち

上記のことを理解した上で「どうしてもArelを使いたい場合」もしくは「遊びで触ってみたい場合」に以下を読み進めてみてください。

Arel使用のサンプル

Arelを使ったサンプルを作りました。特に断りがなければMySQLPostgreSQL両方で動くはずです。MySQLPostgreSQLで記述が異なる場合は両方を用意しました。MySQL/PostgreSQL以外のRDBMSは未確認です。

以下のバージョンで動作確認しています。

および

SQL関数の利用(DATE型の年で検索)

SQL関数を使うためにはArel::Nodes::NamedFunctionを使います。例としてDATE型を文字列に変換して、その値でWHEREをかけてみます。

MySQL

Task.where(
  Arel::Nodes::NamedFunction.new(
    'DATE_FORMAT',
    [
      Task.arel_table[:created_at],
      Arel::Nodes.build_quoted('%Y')
    ]
  ).eq('2023')
)
# => SELECT `tasks`.* FROM `tasks` WHERE DATE_FORMAT(`tasks`.`created_at`, '%Y') = '2023'

ちなみにSELECT句で使う場合はこんな感じです。

years = Task.select(
  Arel::Nodes::NamedFunction.new(
    'DATE_FORMAT',
    [
      Task.arel_table[:created_at],
      Arel::Nodes.build_quoted('%Y')
    ]
  ).as('year')
)
# => SELECT DATE_FORMAT(`tasks`.`created_at`, '%Y') AS year FROM `tasks`

years.first.year #=> "2023"

PostgreSQL

Task.where(
  Arel::Nodes::NamedFunction.new(
    'TO_CHAR',
    [
      Task.arel_table[:created_at],
      Arel::Nodes.build_quoted('YYYY')
    ]
  ).eq('2023')
)
# => SELECT "tasks".* FROM "tasks" WHERE TO_CHAR("tasks"."created_at", 'YYYY') = '2023'

こちらもSELECT句で使う場合はこんな感じです。

years = Task.select(
  Arel::Nodes::NamedFunction.new(
    'TO_CHAR',
    [
      Task.arel_table[:created_at],
      Arel::Nodes.build_quoted('YYYY')
    ]
  ).as('year')
)
# => SELECT TO_CHAR("tasks"."created_at", 'YYYY') AS year FROM "tasks"

years.first.year #=> "2023"

JSON型(JSONB型)への問い合わせ

昨今、RDBで部分的にドキュメント指向っぽいことをしたくてJSON型(あるいはJSONB型)を使うことがあります。JSON型へのクエリは各RDBMSで独自に構文が定義されていることが多いです。それをArelで使ってみます。これにはArel::Nodes::InfixOperationを使います。

MySQL

JSON 値を検索する関数はこちらを参照: MySQL :: MySQL 8.0 リファレンスマニュアル :: 12.18.3 JSON 値を検索する関数

ここでは->演算子を使ってみます。

Task.where(
  Arel::Nodes::InfixOperation.new(
    '->',
    Task.arel_table[:contents],
    Arel::Nodes.build_quoted('$.meta')
  ).eq('foo')
)
# => SELECT `tasks`.* FROM `tasks` WHERE `tasks`.`contents` -> '$.meta' = 'foo'

PostgreSQL

JSON関数と演算子はこちらを参照: 9.15. JSON関数と演算子

ここでは->>演算子を使ってみます。

Task.where(
  Arel::Nodes::InfixOperation.new(
    '->>', 
    Task.arel_table[:contents], 
    Arel::Nodes.build_quoted('meta')
  ).eq('foo')
)
# => SELECT "tasks".* FROM "tasks" WHERE "tasks"."contents" ->> 'meta' = 'foo'

特定のレコードを先頭にする

MySQLPostgreSQLではORDER BY id = 123 DESCなどとすると、条件に一致するレコードを先頭に持ってくることができます。これをArelで書いてみます。

Task.order(Arel::Nodes::InfixOperation.new('=', Task.arel_table[:id], 123).desc)
# => SELECT `tasks`.* FROM `tasks` ORDER BY `tasks`.`id` = 123 DESC

おわりに

書き始める前はLEFT OUTER JOINUNIONについても書くつもりでしたが、そういえばLEFT OUTER JOINRails 6でArelを使わずに書けるようになったんでした。UNIONもいつの間にかArelを使わずに書けるようになってました。流石にUNION ALLは無理だろ、と思ったらこれもArelをつかわずに書けました(UNION, UNION ALLは内部APIっぽいメソッドを使う必要があるけど)。おかげで書く内容が少なくなってしまいました。

短かったですが、いかがだったでしょうか?ググってもすぐには見つからない、ちょっとマニアックな内容だったんじゃないでしょうか?ちなみにこの記事のタイトルについて。「むやみに使うものではない」という意味で「使いこなし術」です(後付)。

Arelは積極的に使うべきものではないですが、非常時の対応として道具箱の中に入れておいても良いんじゃないかと思います。読者の方の開発に少しでも役立てれば幸いです。

参考文献

意外と知らないRubyの基礎知識(と少し応用)

こんにちは。フォースタートアップス株式会社、エンジニアの石田です。

前回の記事では「主にバックエンドのエンジニアをしています。」と自己紹介しましたが、今はバックエンドとフロントエンド両方を担当しています。

特に直近数ヶ月はフロントエンド(React/Next + TypeScript)ばかりやっています。ですが、私が一番好きな言語はRubyですので、今回も前回と同じくRubyの話をしたいと思います。

今回の内容はRubyの基礎知識+豆知識的な内容になります。後半になるにつれて認知度が低く、マニアック度が高くなる気がします。お付き合いください。

ゲッター/セッターメソッド

Rubyで頻繁に使うattr_reader, attr_writer, attr_accessor の正体について説明します。

class Hoge
  attr_reader :foo # ゲッターを定義
  attr_writer :bar # セッターを定義
  attr_accessor :baz # ゲッター/セッターを定義

  def initialize(foo, bar, baz)
    @foo, @bar, @baz = foo, bar, baz
  end
end

これはよく見るRubyのコードですね。ゲッター・セッターを定義しています。Rubyにおいてこの「ゲッター・セッター」とは何でしょうか?実はこれはただのメソッドです。

上のコードは下のコードと同じ意味です(注意: 文字通り「意味的に」同じという意味です。Rubyインタプリタがどのようにこれを実行するかは別です。後述)。

class Hoge
  def initialize(foo, bar, baz)
    @foo, @bar, @baz = foo, bar, baz
  end

  # ゲッターを定義
  def foo
    @foo
  end

  # セッターを定義
  def bar=(val)
    @bar = val
  end

  # ゲッター/セッターを定義
  def baz
    @baz
  end
  def baz=(val)
    @baz = val
  end
end

hoge = Hoge.new(1, 2, 3)
hoge.bar=(100) # 普通のメソッド実行
hoge.bar= 100  # カッコが省略できる
hoge.bar = 100 # 特別ルールでメソッド名の「=」の前にスペースが入ってもOK

Rubyインスタンス変数は全て必ずprivateスコープです。外部からアクセスするためにはpublicスコープのメソッドをつかう必要があります。

attr_〇〇 を使うとこれらのメソッドが定義されます。特に注目なのがセッターメソッドです。実際にはメソッドの実行なので、bar=という名前のメソッドを実行しているだけですが、hoge.bar = 100 というふうにあたかもhoge.barに値を代入しているように書くことができます。この辺がRubyマジックですね。

注意

attr_〇〇を使うのとゲッター/セッターメソッドを自力で書くのは意味的には同じですが、実行速度は違います。(少なくともCRubyでは)attr_〇〇はCレベルの専用処理が動くのでattr_〇〇を使ったほうが実行速度は速くなります。特に理由がなければattr_〇〇の方を使いましょう。

privateメソッドは「外部からアクセスできないメソッド」という意味ではない(?)

以下のコードがあったとします。これを実行するとNoMethodErrorが発生します。privateなので当たり前ですね。

class Hoge
private

  def say_foo
    puts 'foo'
  end
end

hoge = Hoge.new
hoge.say_foo # private method `say_foo' called for #<Hoge:0x00007f86bb887f40> (NoMethodError)

では以下のコードの場合はどうなるでしょうか?

class Hoge
  def public_say_foo
    self.say_foo
  end

private

  def say_foo
    puts 'foo'
  end
end

hoge = Hoge.new
hoge.public_say_foo # どうなるでしょう?

この実行結果は実はRubyのバージョンによって変わります

# Ruby 2.7より前
hoge.public_say_foo # private method `say_foo' called for #<Hoge:0x00007f86bb887f40> (NoMethodError)

# Ruby 2.7以降
hoge.public_say_foo #=> foo

なぜRuby 2.7より前のバージョンではNoMethodErrorが発生したのでしょうか?それは以下のような理屈です。

  • もともとprivateメソッドは「レシーバを明示的に指定して実行できないメソッド」という意味だった
  • レシーバのselfは省略できる
  • privateメソッドはレシーバを省略しなければならない = レシーバは必ずself

上記コードはレシーバselfを明示的に指定しているので、Ruby 2.7より前ではNoMethodErrorが発生しました。

Ruby 2.7ではこれにルールが追加されました。

なのでRuby 2.7以降ではエラーにならなかったんですね。 レシーバselfを明示的に指定しない場合はバージョンに関わらずエラーにはなりません。

class Hoge
  def public_say_foo
    say_foo
  end

private

  def say_foo
    puts 'foo'
  end
end

hoge = Hoge.new
hoge.public_say_foo # どのバージョンでも foo が返る

子クラスからのprivateの実行

さて、次は以下のコードです。

class SuperHoge
private

  def say_foo
    puts 'foo'
  end
end

class Hoge < SuperHoge
  def public_say_foo
    say_foo
  end
end

hoge = Hoge.new
hoge.public_say_foo # どうなるでしょう?

これは"foo"が返ります。

hoge.public_say_foo #=> foo

先述の通りprivateメソッドはレシーバを省略すれば実行できます。そのクラスに対象メソッドが定義されていなかった場合、Rubyは自動的に先祖クラスを順番に探しに行きます。この場合SuperHogeクラスにsay_fooが存在しているので無事実行できました。

「あれ?」と思った方へ

ここまで読んで、一部の方(とくにJavaオブジェクト指向を学んだ方)はこう思うかもしれません。「子孫クラスで先祖クラスのprivateメソッドを実行できるならprotectedスコープって何?」と。これについては次の章で話しますが、RubyprotectedJavaprotectedは全く別の概念です。

とりあえず今は「子孫クラスで先祖クラスのprivateメソッドを実行できる」とおぼえて、例えばテンプレートメソッドパターン(リンクは弊社ブログ)protectedキーワードが使われているのを見つけたら、そっとprivateに置き換えてあげてください

protectedメソッドは「自身と子孫のクラスからしかアクセスできないメソッド」という意味ではない

先述の通り、RubyprotectedJavaprotectedは全く別の概念です。 Rubyのprotectedメソッドは「自身と子孫のクラスからしかアクセスできないメソッド」という意味ではありません。

Ruryのprotected は公式ドキュメントでは以下のように説明されています。

protected に設定されたメソッドは、そのメソッドを持つオブジェクトが selfであるコンテキスト(メソッド定義式やinstance_eval)でのみ呼び出せます。

https://docs.ruby-lang.org/ja/latest/doc/spec=2fdef.html#limit

これだけ読んでもよく意味がわかりませんね。簡単な例を用意しました。

protectedを使ったサンプルコード

サンプルの要件は以下です。

  1. FullNameという値オブジェクトクラスが存在している
  2. FullNameクラスのインスタンスは2つの値を持っている
    • first_name
    • family_name
  3. FullNameクラスのインスタンスの比較は「持っている値がすべて同じ場合」はtrueにしたい
  4. FullNameクラスが持っている値は外部から隠蔽したい(この要件が存在する理由は不明ですが、とにかくそういう要件があると思ってください)。

この要件を満たすFullNameクラスを作っていきます。

まず単純に要件1と要件2を満たすクラスを作ってみます。

class FullName
  def initialize(first_name, family_name)
    @first_name = first_name
    @family_name = family_name
  end
end

foo = FullName.new('foo', 'bar')
bar = FullName.new('foo', 'bar')

puts foo == bar #=> false <- これがtrueになるようにしたい

このままでは要件3を満たしません。要件3を満たすために==メソッドをoverrideしましょう。

class FullName
  def initialize(first_name, family_name)
    @first_name = first_name
    @family_name = family_name
  end

  def ==(other)
    self.first_name  == other.first_name && \
    self.family_name == other.family_name
  end

  def first_name
    @first_name
  end

  def family_name
    @family_name
  end
end

foo = FullName.new('foo', 'bar')
bar = FullName.new('foo', 'bar')

p foo == bar #=> true <- 要件3を満たす

foo.first_name #=> "foo" <- 要件4違反

これで要件3を満たすようになりました。しかし要件4に違反しています。どうすればいいでしょうか?#first_name, #family_name をprivateにすることはできません。other.first_name, other.family_nameNoMethodErrorになってしまうからです。

ここでprotectedの出番です。

class FullName
  def initialize(first_name, family_name)
    @first_name = first_name
    @family_name = family_name
  end

  def ==(other)
    self.first_name  == other.first_name && \
    self.family_name == other.family_name
  end

protected

  def first_name
    @first_name
  end

  def family_name
    @family_name
  end
end

foo = FullName.new('foo', 'bar')
bar = FullName.new('foo', 'bar')

p foo == bar #=> true <- 要件3を満たす

foo.first_name #=> NoMethodError <- 要件4を満たす

これですべての要件を満たすFullNameクラスができました!

問題は「そもそもこんな要件が実際に存在するのか(とくに要件4)?」ということです。想像の通りprotectedを使わなければいけない場面は相当少ないと思われます。私は実務でも私的なプロジェクトでもprotectedを使ったことが一度もありません。

Rubyの親であるMatz氏もprotectedを実装したことを後悔している様子です

注意

このFullNameは値オブジェクトのクラスとしては完全ではありません。HashのキーとしてFullNameインスタンスが使えないからです。Hashのキーの同一性は==メソッドではなくeql?メソッドを使って比較します。詳しくは述べませんが、Hashのキーとしても使える値オブジェクトを実装したい場合、上記のコードは参考にしないでください

Bool/Boleanクラスは存在しない

どの言語でもありそうなBool/Bolean型・Bool/BoleanクラスというものはRubyには存在しません。確かめてみましょう。

p true.class  #=> TrueClass
p false.class #=> FalseClass

TrueClassFalseClassクラスは全く別のクラスです。共通の親クラスも(すべてのクラスの共通の先祖クラスであるObject/BaseObjectクラスを除いて)ありません。

ちなみに、TrueClass/FalseClass はシングルトンパターンで実装されているのでインスタンスtrue/falseただひとつです。

Procとlambdaの違い

RubyにはProclambdaというかなり似ているけどちょっと違う2つの実装があります。

下のコードを見てみましょう。

proc = Proc.new { |x| x + 1 }
lamda = lambda { |x| x + 1 }

p proc.call(1) #=> 2
p lamda.call(1) #=> 2

p proc.class #=> Proc
p lamda.class #=> Proc

はい、全く同じに見えますね。しかし、細かく見ると違いがあります。

ちなみにProc.newlambdaにはそれぞれエイリアス(のようなもの)があります。

  • Proc.new -> proc
  • lambda -> -> (Ruby1.9より)

書き換えると以下になります。

proc = proc { |x| x + 1 }
lamda = -> (x) { x + 1 }

Procとlambdaはどう違うか

returnの挙動が違う

def say_foo1
  proc = Proc.new { return 10 }
  proc.call
  puts 'foo1'
end

def say_foo2
  lambda = lambda { return 10 }
  lambda.call
  puts 'foo2'
end

puts say_foo1
puts '-----'
puts say_foo2

このコードを実行すると以下の出力になります。

10
-----
foo2
nil

どういうことかというと、Procの場合はreturnした時点で#say_foo1から抜けてしまうんですね。それに対してlambdaはreturnしてもブロックから抜けるだけです(このブロックというものの概念の説明も難しいのですが、余計ややこしくなるので今回は説明しません)。

引数の扱いが違う

以下のコードを見てください。

proc = Proc.new { |a, b| [a, b] }

p proc.call(1) #=> [1, nil]
p proc.call(1, 2, 3) #=> [1, 2]

# 足りないところをnilで埋められる
# 余ったところは切り捨てられる

lambda = lambda { |a, b| [a, b] }

p lambda.call(1) # ArgumentError
p lambda.call(1, 2, 3) # ArgumentError

上記のようにProcとlambdaでは引数の扱いが違います。

Procとlambdaの識別方法

Procとlambdaは#lambda?をつかって識別できます。

prop = Proc.new { |x| x + 1 }
lamda = lambda { |x| x + 1 }

p prop.lambda? #=> false
p lamda.lambda? #=> true

(Procとlambdaはすくなくともruby 1.8のころからあったのに)#lambda? が実装されたのはruby 2.1です。それまではProcなのかlambdaなのか区別するのは困難でした。

Procとlambdaの違いについてまとめ

正直、Procとlambdaの違いは「Rubyの闇」「Rubyの技術的負債」のように感じます。

とりあえずProcは使わずにlambdaのみを使うようにすればトラブルは少なそうです。

ちなみにブロックというものはProcともlambdaとも挙動が違うのですが、それについて話すと長くなりすぎますし、ますます混乱するので割愛します。1つだけ言うとブロックはProcやlambdaと違い、なにかのインスタンスではないので変数に入れたり、引数で渡すことはできないものです。

なぜdef self.hogeと書くとクラスメソッドになるのか

Rubyではクラスメソッドを定義するときに以下のように書くのが普通です。

class Foo
  def self.what_is_your_name?
    puts "my name is Foo"
  end
end

Foo.what_is_your_name? #=> my name is Foo

でもなんでdef self.what_is_your_name?と書くとwhat_is_your_name?はクラスメソッドになるのでしょうか?それを理解するためには段階を踏む必要があります。

Step1. インスタンス固有のメソッドを定義できる

クラスメソッドからは一旦離れて、以下のコードを見てください。

foo = "foo"

def foo.add_ban
  self + "!!!"
end

p foo.add_ban #=> "foo!!!"

bar = "bar"
p bar.add_ban # NoMethodError

実はRubyではインスタンスに固有のメソッドを定義することができます。これを一般的に「fooインスタンスの特異クラスのインスタンスメソッド」と呼びます。単に「fooの特異クラスメソッド」や「fooの特異メソッド」と呼ばれることもあります。(公式の呼び方はなさそう?)

Rubyの「.」には少なくとも3つの意味があります。

  1. メソッド実行: 「.」の左がレシーバインスタンスで右がメソッド名
  2. 特異メソッドの定義: 「.」の左がインスタンスで右がメソッド名
  3. 小数点の「.

上記のdef foo.add_banは2の意味ですね。1の意味では無いので注意です。

Step2. 全てのクラスはインスタンスである

Rubyでは「全てのクラスはインスタンス」です。言っている意味がわからないかもしれませんが、とりあえず以下のコードを見てください。

class Hoge
end

hoge = Hoge.new
p hoge.class #=> Hoge

p Hoge.class   #=> Class
p String.class #=> Class

hogeのクラスはHogeです。これは当たり前ですね。実はHogeのクラスはClassという名前のクラスです(言い換えるとHogeClassクラスのインスタンス)。Hogeに限らすStringなど、全てのクラスはClassクラスのインスタンスです。

なのでHogeクラスは以下のように定義することもできます。

Hoge = Class.new # こうやってクラス定義できる

hoge = Hoge.new
p hoge.class  #=> Hoge

これは余談になりますが、Rubyでクラス名の頭が大文字なのはクラスが定数だからです。Rubyでは変数の頭の文字によってそれがどんな変数なのかを識別します。

  • 先頭が$ : グローバル変数
  • 先頭が@ : インスタンス変数
  • 先頭が@@ : クラス変数(こいつのことは忘れてください)
  • 先頭が大文字(全部大文字である必要はない): 定数
  • 先頭が小文字: ローカル変数

Step3. クラスの特異メソッドを定義できる

「クラスはインスタンス」ということは、Step1のように「クラスに特異メソッドが定義できる」ということです。これがクラスメソッドの正体です。

class Foo
  p self #=> Foo

  def self.what_is_your_name? # `def Foo.what_is_your_name?` と同じ意味
    puts "my name is #{self}"
  end
end

Foo.what_is_your_name? #=> "my name is Foo"

つまり、Rubyには「クラスメソッド」というものは実は存在せず、全てはインスタンスメソッドです。「クラスに所属するインスタンスメソッド」か「特異クラスに所属するインスタンスメソッド」かの違いしかありません。

変数名やメソッド名はASCII文字じゃなくてもいいんだよ

注意: 下記はソースコードファイルをUTF-8で保存して動作確認しています。それ以外の文字コードを使った場合は動作確認していません。

変数名やメソッド名に使える文字にはルールがありますが、「ASCIIコード文字に限る」のようなルールは存在しません。日本語だろうがUnicode記号だろうが使うことができます。

日本語 = "Japanese"

p 日本語 #=> "Japanese"

Q これなんの役にたつの?

A 特になんの役にもたちません。ただしユビキタス言語を定義するときにどうしても英語にできないものがあったときはつかえるかも?(例は思いつきませんが。)

「なんの役にもたちません」と言いましたが、やろうと思えば以下のような事もできます。

def √(x)
  Math.sqrt(x)
end

p √ 2 #=> 1.4142135623730951

def i(num)
  Complex(0, num)
end

e = Math::E
π = Math::PI

# オイラーの公式
p e ** (i π) + 1 == 0 #=> true

世の中にはこういうコードが「美しい」と思う人が一定数いるようで、こういうのがたくさん定義されたgem(例:unicode_math)が存在します。

おわりに

「基礎知識」と言いながらRubyの深淵を撫でるような内容になってしまいました。ボリュームも多いですね。しかし、これでも省略した内容が複数あります。

  • ブロックとはなにか
  • def には戻り値がある
  • なぜprivateと書くとその下のメソッドがprivateになるのか
  • 評価戦略

これらはまた別に機会があれば話せたらよいな、と思います。

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

参考文献

メタプログラミングRuby