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

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

『中高生国際Rubyプログラミングコンテスト in Mitaka』をシルバースポンサーとして協賛します

これからの社会を担っていくことになる世代に向けての Ruby プログラミングコンテストである『中高生国際Rubyプログラミングコンテスト in Mitaka』をシルバースポンサーとして協賛します。

www.ruby-procon.net

アジャイルと Ruby が実現するソフトウェア開発は、開発者が「楽しさ」を感じられる開発であり、そこにはきっとビジネス価値がある――私たちはそう信じて行動を続けています。同じように、プログラミングを楽しくする Ruby を通じて実現される、中高生の作品を楽しみにしています!

f:id:koic:20211019154845j:plain


株式会社永和システムマネジメントでは、Ruby とアジャイルソフトウェア開発を通じてコミュニティと共生しながら成長したいエンジニアを絶賛募集しています。

agile.esm.co.jp

スペルチェッカーによる自動化のすすめ

さて、ひとめで以下のおかしな点がわかるでしょうか?

Fix a false positive for Layout/ClosingParenthesisIndentation when using keyword arguemnts.

正解例を少し先送りして、これは typo が含まれているテキストでした。私自身の typo 実例からでしたが、世の中には typo が溢れており、普段開発している膨大な量のテキストからひとつひとつの typo を見つけるのはそれなりに困難です。

それでは、正解例を見てみるのにツールを使ってみましょう。

% misspell a.txt
a.txt:1:83: "arguemnts" is a misspelling of "arguments"

このように typo 検出に役立つツールとしてスペルチェッカーがあります。私がリポジトリに対して度々使うスペルチェッカーはふたつあります。

ひとつめは正解例で使った Go 製の misspell です。Go のインストールが必要です。

github.com

そしてふたつめは Python 製の codespell です。Python のインストールが必要です。

github.com

こちらの codespell を使うと以下のような結果になります。

% codespell a.txt
a.txt:1: arguemnts ==> arguments

詳しくはそれぞれの公式サイトを参照となるのですが、テキスト規模が大きくなると検出できる対象が違ったりするので、重ね掛けをして typo のあぶり出しをすることでより効果的な typo 検出をすることができます。

Rails アプリケーションでの例を挙げると、スペルチェッカーの実行で以下の typo 起因のバグが検出されて、before_action が実行されていないアクションがあったことが判明したケースがあります。

-before_action :do_something, only: %i(edit confirm updae)
+before_action :do_something, only: %i(edit confirm update)

静的解析で見つけられれば良いですが、そのような検出機構がない場合にスペルチェッカーの方で検出できたという興味深い (?) ケースでした。

スペルチェッカーの良いところは、個人の開発環境から導入を開始することができる点にもあります。その先のアドバンスドとしては、Rails が GitHub Actions で行っているように CI での typo 検出をしておくと、リポジトリの master (main) ブランチに含まれる前に typo 検出をすることができるのでチーム開発としても便利です。

github.com

そのほかにも Git Hooks に引っ掛けたりと、コマンドラインツールということで自動化に向けたいろいろな応用が効くでしょう。

今回取り上げたスペルチェッカーは英文に対するチェックになりますが、仮にお仕事の開発が日本語でのコミュニケーションだとしても、プログラムの変数名やメソッド名などは英単語主体になります。実プロジェクトの開発に組み込むことで、きっと typo を減らせるきっかけになるでしょう。

今日はここまでです。では、Happy Hacking!


「情報化技術を通じて社会と共生する」株式会社永和システムマネジメント アジャイル事業部では、エンジニアを絶賛募集しています。応募エントリお待ちしております!

agile.esm.co.jp

『エクストリームプログラミング実践者の集い』イベントをオンライン開催します

XP祭り 2021』に登壇したアジャイル事業部メンバーのリアルタイム再演イベントとして、『エクストリームプログラミング実践者の集い』を開催します。

2021年10月25日(月) から 10月29日(金) までの 5夜連続 19:00-20:00 の時間帯で、メンバーが日替わりで登壇します。各日程の講演終了後には、登壇者に直接質問ができる QA 時間を設ける予定です。 最終日 10月29日(金) は本イベント独自の初演登壇があります。

全日通しでのフル参加、あるいは気になる日程のみの参加でも歓迎です。ふるってご参加ください!

申し込み方法

Doorkeeper から申し込みをお願いします。チケット (無料) は5日間通しになっています (気になる日程のみの参加でも歓迎です) 。

esminc.doorkeeper.jp

