ESM アジャイル事業部 開発者ブログ

永和システムマネジメント アジャイル事業部の開発者ブログです。

Railsのバグレポートの書き方

こんにちは。Railsを普段使いしていて、想定しない挙動をしたり/この挙動は不具合ではないかと疑うことがあるかと思います。 今回enum関連の不具合報告をしました。本記事では、バグレポートの書き方と実際に報告した不具合の内容を説明したいと思います。

バグレポートの書き方

バグレポートは、基本的に Rails Guide の Creating a Bug Report に沿って報告するのが良いです。 付け加えると、実際に再現するようなRailsアプリや再現コードがIssueに記載出来るとベストです。

私が実際に挙げたIssueはUse non-exist enum string to get unrelated record in My SQL となります。

Issueの内容

まず、以下のユーザーモデルが定義されていたとします。

class User < ActiveRecord::Base
  enum status: { active: 0, non_active: 1 }
end

この時 User.find_by(status: :non_exist_status) とコードを書くとステータスが active のデータが取れます。原因は SELECT * FROM users WHERE status = 'non_exist_status' が発行されるのですが、文字列から数字への暗黙の変換がされてしまい SELECT * FROM users WHERE status = 0 と同じ意味になってしまうからです。

終わりに

バグ報告をまとめるのは、結構大変な行為かと思います。なので、無理せずに余裕がある時やモチベーションが高い時などに挑戦してみるのはいかがでしょうか?

コミュニケーションに必要なもの

こんにちは。 はじめまして。tkywtnb です。

ソフトウェア開発を行っていると様々な場面で人とのコミュニケーションが発生します。 コミュニケーションが上手くいっているかどうかがソフトウェア開発に大きな影響を与えることがあるのは、みなさんも心当りがあるのでないでしょうか?

今回は、コミュニケーションに必要なものは何なのか、私が考えていることを言語化してみたいと思います。

以降の文中に何度かコンテキストという言葉が出てきますが、知識、経験、立場、価値観などを統合した概念として、コンテキストを使っています。

そもそもコミュニケーションって何だろう?

先に進む前に、そもそもコミュニケーションとは何なのかについて確認しておきます。

コミュニケーション【communication】

1 社会生活を営む人間が互いに意思や感情、思考を伝達し合うこと。言語・文字・身振りなどを媒介として行われる。「コミュニケーションをもつ」「コミュニケーションの欠如」

2 動物どうしの間で行われる、身振りや音声などによる情報伝達。

引用元: デジタル大辞泉

今回取り扱う範囲のコミュニケーションは、 1 の 「社会生活を営む人間が互いに意思や感情、思考を伝達し合うこと」にあたります。

さっそく、伝達することの結果である「伝わる」までを詳しくみていくことにします。

伝わるということ

何かが伝わるまでは、どのような事が行われているのでしょう? 「わたし」から「あなた」へ何かが伝わるまでは、おおよそ次のようなプロセスを経ていると考えられます。

  1. 「わたし」が伝えるべき事を認識する
  2. 「わたし」が伝えるべき事を表現する
  3. 「あなた」が表現されたものから伝えられた事を読み取る

これはかなり単純化したモデルです。 実際のコミュニケーションにおいては、単純に 1 から 3 の順序に進むとは限りません。 例えば、伝えることを表現しようとすることによって伝えるべきことがよりはっきりと認識出来たり、表現したことによって伝えるべきことが違っていると感じたりすることがあります。それでも、この単純なモデルを使ってコミュニケーションで発生する問題を考えることが出来ます。

どんな問題が起きるのかひとつずつ確認していきましょう。

「「わたし」が伝えるべき事を認識する」段階で発生する問題

ここで起きそうな問題は、次のようなものでしょう。

  • 何を伝えるべきなのかわからない
  • 伝えるべき事を誤認する

これらの問題はなぜ発生するのでしょう?

まっさきに挙げられるのは事実と感情を区別できていないケースです。 個人的に観測している範囲では、かなりの割合でこの問題が発生しているように見えます。

