ホワイトヘルスケア - テックブログ

ホワイトヘルスケアは、日本のヘルスケア領域における社会課題に正面から向き合い、現実世界を生きる人々の不安や痛みといった見過ごされるべきでない問題に対して本質的な解決に取り組むことで、持続的な医療システム・社会の実現を目指します。

sidekiqをバージョンアップしたらsidekiq-statisticがエラーになったが解消した話

概要

  • ruby 3.1 系 から 3.2 系 にバージョンアップしたら、sidekiq-statisticでエラーが発生するようになった。
  • sidekiqのバージョンアップに伴い、sidekiq-statisticが期待する I/F が変わったため、エラーが発生した。

動作環境

  • ruby 3.1 系から 3.2 系にバージョンアップ
    • sidekiq6 系から 7 系にバージョンアップ
  • sidekiq-statistic導入済み

はじめに

ホワイトヘルスケア エンジニアの林です。

本記事は、 ruby 3.1 系 から 3.2 系 にバージョンアップした際、sidekiq-statisticがエラーが発生した時の話になります。

発生した現象と調査について

事の始まり

弊社は Ruby on Rails を使ったアプリを開発しており、sidekiqを使用した job で処理を行なうロジックがあります。また、開発補助のためsidekiqの統計情報をグラフィカルに確認できるsidekiq-statisticという gem も導入しています。

アプリは ruby 3.1 系を使用して開発していましたが、脆弱性対応もあり、ruby 3.2 系にバージョンアップすることになりました。

それに伴い、使用していた gem のバージョンアップも同時に実施しました。

発生した現象

sidekiqの job 実行時にエラーが発生し job が失敗するようになりました。sidekiqの UI 上から確認できた エラーログは以下でした。