スケジュール

  • 第1夜: 10/25(月) 19:00-20:00 @koic 『ソフトウェア見積りの公式』
  • 第2夜: 10/26(火) 19:00-20:00 @color_box 『とあるアジャイルプロジェクト108日の記録』/ @kasumi8pon 『チームによい提案をするには』
  • 第3夜: 10/27(水) 19:00-20:00 @fkino 『チームの劇的瞬間 〜航空、医療、類人猿、そして XP に学ぶ〜』
  • 第4夜: 10/28(木) 19:00-20:00 @junk0612 『ソフトウェア開発におけるコミュニケーション』
  • 第5夜: 10/29(金) 19:00-20:00 @m_pixy 『価値創造契約10周年(とそこから得られるアジャイルな受託開発についての理解)』

f:id:koic:20211005125823j:plain

参加にあたって

Zoom ビデオ会議を使用しますので、Zoom が利用できるようにご準備ください。


株式会社永和システムマネジメントでは、Ruby とアジャイルソフトウェア開発を通じてコミュニティと共生しながら成長したいエンジニアを絶賛募集しています。

agile.esm.co.jp

XP祭り 2021 に登壇した弊社メンバーのスライドまとめ

2021年9月18日(土) にオンライン開催した XP祭り 2021 の弊社メンバーのスライドまとめです。

xpjug.connpass.com

@hiranabe 『自分のしたい、から、みんなのしたい、へ。 (Appreciative Inquiry behind Extreme Programming)』

www.slideshare.net

@haru01 『XP祭りの中でドメイン駆動設計の勉強会!!』

ワークショップのためスライドではなくブログとなります。

twop.agile.esm.co.jp

@fkino 『チームの劇的瞬間 〜航空、医療、類人猿、そして XP に学ぶ〜』

@color_box 『とあるアジャイルプロジェクト108日の記録』

@junk0612 『ソフトウェア開発におけるコミュニケーション』

@koic 『ソフトウェア見積りの公式』

@kasumi8pon 『チームによい提案をするには』

@okajima_yukio 『対話と創発 ~ アジャイルなマーケティングチームの1年間』

www2.slideshare.net

さらに、惜しくも聞き逃した人のためのイベントがあるかもしれません。続報にご期待ください。


株式会社永和システムマネジメントでは、Ruby とアジャイルソフトウェア開発を通じてコミュニティと共生しながら成長したいエンジニアを絶賛募集しています。

agile.esm.co.jp

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

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

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

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

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

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

idobata.io

特に募集ページなど設けませんが、上記理由からすでに Idobata のアカウントを持っている方は、当日の案内を Idobata にてご確認ください。 また Idobata はクローズ化されているため Idobata アカウントを持っていない参加希望の方は、@koic までメンションしてください。

パッチ会では、来月開催の Kaigi on Rails 2021 に関する話題などあるかもしれません。

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

Reboot Rails/OSS meetup online · GitHub

Railsでポリモーフィック関連を使った話(理由、必要な作業、注意点)

こんにちは、アジャイル事業部 9sako6 です。

私のいるプロジェクトで大きなエンハンスが行われ、その中で Polymorphic Association(ポリモーフィック関連) を使う場面がありました。 ポリモーフィック関連を選択した理由や行った作業、注意点について話します。

架空のサービスを例に説明を行います。

サービス概要

私たちが運営するサービスでは、Magician(魔法使い)と、その魔法使いが扱える Magic(魔法)を調べることができます。 この世界の Magic(魔法)に同一のものはなく、 ユニークです。Magic(魔法)はMagician(魔法使い)に属しています。

class Magic < ApplicationRecord
  belongs_to :magician
end

class Magician < ApplicationRecord
  has_many :magics
end

ところが、つい半年ほど前でしょうか、魔法を扱える動物 MagicAnimal(魔法動物)の存在が確認されました。 私たちは、魔法動物もサービスに登録できるようエンハンスを行いました。

私たちは、Magic(魔法)を Magician(魔法使い)と MagicAnimal(魔法動物)に従属させることにしました。ポリモーフィック関連付けを行ってできたモデルは以下です。

class Magic < ApplicationRecord
  belongs_to :magicable, polymorphic: true
end

class Magician < ApplicationRecord
  has_many :magics, as: :magicable
end

class MagicAnimal < ApplicationRecord
  has_many :magics, as: :magicable
end

これを実現するために以下のマイグレーションを行いました。