また、何を取り扱うのかについて、伝える相手との間でまったく共有されていない状況だとしたら、容易に発生しそうです。 取り扱う対象への理解が十分でないことによっても引き起こされるでしょう。 相手が取り扱う対象をどのように理解しているのかについての認識が不十分であっても問題が発生すると考えられます。

さらに、何かを教えたりする状況において、相手のスキルや知識に適さないことを伝えるべき事として誤認してしまうことがあります。 学習する人が何かを学ぶ為には、前提となる知識やスキルが必要となることがほとんどです。 相手の状態を十分に把握していないと、あまり効果がない指導になってしまうでしょう。

「「わたし」が伝えるべき事を表現する」段階で発生する問題

ここで起きそうな問題は、次のようなものでしょう。

  • どのように表現したら良いかわからない
  • 表現の仕方を間違える

これらの問題の要因は、自身のスキルと知識によるものです。

どのように表現したら良いかわからないことの要因は、表現の手段により異なります。

まず言葉による表現を考えると、必要な語彙を持っていないことによって引き起こされます。 語彙が乏しいことによる影響は、表現が出来ないだけに留まらず、もっと大きなものになります。 語彙が乏しいことは、言葉の違いによる微妙なニュアンスの違いを認識出来ないことにも繋がり、先に述べた取り扱う対象などへの理解が浅くなる要因にもなり得ます。

言葉による表現は、細心の注意をはらったとしても曖昧さを含んでしまうものであり、図などを用いて表現することが有効なケースがしばしばあります。 プログラマ同士のコミュニケーションの場合、具体的なコードで表現することが可能なものはコードで表現するのが一番でしょう。 しかし、図解を用いたり適切なコードで表現するためには、ある程度のスキルが必要になります。 これらのスキルが不足していると、問題が発生することになります。

表現の仕方については、単に事実などの情報を誤って表現してしまうというのも問題となりますが、もう少しややこしい問題を含んでいます。

攻撃的な表現を使ってしまったり明言を避ける為に曖昧な表現を使ってしまったりといった問題も、適切な表現でないという意味で、表現の仕方を間違えていると言えるでしょう。 例えば、ただ相手の理解を確認すれば良いケースにおいて、まだ理解できないのか?といったプレッシャーを掛けてしまうようなものが、攻撃的な表現にあたります。 このような態度は意図が正しく伝わらないだけでなく、相手が理解することを諦めてしまう事などに繋がるのでとても有害です。 ここで挙げた表現の差異は性格の問題のように捉えられがちですが、スキルの問題です。

相手のスキルや知識に適していない表現を使ってしまうのも、表現の仕方の間違いの一種でしょう。 これは相手に関する理解が不十分な為に引き起こされます。

「「あなた」が表現されたものから伝えられた事を読み取る」段階で発生する問題

ここで起きそうな問題は、次のようなものでしょう。

  • 表現に使用された言葉を知らない
  • 表現に使用された言葉などの解釈が「わたし」と異なる

これらの問題の要因は、相手のコンテキストに適さない表現を選択したことによるものと考えられます。 つまり、このステップで問題が発生しているわけではなく、前の段階である表現の選択時点で問題が発生していることになります。

意図したとおりに伝わったかどうかは相手に確認してみるしかないのですが、その事が問題をややこしくしています。 この後に何が起きるのかをみてみましょう。

  1. 「あなた」が伝えられた事をもとに「わたし」へ伝える事を認識する
  2. 「あなた」が伝えるべき事を表現する
  3. 「わたし」が表現されたものから伝えられた事を読み取る

これらのステップは「わたし」から「あなた」への逆となっているだけで、1.の表現が少し異なるものの同じものと言えるでしょう。 よって、これらのステップにおいてもここまで見てきた問題が発生する可能性があり、2重3重に問題が積み重なってしまうこともあります。 その為、問題が発生した場合にどの時点で発生した問題かを特定するのは困難です。

実際のコミュニケーションにおいては、何か噛み合っていないな?という違和感に気が付き、ひとつずつ確認することによって問題を把握することになると思います。

問題の原因とその対策

