メンタルゴリラの開発ブログ

ジュニアレベルのエンジニアがノーマルレベルになるための過程を残していくブログです。

nullのある特定期間のバリデーション

仕事でマスク機能を開発することになり、その仕様で大変頭を使ったので備忘録として残しておく。

要件

  • マスクを作成する際、公開/終了時間をそれぞれ設定できる
  • 終了期間はnull可
  • 公開時間 < 終了時間
  • 既に設定されている公開期間と重複して設定できない

具体例

マスク①:2021/01/01 10:00~12:00

マスク②:2021/01/02 17:00~

以上2件のマスクが既に設定されていたとする。

ここで新たに

マスク③:2021/01/01 11:00~13:00

を作成しようとすると①の期間と重複する期間があるためバリデーションエラーで作成不可。

また、

マスク④:2021/01/02 18:00~20:00

も、②が17時以降ずっと設定されていることになるためバリデーションエラーで作成不可。

マスク⑤:2021/01/01/09:00~07:00

を作成することはもちろんできない。(どんな世界線だ)

実装

この上記バリデーションの要件をどう実装するか、大変頭を使った。

実際に実装しようとすると分かると思うが、これが本当に難しかった…。

紙に書きまくって「これはダメ」「それもダメ」といろいろ案を出していったがなかなか要件を満たせるコードが出てこない。

一旦単純に実装しようとし、SQLを分けて書いてみたがまぁ汚いクソコードができあがった。(それも残しておけば良かったな)

しかしこのクソコードがいい仕事をした。

ここにリファクタリングを加えることでなんと1行で実装することができた。

それがこちら。(実際のコードは他に条件が追加されているため期間部分のみ抽出)

self.class
    .where('end_at >= :start_at OR end_at IS NULL', start_at: start_at)
    .where('start_at <= :end_at OR :end_at IS NULL', end_at: end_at)

できあがったとき「私は天才だ…」と自分に惚れ惚れした。

これは紛うことなき天才だろう。

実際先輩に「スマート!」とお褒めの言葉をいただいた。

やっぱり天才だった。

やってみるといいかもしれないこと

今回の実装で紙に時間軸を書いてOK/NGな期間を可視化するようにしてみた。

この可視化が割と自分の頭の整理に繋がって助かった。

これからも複雑な要件があったときは紙に書き出してみることにしようと思う。

おわり。

【Jest】ケバブケースのとき、emitの指定どうすんだっけ?

testしたいmethods

methods: {
  submit(): void {
    this.$emit('submit-post', this.body)
  }
}

普通なら

expect(wrapper.emitted.submit[0])

とかで指定できる。 でもケバブケースの場合どうやってemitのテストしたらいいの?

answer

describe('submit', () => {
  it('親コンポーネントにデータが渡されること', () => {
    const wrapper = shallowMount(Target)
    wrapper.setData({
      body: {
        text: 'a'
      },
    })
    wrapper.vm.submit()
    expect(wrapper.emitted("submit-post")[0][0]).toEqual({ "text": "a"})
  })
})

emitの中で文字列で渡せば良いらしい。解決。

返り値がnilになり得るならscopeは使うな

アイキャッチ

事の発端

scope :showing, ->(now = Time.current) {
  where('start_at <= ?', now).where('end_at > ? OR end_at IS NULL', now)
}

のような日時指定してデータ取得するみたいなscopeを書いた。

このとき一致するものはないため結果は[]を期待していたがなぜか全件取得されていた。

不思議に思いscopeを使用せず直接クエリを発行すると期待する結果に。

なぜ???

原因

発行されたSQLを見合わせるも一言一句違わない…

なぜだ…と思いよく見たらscope指定した際のSQLの下に

Hoge Load (1.0ms)  SELECT `hoges`.* FROM `hoges`

全件取得のSQLが発行されているではないか。

scopeの挙動

scopeはActiveRecord::Relationを返す前提で設計されているらしく、結果がnilの場合全件取得される仕様になっている。

解決策

返り値がnilになり得るならおとなしくメソッド化しよう。

def self.showing
  now = Time.current
  where('start_at <= ?', now).where('end_at > ? OR end_at IS NULL', now)
end

転職して2ヶ月経ちました(半年経ってました)

こんばんは。

先週整体に行って「これは…面白い身体してますね」と苦笑されました、うつです。

肩こりが酷すぎて行ったんですが、そこで肩が45度までしか上がってないことが発覚して「ははっ」と乾いた笑いが出ました。

骨盤が歪んでたみたいで、そこ治すだけで肩が上がりました。すごい。

今週また行ってきます。

さて、ご存知の方が多いですがタイトルの通り、4月に転職しました。

転職した理由はいくつかありますが省きます。

特にネガティブな理由はないです。

今回は転職して2ヶ月経ったということで、前職との違いや今やっていることなどをつらつら述べていきたいと思います。

…というところまで書いて放置してました。

気づいたら転職して半年が過ぎてました。

上記のお話はまた別の機会にして…

ちょうど先日評価面談があったんですが、そこで「アウトプットもっとやろうね」という話になったので今日から開発ブログとして運用していこうと思います。

とは言ってもまだまだジュニアレベル。

大層な記事は書けませんのでご容赦を。

息抜きしたくなったときに雑談でも挟もうかと思います。

あとついでなので名前もうつからJimmyへチェンジしちゃいます。

今後ともうつ改めJimmyをよろしくお願いします。