class AddMagicablePolymorphicColumnsToMagics < ActiveRecord::Migration[6.0]
  def up
    # `magician_id` -> `magicable_id`
    rename_column :magics, :magician_id, :magicable_id
    # Magician か MagicAnimal かを区別するカラム。
    # Magician なら 'Magician'、MagicAnimal なら 'MagicAnimal' を値としてもつ。
    add_column :magics, :magicable_type, :string

    # 既存レコードの `magicable_type` に値を入れる。
    Magic.reset_column_information
    Magic.update_all(magicable_type: 'Magician')

    change_column_null :magics, :magicable_type, false

    # `magicable_id` と `magicable_type` の複合インデックスを追加。
    add_index :magics, [:magicable_id, :magicable_type], options: :online
  end

  def down
    remove_index :magics, column: [:magicable_id, :magicable_type]

    remove_column :magics, :magicable_type
    rename_column :magics, :magicable_id, :magician_id
  end
end

ポリモーフィック関連を選択した理由

STI や delegated types、テーブル追加ではなくモリモーフィック関連にした理由を説明します。

既存への影響を小さくしたい

私たちが開発するサービスは Rails 4 の時代からスタートし、何年もの歴史を持っています。コードベースもデータ量も多く、なるべく修正コストが小さくなるようにしたいという気持ちがありました。

魔法を一覧表示したい

魔法使いと魔法動物のどちらに属するかを区別せず、魔法を一覧表示したい場面がありました。 そこではページネーションを Solr + Sunspot で行っており、1つのモデル、1つのテーブルの方が扱いやすいという前提があります。 したがって、Magic(魔法)を複数モデル、複数テーブルにすると処理が複雑になることが予想されました。

私たちのプロジェクトでは、既存影響なども考慮に入れると Magic(魔法)は1つのモデルにしておくメリットが大きく、STI や別テーブル追加にはしないことにしました。

Rails 6.1 から入った delegated type も一瞬検討候補に上がりましたが、私たちの Rails は 6.0.x だったので使えませんでした。

過去に開発ブログで delegated type について解説を行っているので、詳しく知りたい方はご覧ください。

blog.agile.esm.co.jp

DRY

Magic(魔法)は、Magician(魔法使い)から見ても MagicAnimal(魔法動物)から見ても本質的には同じものです。 同じものは1つのモデルで表すのが筋が良いと考えました。

Magic(魔法)に関しては DRY になるので、後々はメンテナンスが楽になると考えました。 もし新規モデルを追加し、魔法についてのコードが別れている状態になっていたら、片方に機能追加漏れやバグ修正漏れがないよう気を張りつめる必要があります。 開発メンバーは入れ替わっていくものですし、レビューのたびに気をつけなければならない観点は増やしたくありません。

Rails だから

ポリモーフィック関連を使用すると、外部キー制約による参照整合性が失われます。

しかし、Rails が提供する方法に則れば、アプリケーションのロジックがある程度リスクを低減してくれます。 今後 Rails から離れる予定もないため、ポリモーフィック関連を採用しました。

既存コードへの影響

ポリモーフィック関連付けにあたって必要だった修正について述べます。

なお、現在のところコードを自動で修正してくれる魔法は開発されていません。

joins, eager_load できない

ポリモーフィック関連にすると、以下のように joinseager_load できなくなります。

Magic.joins(:magicable)

# => Cannot eagerly load the polymorphic association :magicable (ActiveRecord::EagerLoadPolymorphicError)

クエリを工夫し、パフォーマンスに影響がないよう joinseager_load を使わないように修正する必要があります。

magician から magicable への書き換え

ポリモーフィック関連付けしたことにより、魔法テーブルは magician_id ではなく magicable_id を持つようになりました。

create_table "magics" do |t|
-   t.integer "magician_id", precision: 38, null: false
+   t.integer "magicable_id", precision: 38, null: false
+   t.string "magicable_type", null: false
end

従来 magician としていた箇所は、必要に応じて magicable に書き換えます。

- Magician.find(@magic.magician_id)
+ Magician.find(@magic.magicable_id)

- Magic.where(magician_id: current_magician.id)
+ Magic.where(magicable_id: current_magician.id, magicable_type: 'Magician')

- magician = magic.magician
+ magician = magic.magicable

Magician(魔法使い)と MagicAnimal(魔法動物)で同じ id をとりうる場合、magicable_type を漏れなく指定する必要があります。

こういった変更は、バッチ処理用のスクリプトや管理画面 (RailsAdmin 製)にも及びました。

Solr インデックス

プロジェクトでは、全文検索に Solr が使われています。 先ほどと似た話で、もともと magician_id を渡していた箇所を magicable_id に修正する必要があります。

integer :magician_id, as: 'Index.magician_id' do
-   magic.magician_id
+   magic.magicable_id
end