各段階で発生する問題と原因についてみてきました。 改めて原因を分類しつつまとめます。

  • 相手への理解に関するもの
    • 相手が取り扱う対象をどのように理解しているか、十分に把握出来ていない
    • 相手のスキルや知識を十分に把握出来ていない
  • 自身のスキルに関するもの
    • 事実と感情を混同して捉えてしまう
    • アサーティブでない態度になっている
    • 表現するのに使用するスキルが十分でない
  • 事前準備に関するもの
    • 何を取り扱うのかについて共有されていない
    • 取り扱う対象への理解が不十分である
    • 取り扱う対象を含む分野に関する語彙が乏しい

ここからは原因の分類ごとに対策を考えてみます。

相手への理解に関するもの

ここに挙げたものは、相手に尋ねるなどする必要があるものになります。 つまり、対策する為にコミュニケーションが必要になります。 コミュニケーションの矛盾ともいうべきところがここです。 冗長な表現になってしまうのを覚悟で矛盾について説明するならば、次のようになります。 総じてコミュニケーションの目的は何らかの共通理解を築くことだと捉えることができますが、共通理解を築かなければならない状況とは共通理解が築かれていない状況なのです。

ここに挙げられているような要因を解消していくことが、コミュニケーションだと言い換えられるかもしれません。

共通して理解出来る表現を探りながら、 それを足がかりとしてお互いのコンテキストについて理解を深めるよう、 丁寧に対話を重ねていく。 これ以外の方法は無さそうです。

自身のスキルに関するもの

ここに挙げたものも、基本的には事前に対策が可能なものです。 ただし、対策がスキルの習得となる為、必要な期間が長くなります。 それでは個別に見ていきます。

事実と感情を混同して捉えてしまう

客観的事実と、どのように感じたのかという感情を区別し整理することで、このケースは避けることが出来ます。 次に取りあげるアサーティブコミュニケーションのスキルを身に付けることで、自然と実践出来るようになるでしょう。

アサーティブでない態度になっている

突然アサーティブという言葉が出てきましたが、これは何でしょうか?

アサーティブネス(Assertiveness)の訳語は、「自己主張すること」。でも、アサーティブであることは、自分の意見を押し通すことではありません。 自分の気持ちや意見を、相手の気持ちも尊重しながら、誠実に、率直に、そして対等に表現すること を意味します。

引用元: はじめに | アサーティブジャパン

ポイントは性格を変えるのではなく伝え方を変えることにあります。 アサーティブなコミュニケーションはスキルとしてトレーニングする事が可能です。 詳細については、書籍*1などを参照いただければと思います。

表現するのに使用するスキルが十分でない

問題の記述の中では、図解とコードで表現するスキルを取りあげました。 これらは日頃の訓練によって身に付けておく必要があります。 特に図解するスキルについては、プログラマ以外の方とのコミュニケーションにも有効なので、積極的に身に付けると良いでしょう。

事前準備に関するもの

ここに挙げたものは、次のように事前に準備しておくことで対策が可能なものです。

  • 何を取り扱うのか事前に共有しておく
  • 取り扱う対象について調査するなどして理解を深めておく

もちろん、シンプルな状況においてはこのような準備が必要ないことも多いでしょう。 しかし、思い込みによって本来は準備が必要だった状況をみすごしてしまうこともあるので、注意が必要です。

コミュニケーションにおいて一番大切なこと

色々な問題に対する対策を見てきましたが、コミュニケーションにおいて一番重要な事はなんでしょうか?

対策の中で触れたように、スキルはもちろん必要です。 ですが、それ以上に大切なのは異なるコンテキストを持つことを受けいれ理解しようとする姿勢にあると、私は考えています。 どちらかが正しいというのではなく、違いを知り理解しない事にはすり合わせる事すらできません。

違いを受け入れることによって、異なる視点をもつことを強みに変えていくことが出来るでしょう。

おわりに

ソフトウェア開発は創造的な問題解決と捉えることが出来ます。 創造的な問題解決には多様性に富む集団の方がそうでない集団と比べると、 たいてい高い成果を出すという実験結果があるそうです*2。 しかし、多様性は摩擦の増加の要因になります。 異質な人同士がひとつの問題に取り組み成果を出すのは容易な事ではありません。 多様な視点から新たな気づきを得て成果につなげるには、異なるコンテキストを持つ事を前提としたコミュニケーションが不可欠です。