NoMethodError: undefined method `value` for 300:Integer Processor (以下略

調査

エラーの原因はバージョンアップではあるのは確かでしたが、どこで何が起こりエラーになったのかわからなかったため、順番にエラーの調査を行っていきました。

1. バージョンアップ前後での rspec の実行状況

弊社は rspec でテストを書くことになっており、今回エラーとなったsidekiqの job が実行している class に対してもテストが存在していました。

そこで、まず rspec がバージョンアップ前後で 失敗しないか確認しました。 バージョンアップ直後にも全体を通して実行し、エラーが発生していないことは確認はしていましたが、ランダム値による突発的な発生なども考えられるため、エラーが起こった job を何度か実行してみました。しかし、rspec 上では再現しませんでした。

2. job の内容の確認

次に、job の内容について調査しました。

job の挙動をざっくりと説明すると、DB のテーブルコピーを行う、というものです。エラー発生時の環境と rspec 実行時の環境とでは、DB 内のデータの作り方が異なる(rspec では FactoryBot を使ってダミーデータを投入する)ため、そこで差が出たのかと考えました。前述の観点でコードを確認しましたが、特にデータの作り方で何か変わるようなロジックはありませんでした。

また、rails のコンソール(rails c)上で job を直接実行したところ、エラーが発生しなかったため、 job ではなく別のところに原因があると予測しました。

エラーの原因の特定

前述の調査の他にも、google で検索をかけたり等行っていましたが、原因の特定に至らず手詰まり状態になっていました。

そこで、エラーメッセージ前後のログの内容を確認することにしました。前述したエラーログの前後に、以下のログも出力されていました。これは、「hmsetが deprecated である」旨のメッセージであり、エラーになっていたわけではありませんが、このログを元にsidekiq-statistic-1.4.0/lib/sidekiq/statistic/middleware.rbを確認してみることにしました。

worker  | [sidekiq#5788] Redis has deprecated the `hmset`command, called at ["/usr/local/bundle/gems/sidekiq-statistic-1.4.0/lib/sidekiq/statistic/middleware.rb:42:in `block (2 levels) in save_entry_for_worker'"]

sidekiq-statistic-1.4.0/lib/sidekiq/statistic/middleware.rbを調べたところ、deprecated に該当しているコードが確認できました。それとは別に、問題の発端となったエラーに出てきたvalue method を使っているコードも発見しました。

参考リンク:https://github.com/davydovanton/sidekiq-statistic/blob/v1.4.0/lib/sidekiq/statistic/middleware.rb

コード抜粋

if times_list_length.value > max_timelist_length
  redis.ltrim(timeslist_key, 0, (max_timelist_length * 0.75).to_i)
end

このコードに対しログを仕込んでみたところ、times_list_length.valueでエラーになっていることがわかり、エラーの発生源を突き止めることができました。

根本原因

根本的な原因は、sidekiqのバージョンアップにより、内部で使用している redis のクライアントがredisからredis-clientに変わり、I/F も変わってしまったためでした。そのため、sidekiq-statisticがその変更に対応できていなかったため、エラーが発生してしまったということです。

本体コードを簡素化した以下のコードを実行しても クラスや I/F が変わっていることが確認できます。

Sidekiq.redis do |redis|
  r = nil
  redis.pipelined do
    r = redis.lpush "timeslist_key", 0.12132123
  end
  p r.class
  p r
end

# sidekiq v6 の場合
# => Redis::Future
# => <Redis::Future [:lpush, "timeslist_key", 0.12132123]>

# sidekiq v7 の場合
# => Integer
# => 1

対応策

対応方法としてはいくつかあると思いますが、他 gem との兼ね合いなども考慮した結果、該当 method をclass_evalを使って上書きすることで対応することとしました。こうすることで、他の gem への影響を最小限に抑えて対応することができました。

まとめ / 感想

今回調査した中で、色々と学ぶ点が多くあったと感じました。具体的には以下の通りです。

  • 原因となったエラーメッセージ前後のメッセージも、エラー特定に役立つ
    • エラーの原因が特定できたのは偶然性が高いと感じている
  • 想定していないところでアップデートの影響が出ることもある
    • アプリの挙動と直接関係ない gem で発生した
    • rspecAPI サーバとして確認する方法では捕捉できなかった
  • gem 管理リポジトリhttps://rubygems.org/だが、そこに最新バージョンが登録されているわけではない

採用情報

Ruby on Rails を使った Whitehealthcare に興味のある方はこちらから https://whitehealthcare.co.jp/recruit/

ActiveRecordのerrorsにaddしてもsaveに成功するとaddした内容がなくなる

概要

  • ActiveRecordを継承したmodelには、errorsで任意にエラーを追加できる
  • 追加したエラーは、savevalidateに成功すると、errorsから削除される

はじめに

ホワイトヘルスケア エンジニアの林です。

本記事は、ActiveRecord を使っていた際に、errorsに追加したエラーが意図せず消えてしまった時の挙動を調べた際の話になります。

発生した現象と調査について

事の始まり

1 対多の関係のテーブルに対し、ある機能を作成していた時の話です。

テーブルをざっくり例えると以下のようなイメージのものでした。

  • userが複数のarticlesを所有している
  • articlesは、公開/非公開のstatus、記事のtext、記事の文字数text_countをフィールドとして持つ
  • userは、公開記事の全文字列の合計数であるpublished_text_countをフィールドとして持つ

図として表現するとこのような感じ。

erDiagram
users ||--|{ articles: ""

users {
  integer published_text_count
}

articles {
  enum status
  string text
  integer text_count
}

コードで model を表現するとこのような感じ。

class User < ApplicationRecord
  has_many :articles
end

class Article < ApplicationRecord
  belongs_to :store_invoice
  enum :status, {
        DRAFT: 'draft',             # 下書き
        PUBLISH: 'publish',         # 公開
  }
end

発生した問題

作成していた機能は、以下のようなものでした。

  1. userを指定し、articlesを更新する
    • articleDRAFTだった場合、PUBLISHに変更する
    • articlePUBLISHだった場合、変更できなかったArticle.idのリストをエラーとして表示する
  2. articlePUBLISHであるtextの合計値であるpublished_text_countを更新する

上記をdraft_to_publishとして以下のように実装しました。

class User < ApplicationRecord
  has_many :articles

  def draft_to_publish
    # 元々PUBLISHだったものは、idをエラーとして保持
    errors.add(:base, articles.PUBLISH.ids)

    # DRAFTはPUBLISHに変更
    articles.DRAFT.update(status: :PUBLISH)

    # `published_text_count`の更新
    published_text_count = articles.PUBLISH.sum(:text_count)

    # フィールド値の更新
    save
  end
end


# 想定していた使い方

# 対象のユーザでmethodをcall
user = User.first
user.draft_to_publish

# PUBLISHのarticlesがあれば、このraiseでcall側に通知することを期待してた
raise StandardError(user.errors.full_messages) if user.errors.empty?

しかし、PUBLISHarticlesが存在するuserであっても、エラーがraiseされることがなかったため、調査することにしました。

原因の調査

結論から言うと、save(正確にはvalid?)でerrorsがクリアされている、ということがわかりました。

該当箇所:https://github.com/rails/rails/blob/v7.1.2/activemodel/lib/active_model/validations.rb#L363-L369

明示された文章を探し出せなかったのですが、rails の思想としては、「あるタイミングでエラーが発生しても、その後の処理で保存できる(検証 OK になった)なら、値の修正などでエラーが解消されたとみなす」ということなのだろうと理解しました。

まとめ / 感想

一般的な(?)使い方を考えると、「新規に validation に成功したら errors がクリアされる、は確かにそうだな〜」と思いました。が、反面、自分の認識していないところでデータが書き換わるのは想定しておらずだいぶ困惑しました。

Framework を使う場合、どういった使い方を想定されて作られているのか、という点を意識して使う必要があることを再認識させられる出来事でした。

採用情報

Ruby on Railsアプリ開発をしているホワイトヘルスケアが気になった方は、ぜひお気軽にお問い合わせください!

採用情報詳細

hrmos.co

ActiveRecordのenumにvalidateオプションが追加されてて嬉しい

こんにちは、エンジニアの菅野です。

今回はRailsの最近の変更で地味に嬉しかった enumのvalidateオプションについて書いていきます。

概要

Rails7.1からActiveRecordenumvalidate オプションが指定できるようになりました。

PR

github.com

これにより validate: true が指定された場合、 個別にvalidationを書かずとも対象外の値が入力された際にバリデーションエラーとして扱ってくれるようになりました 🎉

実践

例えばこんなテーブルがあったとします。

# migration
class CreateCountries < ActiveRecord::Migration[7.1]
    def change
        create_table :countries do |t|
            t.string :name, null: false
            t.string :code, limit: 2, null: false
            t.integer :continent, limit: 1, null: false
            t.datetime :created_at, null: false
        end
    end
end

モデルクラスは以下で、まだ validate オプションはつけていません。

class Country < ApplicationRecord
    enum :continent, {
        asia: 'Asia',
        africa: 'Africa',
        europe: 'Europe',
        north_america: 'NorthAmerica',
        south_america: 'SouthAmerica',
        australia_oceania: 'Australia/Oceania'
    }

    validates :name, presence: true
    validates :code, presence: true
end

まずはサンプルとして日本のレコードをつくります。

[1] pry(main)> japan = Country.create(name: 'Japan', code: 'JP', continent: :asia)
  TRANSACTION (0.4ms)  BEGIN
  Country Create (2.4ms)  INSERT INTO `countries` (`name`, `code`, `continent`, `created_at`) VALUES ('Japan', 'JP', 'Asia', '2023-11-02 02:22:45.486031')
  TRANSACTION (3.1ms)  COMMIT
=> #<Country:0x0000ffff954d74d0
 id: 1,
 name: "Japan",
 code: "JP",
 continent: "asia",
 created_at: Thu, 02 Nov 2023 02:22:45.486031000 UTC +00:00>
[2] pry(main)> japan.continent
=> "asia"

続いて continent に範囲外の値を指定します。 試しに日本を南極大陸に移動させてみましょう。

[3] pry(main)> japan.continent = :antarctica
ArgumentError: 'antarctica' is not a valid continent

値を代入した維持点で ArgumentError が発生してしまいました。

今度はモデルクラスのenum定義で validate オプションを有効にしてみます。

enum :continent, {
    asia: 'Asia',
    africa: 'Africa',
    europe: 'Europe',
    north_america: 'NorthAmerica',
    south_america: 'SouthAmerica',
    australia_oceania: 'Australia/Oceania'
}, validate: true

改めて範囲外の値を代入します。しかし今回ArgumentErrorは発生しません。

[2] pry(main)> japan = Country.last
  Country Load (3.0ms)  SELECT `countries`.* FROM `countries` ORDER BY `countries`.`id` DESC LIMIT 1
=> #<Country:0x0000ffff8e9ba788
 id: 1,
 name: "Japan",
 code: "JP",
 continent: "asia",
 created_at: Thu, 02 Nov 2023 02:22:45.486031000 UTC +00:00>
[3] pry(main)> japan.continent = :antarctica
=> :antarctica

続いてモデルのバリデーションをかけたところで、初めてバリデーションエラーが発生するようになりました。

[3] pry(main)> japan.continent = :antarctica
=> :antarctica
[4] pry(main)> japan.valid?
=> false
[5] pry(main)> japan.errors.messages
=> {:continent=>["is not included in the list"]}
[6] pry(main)> 

代入時点で無条件に例外がスローされることもなくなり、他フィールドと同じようにActiveRecordのエラーとして扱えるようになりましたので、地味に嬉しい変更ではないかと思います。

利用バージョン

ruby '3.2.2'
rails '7.1.1'

まとめ

これまで個別に validates :xxx, in: { inclusion: %w[bla bla bla] } などと記述していたenum値のバリデーションが、7.1からoption一発で宣言できるようになりました。嬉しいですね。

BigQueryでGROUPING SETSが実装されたので早速試してみた

概要

  • BigQueryがGROUPING SETSをサポートし始めました
  • GROUPING SETSの基本的な使用方法と、それがデータ分析にどのように役立つかを実際のクエリ例とともに紹介します

目次

  • はじめに
  • GROUPING SETSとは何か?
  • GROUPING SETSの使い方
  • ROLLUPとCUBEとの関係
  • まとめ

はじめに

こんにちは、データエンジニアの石田です。今月の初め頃にBigQueryにGROUPING SETSの機能がリリースされました。一般的なSQL環境では既にお馴染みの機能ですが、BigQueryでは長らくサポートされていませんでした。いままでもUNIONやUNNESTを駆使すれば同様の集計は可能でしたが、GROUPING SETSを使うことで簡素に書けるようになりましたので、実際の使用例を通じて使い方を紹介します。 cloud.google.com

GROUPING SETSとは何か?

GROUPING SETSは、GROUP BY句の一部で、複数のグループ化基準を一度のクエリで指定できる機能です。簡単に言えば、一つのクエリで複数のGROUP BYを実行する機能だと思ってください。

異なるレベルの集計を同時に行いたい場合に役立ちます。とてもシンプルに書けるので冗長性が減少し、実行効率が向上します。

GROUPING SETSの使い方

それではGROUPING SETSの使用例を見てみましょう。

我々はホワイトヘルスケアでは、医療データの分析をメインで行っています。ここでは、hospital(病院)、patient(患者)、point(点数、患者の医療費のこと)の3つのフィールドを持つ架空の医療データセットを使用します。

hospital patient point
ヘルスケア病院 石田さん 200
ヘルスケア病院 山田さん 250
ヘルスケア病院 鈴木さん 550
ホワイトクリニック 石田さん 100
ホワイトクリニック 山田さん 200
ホワイトクリニック 鈴木さん 450

この点数を「病院ごと」、「患者ごと」、そして「病院と患者の組み合わせごと」に集計したいとします。普通に集計するなら、集計したいレベルごと3つの別々のクエリでGROUP BYを実行します。しかし、GROUPING SETSを使用すると、これを一度のクエリで実行できます。

SELECT 
  hospital, 
  patient, 
  SUM(point) as total_points
FROM 
  medical_data
GROUP BY 
  GROUPING SETS (
    (hospital),
    (patient),
    (hospital, patient)
  );

このクエリは、次の3つの集計を一度に行います:

  1. 病院ごとの点数合計
  2. 患者ごとの点数合計
  3. 病院と患者の組み合わせごとの点数合計

結果セットは以下のようになります。

hospital patient total_points
ヘルスケア病院 NULL 1000
ホワイトクリニック NULL 750
NULL 石田さん 300
NULL 山田さん 450
NULL 鈴木さん 1000
ヘルスケア病院 石田さん 200
ヘルスケア病院 山田さん 250
ヘルスケア病院 鈴木さん 550
ホワイトクリニック 石田さん 100
ホワイトクリニック 山田さん 200
ホワイトクリニック 鈴木さん 450

いくつかの行にhospitalまたはpatientがNULLとして表示されていますが、これはその行が特定のレベルの集計(病院全体または患者全体)を表しているためです。例えば、patientがNULLの場合、その行は病院全体の点数を示しています。

GROUPING SETSを使わずにこれを集計しようとするなら、hospitalGROUP BYしたレコード、patientGROUP BYしたレコード、hospital, patientGROUP BYしたレコードを個別に集計した後、それらをUNIONしなくてはなりません。

ROLLUPとCUBEとの関係

GROUPING SETSROLLUPCUBEにも対応しており、これらと組み合わせるとさらに複雑な集計を1つのクエリで実現できます。それぞれ使い方を見てみましょう。

ROLLUP

ROLLUPは特定の階層での集約データ(サブトータル)と、すべてのレベルの集計(グランドトータル)を簡単に取得できる機能です。GROUPING SETSと組み合わせると、さまざまなレベルの集計を一度のクエリで取得できます。

以下は、hospital(病院)とdepartment(診療科)の架空のデータセットです。

hospital patient point
ヘルスケア病院 循環器科 350
ヘルスケア病院 神経科 650
ホワイトクリニック 循環器科 400
ホワイトクリニック 神経科 350

例えば、病院内の各診療科で行われた診療の点数の合計を計算し、さらにその病院全体での合計も計算したい場合、ROLLUPが役に立ちます。

SELECT 
  hospital, 
  department, 
  SUM(point) as total_points
FROM 
  medical_data
GROUP BY 
  GROUPING SETS (
    ROLLUP (hospital, department)
  );

このクエリは、以下の集計を行います。

  1. 各病院と診療科部門の組み合わせごとの点数合計(最も詳細なレベル)
  2. 各病院全体の点数合計(診療科をロールアップした結果)
  3. 全病院の点数総合計(全てをロールアップした結果)

結果セットは以下のようになります。

hospital department total_points
ヘルスケア病院 循環器科 350
ヘルスケア病院 神経科 650
ヘルスケア病院 null 1000
ホワイトクリニック 循環器科 400
ホワイトクリニック 神経科 350
ホワイトクリニック null 750
null null 1750

NULLはロールアップされた集計を示しています。たとえば、departmentがNULLの場合、その行は病院全体の小計を示しています。表の一番下、hospitaldepartmentの両方がNULLのレコードは、全病院の総合計です。

各レベルは、前のレベルのデータを「ロールアップ」して、より集約された値を集計しています。表の下に行けば行くほど、全体に近くなるイメージでしょうか。上の例はカラムが少ないのでピンとこないかもしれませんが、これに患者、地域、疾患、担当医、etc...と様々な階層が加わったときも、このクエリで一撃で全ての粒度の集計が行えるため、詳細からより広い視点の洞察を一度に得ることができます。

CUBE

CUBEROLLUPと似ていますが、CUBEの方が提供される集計の種類がより多いです。CUBEは、指定された列のすべての可能な組み合わせにわたって集計を行います。

実行すると、データを複数の次元で集計し、各階層のサブトータルと、全階層のグランドトータルを含む、クロス集計(ピボットテーブルのような)結果を得ることができます。

具体的な例を使いながら説明します。以下は、hospital(病院)、department(診療科)、doctor(医者)という3つの階層があるセットです。

hospital department doctor point
ヘルスケア病院 循環器科 山本 150
ヘルスケア病院 循環器科 佐藤 200
ヘルスケア病院 神経科 山本 300
ヘルスケア病院 神経科 佐藤 350
ホワイトクリニック 循環器科 山本 250
ホワイトクリニック 循環器科 佐藤 150
ホワイトクリニック 神経科 山本 200
ホワイトクリニック 神経科 佐藤 150

CUBEを使用すると、これらのすべての可能な組み合わせ(病院、診療科、医者、病院+診療科、病院+医者、診療科+医者、病院+診療科+医者、など)にわたってデータを集計できます。

以下が、GROUPING SETSCUBEを使用したクエリです。

SELECT 
  hospital, 
  department, 
  doctor,
  SUM(point) as total_points
FROM 
  medical_data
GROUP BY 
  GROUPING SETS (
    CUBE (hospital, department, doctor)
  );

以下がクエリの実行結果になります。

hospital department doctor total_points
ヘルスケア病院 循環器科 山本先生 150
ヘルスケア病院 循環器科 佐藤先生 200
ヘルスケア病院 循環器科 NULL 350
ヘルスケア病院 神経科 山本先生 300
ヘルスケア病院 神経科 佐藤先生 350
ヘルスケア病院 神経科 NULL 650
ヘルスケア病院 NULL 山本先生 450
ヘルスケア病院 NULL 佐藤先生 550
ヘルスケア病院 NULL NULL 1000
ホワイトクリニック 循環器科 山本先生 250
ホワイトクリニック 循環器科 佐藤先生 150
ホワイトクリニック 循環器科 NULL 400
ホワイトクリニック 神経科 山本先生 200
ホワイトクリニック 神経科 佐藤先生 150
ホワイトクリニック 神経科 NULL 350
ホワイトクリニック NULL 山本先生 450
ホワイトクリニック NULL 佐藤先生 300
ホワイトクリニック NULL NULL 750
NULL 循環器科 山本先生 400
NULL 循環器科 佐藤先生 350
NULL 循環器科 NULL 750
NULL 神経科 山本先生 500
NULL 神経科 佐藤先生 500
NULL 神経科 NULL 1000
NULL NULL 山本先生 900
NULL NULL 佐藤先生 850
NULL NULL NULL 1750

NULLを多数含むので一見とっつき難い印象があるかもしれませんが、今までの一連の出力例と同様だと思えば、それほど違和感はないでしょう。

簡潔なクエリで様々な角度からのインサイトを得ることができる一面、すべての可能な組み合わせを含むため計算量が多く、中には分析に必要ではない組み合わせも含まれるため、少し冗長かもしれませんね。

まとめ

GROUPING SETSをうまく使いこなせば、グループ集計のダルさを一発で解消できます。レポートにするまでもないけど、なんとなくデータ全体のイメージをカジュアルに把握したいなんて時に最適です。 ただし、やはり予想外に計算量が多くなる可能性があるので、必要なデータだけをしっかりと指定して、スマートにクエリを実行しましょう!

採用情報

ホワイトヘルスケアと医療データの分析に興味がある方は以下をクリック。

hrmos.co

気軽にヘルプを出せるSlack チャンネルを運用してコミュニケーションを促進している話

概要

技術的なことで困った時に、雑にヘルプを出せる場を作るのはいいぞ!

はじめに

ホワイトヘルスケア エンジニアの林です。

本記事は、弊社が導入している Slack の運用についての紹介になります。

弊社勤務形態について

弊社では、エンジニア、非エンジニア問わず従業員の多くがリモート勤務しています。例えばエンジニアの話であれば、関東圏に在住している方が多数ですが、九州在住の方もいらっしゃいます。

またエンジニアはほぼフルリモートで勤務しており、勤務の開始時間や終了時間もまちまちです(私事ですが、子育て真っ最中なので大変ありがたい制度です)。

勤務を行う上で感じる課題

上記のような勤務形態なので、個人的には働きやすい環境ではありますが、課題も感じています。その 1 つがコミュニケーションのハードルです。

弊社ではコミュニケーションツールとして Slack を導入しており、エンジニア間(従業員間)でのコミュニケーションの心理的ハードルは低い環境であると思っています。

しかしながら、技術的に行き詰まりヘルプを出したい時などで、以下の様な心理的ハードルもあると感じていました。

  • 汎用的な問題を、どこで話題にすれば良いかわからない
    • DM でするのも違うし、プロジェクト用のチャンネルでするのも違うし...
  • 専門的な問題を、誰に聞けばわからない
    • この分野の話、誰が一番知っているのか(知ってそうなのか)わからない...
  • そもそもポストしにくい
    • フルリモートで業務開始時間がまちまちだし、各人色々 MTG 入ってるみたいだし、見られているのかわからない...

解決策

上記のような心理的ハードルを下げる施策として、弊社では「困った時に雑にヘルプを出せるチャンネル」を作成しました(余談ですが、エンジニアだけではなく、非エンジニアも同じ様な使い方をするチャンネルを作成しています)。 このチャンネルでは、エンジニアが全員参加しており、困りごとや質問など気軽に投げられる様になっています。

このチャンネルの導入は、前述した課題の解決の一助となっていると感じています。

  • 汎用的な問題を、どこで話題にすれば良いかわからない
    → とりあえず困ったらこのチャンネルに投げれば OK 。なのでどこで話題にするのかを悩む必要なし
  • 専門的な問題を、誰に聞けばわからない
    → エンジニア全員がチャンネルに参加しているため、得意分野関係なくエンジニア同士で議論が進み解決に繋がる可能性が高まる
  • そもそもポストしにくい
    → 良くも悪くもレスを期待するチャンネルではない位置付けのため、タイミングや内容に関わらずポストできる

またそれだけでなく、そのほかにも以下の様な良いところもあると感じています。

  • 知識の幅が増える
    → 問題の当事者でなくても、話題に上がった問題を知ることで、関連する情報として認識や知識の幅が増える
  • エンジニア同士のコミュニケーションの促進
    →「技術」という共通の話題から、プロジェクト関わらずエンジニア間でコミュニケーションが発生する

まとめ

弊社では Slack の運用として、技術的なことを気軽にポストできるチャンネルを作成しています。フルリモートという働きであっても、エンジニア間でのコミュニケーションのハードルを下げることができていると感じています。

採用情報

弊社はまだまだ Slack の運用や開発環境がリッチとはいえないため、今後もより良くなるように改善や続けて行きます。この様な開発環境である Whitehealthcare に興味が出た方はこちらからご確認下さい。

https://hrmos.co/pages/whitehealthcare

【入社エントリー】育児とキャリアの両立を実現するために

はじめに

はじめまして、開発チームの岩本です。

2023年5月に業務委託から関わらせてもらい、2023年8月から正社員としてチームメンバーとなりました。 現在は熊本県在住で、夫と3歳と1歳の男の子との4人家族です。

育児とキャリアをどうやって両立しているのか、どういう工夫をしているの?といった部分を含め入社エントリーとして書かせていただきます。

転職を考えたきっかけ

前職では、自社サービスや受託開発の会社に勤めていました。 また、医療事務や医師事務作業補助で診療所に勤めていたこともあり、エンジニアとしても医療業界に関わっていければいいなと思ったのがきっかけです。

入社したきっかけや入社を決めた理由

転職先を探していたときに重視していたこと

以下の2点を軸に転職先を探していました。

  1. 医療業界に関連する仕事であること
  2. 働き方が柔軟で、育児と両立可能な環境があること(例:フルリモート)

入社したきっかけ

転職サイトで色々と情報収集をしていたところ、エージェント経由で人事の方からメッセージをいただきました。 その後、求人内容が自分の希望に沿っていると感じたので、カジュアル面談したのがきっかけです。

入社を決めた理由

事業・サービスで「一人ひとりが健康に向けて行動する社会の早期実現」という目標に、なるほど!そういう視点は面白いし今後絶対に必要になってくるだろうなと思い入社を決意しました。

入社前の不安とその後

入社前はいくつかの不安がありました。

  1. 子どもが突然発熱し、数日の休暇が必要になる可能性
  2. 本社が東京で、直接出社が難しい状況下でのリモートワークによるコミュニケーション(前職では本社が福岡で、緊急時には新幹線でアクセス可能だった)

これらの不安は、実際に入社してみると意外とスムーズに解消されました。

まず、入社初日から有給休暇が15日取得できるという点です。 入社すぐに取得するのは気が引けると感じましたが、人事の方から必要なときは休んでくださいと声をかけていただきとても助かりました。

次に、コミュニケーションに関してですが、開発チームの雰囲気のセクションでも記載していますが、テキストコミュニケーション能力が非常に高い方が多く進捗に大きく影響する問題は起きませんでした。

以上のように、入社前に感じていた不安は実際には会社の文化や制度によってほぼ解消され、より安心して仕事に取り組むことができました。

入社後の仕事内容

健康保険組合向けの「セルフメディケーション支援」ECサイトの運用・保守、機能改善などを担当しています。 最近はRailsでの開発が多めです(笑)

開発チームの雰囲気

コミュニケーションスタイル

私たちの開発チームにはフルリモートで働いているメンバーもいるため、コミュニケーションは基本的にテキストベースです。 しかし、メンバーそれぞれのテキストコミュニケーション能力が非常に高いため、多くの課題や問題点はオンライン上でスムーズに解決しています。

ペアプログラミングと認識合わせ

テキストだけでは足りない時はペアプログラミングやビデオ会議を利用して、認識齟齬がないようにしています。

オープンなフィードバック

また、進捗のMTGで「困っていることを気軽に共有できる場が欲しい」と提案したところ、その意見がすぐに取り入れてくれました。 結果として、個々のメンバーが気軽に質問や疑問を投稿できる、times風のチャンネルが作成されました。 これにより、チーム内での情報共有がさらにスムーズになっています。

育児と仕事の両立について

両立するための工夫

うまく進んだとき(理想)の1日のスケジュールはこんな感じです。

1日のスケジュール(画像作成元:tools.loumo.jp さま)

保育園のお迎えなど、家庭の用事を優先したい時間帯は会社のカレンダー(Outlook)にスケジュール登録してMTGが入らないようにさせてもらっています。 夕食に関しては、昼休憩に作り置きできるようレシピと食材がセットになったミールキットを利用して時短できるところは工夫しています。

…体力が尽きた場合は、趣味開発の時間は睡眠時間へ変わります(笑)

共働き夫婦の協力体制

私の夫もフロントエンドエンジニアであり、在宅勤務をしていることから、家庭内での協力体制は工夫しています。 Google カレンダーでお互いの仕事スケジュールを共有し、家族専用の Slack ワークスペースで日常業務や家事に関する情報交換を行っています。

また具体的な役割として、夫は主に保育園の送迎(朝)、洗濯、ゴミの回収といったタスクを担当してもらっています。 このような明確なロール分担とコミュニケーションの高度化により、共働きでも業務と家庭の両立がスムーズに行えているのでキャリアを諦めずにフルタイムで勤務できているのかなと思います。

まとめ

カジュアル面談したときから、イメージが変わることがなくとても働きやすい環境だと感じています!

地方在住でフルリモート勤務ができるというのは、キャリアも諦めることなく働けるのでとても助かっています。 (夫の家族のサポートもフル活用できますし…笑!!)

まだまだ入社したばかりですが、私自身の技術もしかりプロダクトに関しても会社と共に成長していきたいと考えています。

採用情報

子育てしながらも働きやすい環境となっています! まだまだこれからの部分は一緒に作っていきましょう!! 少しでも気になった方は、ぜひお気軽にお問い合わせください!

採用情報詳細

hrmos.co

ActiveRecordにおけるscopeとclass methodの使い分けに対する考察

概要

  • ActiveRecord には、class method と同じような挙動をする scopeが定義できる
  • class methodscope を使い分ける方針として下記に統一すると良いと考える
    • 戻り値としてActiveRecord::Relationを期待する場合は scope
    • 戻り値としてActiveRecord::Relationを期待しない場合は class method

はじめに

ホワイトヘルスケア エンジニアの林です。

本記事は、Rails 初学者が scopeclass method の使い分けに悩み、最終的にどのように使い分けるか考察した内容を記載しています。

scope とは

公式より scope の説明を抜粋します。

よく使うクエリをスコープに設定すると、関連オブジェクトやモデルへのメソッド呼び出しとして参照できるようになります。スコープでは、where、joins、includes など、これまでに登場したメソッドをすべて使えます。どのスコープメソッドも、常に ActiveRecord::Relation オブジェクトを返します。

railsguides.jp

私はホワイトヘルスケアに join するまで Django を扱っていましたが、この scope という概念は Django にありません。また、プロジェクトに途中から join したこともあり、コード内に定義されていた scopeclass method を見て、ふわっとした理解から入ったため、使い分けに悩みました。

使い分けに悩んだ点は、scopeclass method で同じ挙動を定義できる点です。 例えば、以下の scopeclass method は同じ挙動をします。

class Article < ApplicationRecord
  # 公式ページに挙げられているscope例
  scope :published, -> { where(published: true) }

  # 上記scopeと同じ挙動をするclass method
  def self.method_published
    where(published: true)
  end
end

Ruby には for 文のように、同じ挙動を異なる表現で実装できるということもあり、使い分ける必要があるのか非常に悩みました。

scope の位置付けと class method の使い分け考察

使い分けを考えた際、Rails における scope の役割や位置付けは何かを調べました。結論として、位置付けに関しては、全てこの一文に集約されると考えています。

どのスコープメソッドも、常に ActiveRecord::Relation オブジェクトを返します。

また、scope という単語が表す「範囲」という意味からも、レコードの範囲を絞るために使う(= filter として使う= ActiveRecord::Relation オブジェクトを返す)ことが想定されている役割であると考えました。

(余談)個人的には、この一文は Rails における scope の位置付けを示しており、コーディング規約(もっというと Rails の思想)に関わるため、もっと強い言葉にした方が良いと思っています。

では、scope はレコードの範囲を絞るために使う、とした場合に、class method はどのように扱うべきか? 結論としては、ActiveRecord::Relation オブジェクトを返さない場合は全て class method にする、という結論に至りました。

上記を実装方針とすることで、scopeclass method の役割が明確かつ分離されるため、レビュー時やリーディング時の認知負荷軽減にも役立つと考えています。(私が認識していなかっただけで、そのような実装方針は定まっていました)。

まとめ

scopeclass method の使い分けをまとめると以下になります。

  • 戻り値としてActiveRecord::Relationを期待する場合は scope
  • 戻り値としてActiveRecord::Relationを期待しない場合は class method
    • ex.1. ActiveRecord::Relationの値を変更する(戻り値は成功/失敗を表す bool)
    • ex.2. 特定条件に合わせたレコードを作成する(戻り値は作成したレコード)

このような実装方針とすることで、認知負荷を下げ統一のある実装になると考えています。

採用情報

Whitehealthcare に興味が出てきた方はこちらからチェックをどうぞ

https://hrmos.co/pages/whitehealthcare