しかし、MagicAnimal(魔法動物)も magicable_id で関連付いているので、上記の修正により検索対象が変わってしまいます。 ここでは Magician(魔法使い)だけを対象としたいです。

他の手として、インデックス名 Index.magician_idIndex.magicable_id に変更し、Index.magicable_type インデックスを追加する方法があります。綺麗な姿に見えますが、実現するためには既存レコードの再インデックスが必要です。 再インデックス処理には長い時間がかかるため、現実的ではありませんでした。1

そこで、magicable_type で分岐して Solr に渡す値を変えることにしました。

integer :magician_id, as: 'Index.magician_id' do
-   magic.magician_id
+   magic.magicable_id if magic.magicable_type == 'Magician'
end

上記のように magicable_type で分岐させることで、既存のインデックス済みのレコードに影響を与えずに Solr を使うことができました。

バリデーションの場合分け

Magician(魔法使い)が使う魔法には、必ず rank(階級)が定められています。Magic(魔法)はこの rank カラムを持っていますが、MagicAnimal(魔法動物)の扱う魔法には階級がありません。

Magician(魔法使い)がもつ Magic(魔法)のときだけ rank にバリデーションをかけたいです。

enumerize :rank, in: MAGIC_RANKS, predicates: true
- validates :rank, presence: true
+ validates :rank, presence: true, if -> { magicable_type == 'Magician' }

このように片方に必要で片方に必要ないカラムがあるなら、バリデーションを分ける必要があります。

そして、Magician(魔法使い)と MagicAnimal(魔法動物)のための別々のカラムが増えるほど、Magic(魔法)のバリデーションは複雑になっていきます。 実際に、開発途中で魔法動物に関する法律が制定されたことで要求に変化があり、 MagicAnimal(魔法動物)にだけ必要なカラムが増えてしまいました。 結果、バリデーションは結構複雑なことになりました。これは仕方がないので受け入れています。

View での分岐

ある場面では、magicable が Magician か MagicAnimal か判定するために分岐が増えてしまうかもしれません。

例えば、Magician#employed? は、魔法使いが雇われているかそうでないかを返す predicate method です。魔法動物には雇用の概念がないので、employed? は実装されていません。

これまで、雇われ魔法使いなら雇用企業の情報を、非雇われ魔法使いなら卒業学校の情報を表示していました。 しかし、魔法動物は働かないし学校に行ったことがないので、魔法使いにだけ employed? を呼び出す必要があります。

- - if @magic.magician.employed?
-   = render 'company', magician: @magic.magician
- - else
-   = render 'school', magician: @magic.magician
+ - if @magic.magicable_type == 'Magician'
+   - if @magic.magicable.employed?
+     = render 'company', magician: @magic.magicable
+   - else
+     = render 'school', magician: @magic.magicable

リリース後の話

リリースして様子を見ていたところ、パフォーマンスリグレッションが起きました。 インデックスの貼り方に問題があったのです。

magicable_type の漏れや、複合インデックスの順番ミスによってインデックスが効いていませんでした。 すぐに修正し、その日のうちに問題は解決されました。

- t.index ["magicable_id", "name"], ...
+ t.index ["magicable_id", "magicable_type", "name"], ...
- t.index ["magicable_type", "magicable_id"], ...
+ t.index ["magicable_id", "magicable_type"], ...

それ以外は大きな問題なく、リリース後Nヶ月月経った今でもきちんと動いています。

さいごに

本記事では、ポリモーフィック関連を採用するに至った理由や、ポリモーフィック関連付けにするための修正について書きました。 もし同様の問題解決に迫られた時、本記事で述べた観点が技術選択のヒントになると幸いです。

最後に、永和システムマネジメント アジャイル事業部では Web アプリケーションを開発する仲間を募集しています。 Ruby や Rails に馴染みがあったり、会社に興味をもっていただけた方はぜひご応募ください!

agile.esm.co.jp


  1. 再インデックスする場合は、バッチ処理で行います。対象件数を絞って再インデックスする処理を繰り返し、何日もかけて処理が完了します。今回はリリースタイミングの都合などで採用できない案でした。

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

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

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

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

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

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

idobata.io

特に募集ページなど設けませんが、上記理由からすでに Idobata のアカウントを持っている方は、当日の案内を Idobata にてご確認ください。 また Idobata はクローズ化されているため Idobata アカウントを持っていない参加希望の方は、@koic までメンションしてください。

パッチ会では、来月開催の RubyKaigi Takeout 2021 に関する話題などあるかもしれません。

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

Reboot Rails/OSS meetup online · GitHub