豊かなコミュニケーションにより多様性を生かして素晴しいものを生み出していく。 少しでもそのヒントになれば幸いです。

*1:

*2:出典: 「Learn Better――頭の使い方が変わり、学びが深まる6つのステップ」 P.221

昨年末は "どう書く"(プログラミングイベント) を開催しました

こんにちは、新卒2年目の @wai-doi です。

今回は、昨年(2020年)の年末にアジャイル事業部内で「どう書く」というイベントを開催したことについて書きます。(※社内でのみ行ったイベントです)

例年、年内最後の営業日には事業部のメンバーでオフィスの大掃除をしています。しかし2020年は、新型コロナウイルスの影響で全員がリモートワークとなっており、大掃除を行わないことになりました。その代わりに何か事業部内でイベントをやろうということで、mattsan さんに「どう書く」を提案していただきました。

どう書くって何?

「どう書く」とは以下のようなイベントで、TLE(実行時間制限)の無い競技プログラミングをイメージしていただければよいと思います。当日に用意していただいた資料から抜粋しています。

  • 出題された問題を解くプログラムを時間内に書くイベント(遊び?)です
  • 問題に沿ったサンプルデータ(入力と期待する出力)を用意しますので、出力が一致することで確認してください
  • プログラミングの時間が終わったら、互いにプログラムを発表し解説します
  • この体裁は、ソフトウェアエンジニアの @Nabetani さんをはじめとする「横浜へなちょこプログラミング勉強会」さんが 2012 年から2019 年にかけて開催されていたイベント「オフラインリアルタイムどう書く」に倣っています
  • 「オフラインリアルタイムどう書く」のキャッチフレーズは 「負けられる戦いがここにある」 でした

過去に事業部で合宿をした際にも「どう書く」を行ったことがありました。

blog.agile.esm.co.jp

ESM どう書くReturns

「どう書く」が復活したということで「ESM どう書くReturns」と題して開催されました。

当日の様子

アジャイル事業部ではリモートワークがメインになってから、oVice を導入して仮想オフィスでコミュニケーションをとったりしています。今回のイベントも oVice に集まって会話しながら進めました。

f:id:yusukedoi:20210106011009p:plain:w300
当日のoViceの様子

当日のお題

お題は「カードの組み合わせのうち、数字とアルファベットの両方でビンゴになる組み合わせを出力する」というWビンゴ問題でした。

当日のお題はこちら Wビンゴ 〜 ESM どう書く Returns (2020/12/29)

f:id:yusukedoi:20210104235908p:plain:w400

解答時間は60分で、終わったら順番に全員の解答を発表していきました。

「どう書く」という名前の通り、他の方の解答を見るとこういう書き方もあるのかと発見があり楽しかったです。AtCoder のような TLE が無いため、いろんな解答があるのが面白いなと思いました。 ほとんどの方が Ruby で解答していましたが、言語の指定はなかったので C++ で解いている方もいました。

mattsan さんの模範解答です。

esmどう書く (2020/12/29) の解答 · GitHub

自分の解答も載せておきます。

https://github.com/wai-doi/doukaku-wbingo

おわりに

競技プログラミングを解き慣れている方もいて盛り上がり、大変良いイベントでした。 終了後の感想には、またやりたいという声がたくさんありました。

みなさんも腕に自信のある方はWビンゴ問題に挑戦してみてください〜。

Rails / OSS パッチ会オンライン 2021年1月のお知らせ

2021年1月の Rails / OSS パッチ会を 1月14日(木)にオンライン開催します。

この会をひとことでいうと、日頃のお仕事で使っている Rails をはじめとする OSS への upstream にパッチを送る会です。

今回は終わった後に、有志でオンライン新年会を行います。

会には Ruby と Rails のコミッターである顧問の a_matsuda もいますので、例えば Rails に送るパッチのネタがあるけれど、パッチを送るに適しているかの判断やパッチを送る流れが悩ましいときなど a_matsuda に相談して足がかりにするなどできます。

開催時間は 17:00-19:00 となりますがご都合のあう方はぜひご参加下さい。Google Meet あたりのテレビ会議システムを使います。

当日の招待 URL は Idobata の esminc/rails ルームで共有する予定です。

idobata.io

特に募集ページなど設けませんが、上記理由から Idobata のアカウントと (TV 会議システムによっては Google アカウント) が必要になると思います。

以下、前回の活動が関わる成果です。

ima1zumi: Reline

github.com

koic: RuboCop Rails

github.com

osyo-manga: activerecord-multi-tenant

github.com

yahonda: activerecord-oracle_enhanced-adapter

github.com

昨年末にリリースされた Ruby 3.0 や、Rails 6.1 といった話題などあるかもしれません。

その他の開催方針については以下の Gist に記していますので、ご参照ください。

Reboot Rails/OSS meetup online · GitHub

プロファイラを使ってRubyのコードをパフォーマンス改善したい

こんにちは。永和システムマネジメントの内角低め担当、はたけやまです。

作成したプログラムが想定していた速度で動かず困ってしまうこと、ありますよね? パフォーマンス改善を行う場合、プロファイラなどを使ってプログラムを計測し、どこがパフォーマンスのボトルネックとなっているかを見つけることが重要です。

Ruby プログラムをプロファイリングするための方法はいくつかありますが、今回は stackprof を使った方法をご紹介します。

stackprof を使ったプロファイリングは以下の手順で行います。

  • 計測対象のプログラムに stackprof を仕込む
  • 計測対象のプログラムを実行する
  • 計測結果からボトルネックを割り出す

計測対象となるプログラム

今回は例題として以下のライフゲームを計測してみます。

記事を読み進めるのに実際に動かす必要はありませんが、ぜひ動かしてみたい!という方は以下の手順でどうぞ。

$ git clone https://github.com/thata/lifegame-ruby.git
$ cd lifegame-ruby/
$ bundle install
$ bundle exec ruby main.rb
(Ctrl-Cで終了)

計測対象のプログラムに stackprof を仕込む

まず stackprof をインストールします。stackprof は RubyGems として提供されているので、Gemfile へ以下を追加して $ bundle install を実行してください。

diff --git a/Gemfile b/Gemfile
index dfc4a95..93fd4d1 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,3 +1,4 @@
 source "https://rubygems.org"
 
 gem "pry"
+gem "stackprof"

次に計測対象のプログラムへ以下のような感じに stackprof を仕込みます。out パラメータで指定されたファイル( /tmp/stackprof.dump )へプロファイリングの結果が出力されます。

diff --git a/1000_generation.rb b/1000_generation.rb
index 66a209a..41075cd 100644
--- a/1000_generation.rb
+++ b/1000_generation.rb
@@ -14,4 +14,7 @@ def start_game
   end
 end
 
-start_game
+# ブロック内の処理を計測
+StackProf.run(out: '/tmp/stackprof.dump') do
+  start_game
+end

計測対象のプログラムを実行する

次に、計測対象のプログラムを実行してプロファイリング情報を収集します。プロファイリングの結果は先ほど指定した /tmp/stackprof.dump へ出力されます。

$ time bundle exec ruby 1000_generation.rb

stackprof はサンプリング型のプロファイラ(1秒間に1000回スタックフレームを確認してどのメソッドが実行中かを調べる)なため「メソッドの実行割合」は調べることができますが「メソッドの実行時間」は調べることができません。そのため、時間の計測のため time コマンドを利用します。

計測結果からボトルネックを割り出す

出力されたプロファイリング結果を stackprof コマンドで確認します。

$ bundle exec stackprof --limit 100 /tmp/stackprof.dump

(省略)

bundle exec ruby 1000_generation.rb  1.97s user 0.15s system 96% cpu 2.203 total
==================================
  Mode: wall(1000)
  Samples: 1756 (0.00% miss rate)
  GC: 55 (3.13%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
       760  (43.3%)         760  (43.3%)     LifeGame#alive?
      1086  (61.8%)         534  (30.4%)     LifeGame#num_of_neighbors
       213  (12.1%)         213  (12.1%)     LifeGame#cell_str
       314  (17.9%)         101   (5.8%)     LifeGame#dump_cell
        48   (2.7%)          48   (2.7%)     (sweeping)
      1383  (78.8%)          45   (2.6%)     LifeGame#next_generation
      1338  (76.2%)          44   (2.5%)     LifeGame#next_generation_cell
         6   (0.3%)           6   (0.3%)     (marking)
      1701  (96.9%)           3   (0.2%)     Object#start_game
         1   (0.1%)           1   (0.1%)     LifeGame#initialize
        55   (3.1%)           1   (0.1%)     (garbage collection)
      1701  (96.9%)           0   (0.0%)     <main>
      1701  (96.9%)           0   (0.0%)     <main>
      1701  (96.9%)           0   (0.0%)     block in <main>

出力された結果を見ると、LifeGame#alive?LifeGame#num_of_neighbors に比較的時間がかかっていることが分かります。

パフォーマンス改善してみる

alive? はあまり高速化しようがなかったので、num_of_neighbors で無駄な計算を行わないようにしてみます。

diff --git a/lib/life_game.rb b/lib/life_game.rb
index fdf877a..de370e4 100644
--- a/lib/life_game.rb
+++ b/lib/life_game.rb
@@ -66,74 +66,52 @@ private
 
   def num_of_neighbors(x, y)
     n = 0
-    _x = 0
-    _y = 0
+    # セルをループさせるため、画面の端のセルを逆側の端のセルとつなげる
+    left_x = x - 1
+    left_x = @width - 1 if left_x < 0
+    right_x = x + 1
+    right_x = 0 if right_x >= @width
+    up_y = y - 1
+    up_y = @height - 1 if up_y < 0
+    down_y = y + 1
+    down_y = 0 if down_y >= @height
 
     # left
-    _x = x - 1
-    _x = @width - 1 if _x < 0
-    _y = y
-    if alive?(_x, _y)
+    if alive?(left_x, y)
       n += 1
     end
 
     # right
-    _x = x + 1
-    _x = 0 if _x >= @width
-    _y = y
-    if alive?(_x, _y)
+    if alive?(right_x, y)
       n += 1
     end
 
     # up
-    _x = x
-    _y = y - 1
-    _y = @height - 1 if _y < 0
-    if alive?(_x, _y)
+    if alive?(x, up_y)
       n += 1
     end
 
     # down
-    _x = x
-    _y = y + 1
-    _y = 0 if _y >= @height
-    if alive?(_x, _y)
+    if alive?(x, down_y)
       n += 1
     end
 
     # upleft
-    _x = x - 1
-    _x = @width - 1 if _x < 0
-    _y = y - 1
-    _y = @height - 1 if _y < 0
-    if alive?(_x, _y)
+    if alive?(left_x, up_y)
       n += 1
     end
 
     # upright
-    _x = x + 1
-    _x = 0 if _x >= @width
-    _y = y - 1
-    _y = @height - 1 if _y < 0
-    if alive?(_x, _y)
+    if alive?(right_x, up_y)
       n += 1
     end
 
     # downright
-    _x = x + 1
-    _x = 0 if _x >= @width
-    _y = y + 1
-    _y = 0 if _y >= @height
-    if alive?(_x, _y)
+    if alive?(right_x, down_y)
       n += 1
     end
 
     # downleft
-    _x = x - 1 
-    _x = @width - 1 if _x < 0
-    _y = y + 1
-    _y = 0 if _y >= @height
-    if alive?(_x, _y)
+    if alive?(left_x, down_y)
       n += 1
     end
 

再度計測してみると、以下のように若干パフォーマンスが改善しました。やったね。

  • 実行時間
    • 2.203秒 → 1.842秒
  • 全体に占めるnum_of_neighborsの割合
    • 30.4% → 18.9%
bundle exec ruby 1000_generation.rb  1.62s user 0.14s system 95% cpu 1.842 total
==================================
  Mode: wall(1000)
  Samples: 1391 (0.00% miss rate)
  GC: 53 (3.81%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
       694  (49.9%)         694  (49.9%)     LifeGame#alive?
       743  (53.4%)         263  (18.9%)     LifeGame#num_of_neighbors
       196  (14.1%)         196  (14.1%)     LifeGame#cell_str
       297  (21.4%)         101   (7.3%)     LifeGame#dump_cell
      1035  (74.4%)          44   (3.2%)     LifeGame#next_generation
        40   (2.9%)          40   (2.9%)     (sweeping)
       991  (71.2%)          34   (2.4%)     LifeGame#next_generation_cell
        13   (0.9%)          13   (0.9%)     (marking)
      1338  (96.2%)           6   (0.4%)     Object#start_game
      1338  (96.2%)           0   (0.0%)     <main>
      1338  (96.2%)           0   (0.0%)     <main>
      1338  (96.2%)           0   (0.0%)     block in <main>
        53   (3.8%)           0   (0.0%)     (garbage collection)

終わりに

今回は stackprof を使ってパフォーマンスの計測と改善を行いました。

モニタリング系の SaaSとして Datadog や New Relic などがありますが、それらレポートだけじゃ良くわからないよーという時は懐に忍ばせた stackprof で華麗に計測しちゃいましょう。

参考

CurrentAttributesを使ってリクエストごとのdefault_scopeを設定する

こんにちは、永和システムマネジメントに新卒で入社して4年目になりました swamp09 です。

この記事では、以前関わったRailsプロジェクトの default_scopeユースケースと実装例の話をします。

ユーザーが記事を投稿したり、記事にコメントしたりするWebサービスがあり、Twitterのようにユーザーが他のユーザーをブロックする機能がありました。 ブロックすると、ブロックしたユーザーからはブロックされたユーザーの投稿した記事やコメントが一切非表示になります。このブロックのロジックを default_scope で書けるとコーディングが楽になりそうだなぁとプロジェクトメンバーと話していました。 ただ、リクエストごとに異なるログインユーザーのブロックされたユーザーを非表示にするのは default_scope でうまく書けない、というところで悩んでいました。

そんな時に見つけたのが ActiveSupport::CurrentAttributes でした。

ActiveSupprt::CurrentAttributes ではリクエストごとの属性をシステム全体で簡単に利用できるよう保持できます。これを使って上記のような default_scope を書けるのではと思いました。

具体例をRailsチュートリアル第6版のサンプルアプリを元にコードで示します。sample_appにはUserとMicropostというモデルがあります。

class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
end

class Micropost < ApplicationRecord
  belongs_to :user
end

sample_appに追加でユーザーが他のユーザーを非表示にするためのブロック機能を追加していきます。

# Blockモデルを追加する
class Block < ApplicationRecord
  belongs_to :blocker, class_name: "User"
  belongs_to :blocked, class_name: "User"
  validates :blocker_id, presence: true
  validates :blocked_id, presence: true
end

# UserモデルにBlockモデルとの関連を追加する
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy

  has_many :active_blocks, class_name:  "Block",
                                  foreign_key: "blocker_id",
                                  dependent:   :destroy
  has_many :blocking, through: :active_blocks, source: :blocked
  has_many :passive_blocks, class_name:  "Block",
                                   foreign_key: "blocked_id",
                                   dependent:   :destroy
  has_many :blockers, through: :passive_blocks, source: :blocker
end

Blockモデルはsample_appのRelationshipモデルとほぼ同じです。

これに ActiveSupport::CurrentAttributes を用いてリクエストごとの default_scope を設定していきます。

# app/models/current.rb を追加する
class Current < ActiveSupport::CurrentAttributes
  attribute :blocking_ids
end

# app/controllers/concerns/hide_blocking_user.rb を追加する
module HideBlockingUser
  extend ActiveSupport::Concern

  included do
    before_action :set_current
  end

  private

  def set_current
    Current.blocking_ids = current_user&.blocking&.ids
  end
end

# ブロック機能が関わるControllerでconcernをincludeする
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
                                        :following, :followers]
  before_action :correct_user,   only: [:edit, :update]
  before_action :admin_user,     only: :destroy

  # concernをincludeする
  include HideBlockingUser

  def index
    @users = User.paginate(page: params[:page])
  end
end

# MicropostモデルとUserモデルのdefault_scopeを書き換える
class Micropost < ApplicationRecord
  belongs_to :user
  has_one_attached :image
  
  default_scope do
    if Current.blocking_ids.present?
      where.not(user_id: Current.blocking_ids).order(created_at: :desc)
    else
      order(created_at: :desc)
    end
  end
end

class User < ApplicationRecord
  has_many :microposts, dependent: :destroy

  default_scope do
    where.not(id: Currnet.blocking_ids) if Current.blocking_ids.present?
  end
end

以上のようにして、HideBlockedUserinclude したControllerであれば default_scope を使ったブロック機能が使えるようになりました。

まとめ

リクエストごとに default_scope を指定したい、というユースケースはあまり出会うことはないかもしれませんが、ActiveSupprt::CurrentAttributes を使えば実装できることがわかりました。実際に使うかどうかは別として… CurrentAttributesのドキュメントにも注意点が書かれていますが、CurrentAttributes を使う際はグローバルな値をいろんなところからアクセスできるようになるので、慎重に扱う必要があるかと思います。

ブロック機能を追加したコードは GitHub に置きました。なにかの参考になれば幸いです。

プロジェクト参画直後にやってよかった3つのこと

エンジニア2年目に突入しましたyuki0920です。

私は、入社から1年ちょっとの間に、4つのRailsを使ったプロジェクトに携わりました。 おおよそ約3ヶ月に1プロジェクトを経験していることになりますね。

本記事では、この4つのプロジェクトにエンジニアとして携わった経験から得られた、「プロジェクト参画直後にやってよかったこと」を3点紹介します。

1.ペアプログラミングをする

まずは、主要な機能を理解することが重要かと思います。

私は、プロジェクト参画後すぐに、参画済みメンバーと小さなタスクを対象にペアプログラミングすることがあったのですが、いまふりかえると、この経験はよかったなと思います。

早期に主要な機能のソースコードや業務プロセスについての理解を深められたため、その後の改修を効率的に進められるきっかけとなりました。

このペアプログラミングは、キーボードを操作してコードを書く「ドライバ」と、ドライバの補佐をする「ナビゲータ」の役割を、時間を決めて(20分くらい)交代しながら進めました。VSCodeLive Share機能を使うと、1つのエディタを互いに共有できるので、とても捗ります。

2.プロジェクト外のアプリケーションを作って、ライブラリの動作を把握する

アプリケーションで使うライブラリは多岐にわたります。

Railsアプリケーションならば、Gemfileをみると、導入されているGemの一覧がわかります。

就職前に私が利用していたRailsの学習教材では、Gemはあまり取り扱われていなかったので、プロジェクト参画当時は知らないGemが多々ありました。

しかし、Gemと密に関わる改修を行う場合、当然ながらGemの理解が必要となります。

そこで私のオススメは、プロジェクトで扱うアプリケーションとは別のところで、Gemを使い倒してみることです。

rails new してサンプルのアプリケーションを作成し、気になるGemを導入します。 GemのREADMEを見ながらパラメータを変えてみたりと手を動かしながら試すことで、理解が深まるはずです。

下記は私が使ってみたGemの一例です。

管理画面や認証、認可のGemはいくつもの種類がありますが、どれか1種類の使用経験があれば、応用は効きやすいかと思いますので、これらのGemをとっかかりとして、使ってみるのもよいかもしれません。

3. ER図を作成する

アプリケーションの規模が大きくなると、モデルが増え関連が複雑になります。 このモデルの理解には、ER図が役立ちます。

Railsアプリケーションならば、Rails ERDというGemが便利です。 Rails ERDを使うと、コマンド1つでER図を出力することが可能です。

下記は、サンプルのアプリケーションにAuthorモデルとBookモデルを1対多の関係で実装し、Rails ERDを導入して出力したER図です。

ER図

モデルの数が少なければ脳に記憶しておける場合もあるかと思いますが、モデルが増えるにつれ記憶しておくことは難しくなるので、すべてのモデルが記載されているER図が手元にあり、いつでも参照できるのは何かと便利です。

最後に

本記事では、「プロジェクト参画直後にやってよかったこと」を3点紹介しました。

「もっとこれやっといたほうが良いよ」などあれば、教えていただけると幸いです。