patorashのブログ

方向性はまだない

リファクタリングデーでキャッシュ化に取り組んだ話

第三回リファクタリングデーを開催したのだけれど、これがすごくよかったのでまとめておく。

お題はキャッシュ化

後輩氏とパートナーさんは、まだキャッシュにすることの経験がなかったので、過去にそれぞれが実装したところ等でキャッシュ化できそうなところを探してみよう、という感じにした。まぁ目に付けば別に他のところでもいい。

なぜキャッシュ化するのか?

この点から説明しておいた。

Webアプリケーションの開発はDBへのアクセス回数を減らすことが大事っていうのはだいぶ浸透していて、メンバー全員が気を付けて開発できるようになってきた。しかし、そもそもアクセスしなくてもいいようにするのが理想である。つまりはコンテンツの変更がなければ、以前に作ったデータを再利用すればよい。これがキャッシュ化。

キャッシュ化のメリット

キャッシュ化が効くと何が嬉しいか?

  • アプリケーションサーバーの負荷が減る
  • データベースサーバーの負荷が減る
  • ユーザーへのレスポンスが早くなり体験がよくなる

まさにいいこと尽くめ。

キャッシュ化のデメリット

デメリットも、なくはない。

  • データが更新されているにも関わらず、変更が画面上に反映されない等の事故が起こりうる
    • キャッシュの寿命の設計が必要に
  • 本来見えてはいけないデータが見えてしまう等の事故が起こりうる
    • キャッシュの適用範囲の設計が重要に

キャッシュキーに何を持たせるか?

上記のデメリットは基本的に全て事故となりうることばかりなので、そうならないようにするには?をまず教えた。

  • ユーザー個人に紐づくデータをキャッシュする場合は、キャッシュキーに個人を特定できる情報を付加する(ユーザーIDなど)
  • ログイン状態に紐づくデータをキャッシュする場合は、キャッシュキーにログイン状態を特定できる情報を付加する(Deviseでいえば、user_signed_in?の結果等)
  • オブジェクトをキーとして指定すれば、オブジェクトに変更があれば過去のキャッシュは無視されるので有効
  • ActiveRecord::Relationの状態のオブジェクトをキーにしても意味がないので注意(クエリ発行してないから)
  • 何かしらのオブジェクト、配列、Hash等にしておくとキーにしやすい
  • データ更新系のコールバックでキャッシュを削除することを忘れないこと

何をキャッシュするか?

View

基本的にはレンダリング後のViewをキャッシュしておくといいが、会員サイト等ではページキャッシュはやりづらい。ユーザーに依存する情報をAjaxで取ってくるようにでも実装していない限りは。その実装も面倒なので、基本的にはフラグメントキャッシュを使う。

- cache(post) do
  h1 = post.title
  = simple_format post.content

計算結果など

計算結果というと仰々しいが、例えばカウント数とか。毎回カウントを取得してもいいが、何気にカウントするためのクエリは遅い。

user_count = Rails.cache.fetch(:user_count) { User.count }

更に、これをフラグメントキャッシュで使う。

- cache(:"user_count_#{user_count}") do
  p = "今までに#{number_with_delimiter(user_count)}人が活用しているノウハウ"

キャッシュの廃棄もやると…

class User < ApplicationRecord

  after_commit :delete_cache

  private

    def delete_cache
      Rails.cache.delete(:user_count)
    end
end

初期表示データ

例えば、検索などで使う場合であっても、デフォルトで設定されている検索条件の結果等はキャッシュ化しておくとよい。どうせ毎回実行されるからだ。

マスタデータ

マスタデータは、なかなか更新されにくいので、キャッシュのし甲斐がある。

ユーザーに関連するデータ

ユーザーに関連するデータは頻繁に参照するのでキャッシュ化すると速くなりやすい。その分、キャッシュの寿命と適用範囲には気を付けなければならない。

多少ずれていても問題ないデータ

常に最新のデータが表示されていなくてもいいような場合は、キャッシュの寿命を1分とかにしておくと、1分間はDBへのアクセスも減らせるし、1分後には最新のデータになるのでサイトとしても動きがあるように見せられる。ランキングとかで使えるだろうか。

実際にやってみた

以上のようなことを、ざっと説明した後に作業に取り掛かってもらった。実際にSQLの発行回数が減っていることをrack-miniprofilerなどで確認すること!と伝えた。

メンバーはあまりキャッシュを使うことを意識したことがなかったようなので、目に見えて速くなることは楽しかったようであった。

成果

リファクタリングデーの成果としては、主にマスタデータのキャッシュ化や、初期表示データのキャッシュ化をやってもらった。私自身はユーザーに関連するデータをキャッシュ化したりした(閲覧履歴などのデータ)。頻繁に参照する部分でキャッシュを活用できるようにしたのはかなり良かったと思う。

また、これ以降のバックログに取り組む際に、新たな機能においてもキャッシュ化できる箇所は対応してからPRを送ってくれたので、これが最も大きな成果であったと思う。

メンバーが順調に育ってきていると感じられた。

最近取り組んできたことと新たな課題について

4月のまとめ的な感じになるけれど。

予備知識

現在の開発体制が、私がリーダー、後輩氏、パートナーさんの3名体制である。

  • 私はRails歴9年で今のプロジェクトを最初から担当している。40歳とはいえ新米リーダーである。
  • 後輩氏は4月より新卒入社3年目に入ったくらいで、経験はまだ浅いしプロジェクト歴は半年過ぎたくらい。
  • パートナーさんは今年2月くらいから開発に参加。他言語の経験はあったがRubyRailsは初めて。

前はこういうことに取り組んでいた。

patorash.hatenablog.com

現在

チケットの粒度を私側で調整して、それを割り振るようにしていたおかげで、チームメンバーが何をしたらいいかについてはハマりにくくなったので、その点は良かった。事前に仕様を考えて実装前にチェックするので、妙な手戻りも起きにくい。そのため、実装速度も上がってきた。これはいい成果である。

そして、最近のことでいうと、よく言われる

  • 緊急で重要な仕事
  • 緊急ではないが重要な仕事
  • 緊急で重要ではない仕事
  • 緊急でも重要でもない仕事

についての話をして、端から見たら「やらないとなー」とは思うけれど急ぎではないからと放置され続けてしまうようなチケット(つまり緊急ではないが重要な仕事)についても、仕事の具合を見つつ、徐々に取り組んでください。と伝え、最近はそれをやってもらえるようにもなってきた。これもいい成果である。まぁ具体的には、

  • 仕様変更が起きているためにバージョンを固定化しているgemのバージョンアップ
  • Rubyのバージョンアップ
  • Railsのバージョンアップ
  • JSライブラリのバージョンアップ
  • ミドルウェアのバージョンアップ
  • 修正範囲は小さいけれど大量にやらなければならないリファクタリング

というところ。

また、PR*1をチェックする場合は私がレビューする前に後輩氏とパートナー氏の間で先に見てもらうというルールにしていて、それが通ったら私がチェックしてマージする、というふうにしている。

まぁやってほしいところをしてもらえるようになって、成果も出ているので、チームとしては成長していると思う。

ただちょっと、自分の中で色々と課題が出てきている。

課題

コードレビューで一日が終わる

さきほど上げた、修正範囲は小さいけれど大量にやらなければならないリファクタリングに取り組んでもらっている。まぁでも課題自体は二人に任せており、配分もそれぞれで話し合って担当範囲を決めてくださいってことにもしていて、子チケットは二人が作ってくれているので、その点も非常に助かっている…のだが、修正範囲が小さいであるが故に修正もすぐに終わり、PRがバンバン飛んでくる。

そんなPRなので、レビュー後の指摘の修正もそこそこすぐに終わり、再レビューが来る。再レビューを終えてマージする頃には次のPRが来る…という感じで、気づいたら自分のタスクがほぼ進まないまま、1日が終わっている。PRとPRの合間に自分のことをやるのでスイッチングコストもかかる。

作業者にとっては、ストーリーポイントが1のタスクをバンバンこなしている感じになるので、リズムに乗れるし、仕事やってる感もでてくると思う(単純なことなので退屈だなーと思いながら、かもしれないところはある)。しかしレビュワーにとってはキューがどんどん溜まっていくのでキツイ。レビューが遅くなると同じミスを繰り返しているPRが飛んできたりもするので、早めにレスポンスしたほうがいいなとも思って優先度を上げてレビューした結果、ミスも減って良かった…のだが更にPRが来る速度が上がる。

やってほしいと言っているのも私自身なので、「これは仕方ないのか…!?」と思う反面、レビューで終わる日が続くとちょっと精神的に、くる。

解決法?

ちょうどこの前、上司である id:tech-kazuhisa との1on1があったので、その話をしたところ、よいディスカッションができた。

  • スプリントプランニングでそれらのタスクの数を調整しておく
  • 後輩氏とパートナーさんでお互いにレビューしあってマージできるレベルまで育てる

前者は、そもそもうちのチームは「なんちゃってスクラム」みたいになっていて、スプリントプランニングなどを殆どしないで目に付いたバックログを倒していくような運用をしているので、そろそろちゃんとスプリントプランニングしたほうがいいかもなぁという気持ちになった。そこである程度小さいPRの流量を調整しておけば、確かに作業時間を捻出しつつ、PRも取り込んでいけそう。

後者は、まだ難しい…。これはモブ設計ならぬモブレビューみたいなことをしたほうがいいのかな?とか、RubyRailsの本の読書会などをしてRubyらしい視点でのレビューができるように培っていくことが必要かなとも思うので、週に1時間でも何かしらの読書会をしていくべきか…という気持ちになった。 ただ、レビュー時に気を付ける点は明文化できそうである。PRテンプレートに私なりの気を付けていること等を書いておくというのがいいかなと考えたので、GW開けたらそれをやっておきたい。

*1:プルリクエストのこと

gem auditedでrails consoleやrake taskの時にデフォルトユーザーを設定する

auditedというgemを使って、監査ログを保存するようにしたいと思い、現在調査中。

github.com

大体はよさそうなので、採用したいと思っているのだけれど、困ったのが、rails consoleとかでデータ変更されたとき。データ変更の履歴は残るものの、誰がやったかがわからない。

流石にそれだと万が一rails console経由で変な処理してしまったら困るので、誰かは分からずともrails console経由であることくらいはわかるようにしたいということで、設定してみました。

rails consoleであることをどうやって検知するか?

これが全然わからなかったのですが、stackoverflowにありました。

stackoverflow.com

これによると、defined?(::Rails::Server)でいけるとあります。

実際にやってみると…。

bin/rails c
Loading development environment (Rails 6.0.3.6)

[1] pry(main)> defined?(::Rails::Server)
=> nil

なるほど、consoleだと::Rails::Serverの定義を読み込まないということですね。

ちなみにRailsの初期化プロセスはRailsガイドにあるので、こちらも読むと参考になります。最初はこれを読んでどこかで処理を差し込むところはないか?を探ってました。

railsguides.jp

auditedのinitializerを定義する

config/initializers/audited.rbを作ります。

unless defined?(::Rails::Server)
  Audited.store[:audited_user] = "console"
end

ということで、これでめでたし、めでたし…とはなりません😢

rake taskのときにも::Rails::Serverは読みこまれないので、rake taskでもusernameが入ってしまいました。

純粋にrails consoleであることを検知するにはどうしたらいいのか…。

rake taskであることを検知する

これもまたstackoverflowでヒットしました。

stackoverflow.com

Rake.application.top_level_tasks.empty?をすることで、現在の処理がrake task経由であることがわかるようです。

ということで、config/initializers/audited.rbを修正。

unless defined?(::Rails::Server)
  if Rake.application.top_level_tasks.empty?
    Audited.store[:audited_user] = "console"
  else
    Audited.store[:audited_user] = "rake-task"
  end
end

これで、今のところ問題なさそう!👍

と思ったけれど、この記事を書いている最中に、rails runnerのことを考慮できてないやんって気付いた…。ダメだ…😵

Railsの設定で可能だった

ここにきて、神速さんから素晴らしい情報をもらった。

これを設定したら簡単、かつ、わかりやすかった。ダメ元でrunner doも付けてみたらrails runnerの時の設定もうまくいった(ぇ。

しかし、rake taskのときの検出方法がRailsの設定ではわからなかったので、さっきのやつを使うことに。

Rails.application.configure do
  console do
    Audited.store[:audited_user] = "console"
  end

  runner do
    Audited.store[:audited_user] = "runner"
  end
end

unless defined?(::Rails::Server)
  if Rake.application.top_level_tasks.present?
    Audited.store[:audited_user] = "rake-task"
  end
end

これでバッチリでした!👍

[3] pry(main)> hoge = Hoge.find(1)
[4] pry(main)> hoge.name
=> "あああ"
[5] pry(main)> hoge.update(name: "アアア")
[6] pry(main)> Audited::Audit.last
  Audited::Audit Load (1.3ms)  SELECT "audits".* FROM "audits" ORDER BY "audits"."id" DESC LIMIT 1
=> #<Audited::Audit:0x000055b996469a88
 id: 380,
 auditable_id: 1,
 auditable_type: "Hoge",
 associated_id: nil,
 associated_type: nil,
 user_id: nil,
 user_type: nil,
 username: "console", # <= ここが重要!!
 action: "update",
 audited_changes: {"name"=>["あああ", "アアア"]},
 version: 1,
 comment: nil,
 remote_address: nil,
 request_uuid: "4f057afe-2edc-4614-88fd-0a17a7b3d93d",
 created_at: Wed, 14 Apr 2021 18:19:19 JST +09:00>

第31回中国地方DB勉強会で発表しました(録画で)

先週ですが、中国地方DB勉強会で発表してきました。

dbstudychugoku.connpass.com

開催日が平日の夜ということもあり、私自身はリアルタイムで参加できなかったのですが、発表は前からお願いされていたので、なんとか動画を録って主催者のid:ikkitang1211 に送っておいて、録画で発表させてもらいました。

あとでハッシュタグ #chugokudb を追ってみたら、結構コメントをもらえていたのでよかったです。

発表内容について

発表内容は、ここ1ヶ月くらいでやっていたテーブル・カラムにコメントをつけることにした経緯とかその手法とかについて、でした。

まぁ大体のことは、前にブログで書いてますが、それをまとめ直した形です。

patorash.hatenablog.com

録画するときの苦労

新型コロナの影響で、録画発表をすることもまだまだあるかもしれないので、録画したときの苦労とかをまとめておきます。

Chromebookの画面録画は不具合がある

2021年3月に大型アップデートがあって、Chromebookで画面のスクリーンショットや録画ができるようになりました。

support.google.com

スライドをGoogle Slideで作っていた私は、「これは全部Googleで済ませられるのではないか?」と考え、Chromebookで試しに短い時間の録画を試してみたところ、問題なし、と判断。そこで、発表資料を読みながら録画していきました。

録画後に、再生してみると、問題なさそうに見えるのですが、徐々に違和感が…。表示しているページと声が徐々にずれていくのです。最終的に、まだ読み上げているにも関わらず、「ご清聴ありがとうございました」の文字が…。どうも長丁場の録画だといけてないようです。しかし、作成された動画のファイルサイズは18分の動画にも関わらず14MBとめちゃくちゃ軽かったので、このズレが解消されるようだったら使っていけたらいいと思います。

MacQuickTimeで録り直し

仕方ないのでMacQuickTimeを使って取り直したのですが、こちらは問題なし👍実際に勉強会で流してもらったのもこちらです。ただし、動画のファイルサイズは400MBくらいになってました。

とはいえ、安心感があるので、今後はQuickTimeで録ることにするかと思います。

花粉症で喉がやられていた

苦労したのは、花粉症で喉がやられていて、話していると咳が出やすくなること…。最近私は龍角散ダイレクトがこれに効くことを知ったので、Chromebookで動画を撮る前に飲んでました。おかげで、咳をほぼすることなく録れたのですが、さっき書いた通りで使える代物ではなく…😢

Macで録り直そうとしたのですが、龍角散の効果が切れて咳が出始めてしまい…。ちなみに龍角散ダイレクトは一度飲んだら2時間はあけるように、と注意書きがあるので、もう一度飲んで録り直すには1時間ちょい待たなければなりませんでした。この時点で午前1時前。

とりあえず時間が過ぎるのを待って、午前2時になってから再び龍角散ダイレクトを飲んで録り直しました。効果が切れる前に録り終わることができてよかったです。

この時期、発表予定がある方は手元に持っておくといいかもしれません。薬局で売ってます。

【第3類医薬品】龍角散ダイレクトスティックミント 16包

【第3類医薬品】龍角散ダイレクトスティックミント 16包

  • 発売日: 2008/10/03
  • メディア: ヘルスケア&ケア用品

SQLiteのLIKE演算子はデフォルトでESCAPE文字が設定されていない

私はimyouというニックネーム管理用gemを公開しているのですが、開発時にPostgreSQLを使っていました。 しかし、RailsのデフォルトのデータベースはSQLiteなので、SQLiteで開発したほうがよかろうと考え、SQLiteに変更してテストを実行したところ、なんと落ちてしまいました。

落ちた原因の調査

テストで実行されているSQLが変わるのか?と思い、to_sqlを挟んで調査してみましたが、SQL文はほぼ同じ。PostgreSQLの場合はILIKEになるくらい。

落ちていたテストは、LIKE演算子にアンダーバーを含んだ文字列でした。sanitize_sql_likeメソッドを使っているから、_もしっかりエスケープされているから問題ないはず…。と思っていたのですが、どうもSQLiteでは、明示的にESCAPE文字列を指定しないといけないようでした。PostgreSQLでも明示的に指定はできますが、デフォルトは\(バックスラッシュ)です。

明示的に指定していないため、エスケープされた文字列はfoo\_barのようになっているものの、バックスラッシュと任意の一文字と合致する条件となってしまっていました。

対処

sanitize_sql_likeメソッドの第二引数はエスケープ文字の指定なので、'\\'を指定するように修正しました。 こうすることで、WHERE name LIKE '%foo\_bar%' ESCAPE '\'というSQLに変わり、SQLiteでも問題なくエスケープされるようになりました。

まとめ

SQLite_を含むパターンマッチを行う場合はエスケープ文字をちゃんと設定しましょう。

なお、imyouは、この問題を修正したバージョン 1.4.2 をリリース済みです。

Rubyで要素数が異なる二次元配列で行と列を入れ替える

最近のPRのレビューをしているときに、二次元配列のデータを入れ替える処理をしているところがあったので、レビュー後にチャットで意見交換していたら綺麗な感じにできたのでそれをメモして残しておく。

transposeを使うのがミソなのだけれど、transposeを使うには、配列の要素数を統一しなければならない。そのために、values_atを使う。このvalues_atを使うアイデアはパートナーさんが考えてくれた。

docs.ruby-lang.org

docs.ruby-lang.org

# 要素数が異なる配列を準備…
arr = [['a', 'b'], ['c', 'd', 'e', 'f'], [], ['g', 'h', 'i']]
# その中から最大要素数を取得
max_size = arr.map(&:size).max
# 各配列の要素数を最大要素数に合わせる
arr.map! {|it| it.values_at(0...max_size) }
# 行と列を入れ替える
arr.transpose
# => [
# ["a", "c", nil, "g"], 
# ["b", "d", nil, "h"],
# [nil, "e", nil, "i"],
# [nil, "f", nil, nil]]

最初はzipを使おうとしていたのだが、zipを使う場合は先頭要素に最も大きな配列がなければうまくいかないということを指摘してもらって、別のアイデアを考えていたら、こうなった。

縦持ちと横持ちのデータの入れ替えは時々したくなることがあるので、いざと言う時にこの記事を思い出したい。

情報格差を減らす取り組みの話

今期に入ってメンバーが私1人から新人(2年目)とパートナーさんが追加されて3名体制になったので、情報格差をなくすための活動に勤しんでいる。

以前にいたメンバーはそれなりに最初からRailsに詳しかったりしていたので、そこまで情報を整理しなくても勝手に学んでくれていたが、それも今思ってみるとある種の属人性と、私と年代が近く、同じ場所で働いているからお互いにサクッと聞ける、という環境メリットがあったからだなと思う。

しかし現代は令和となり、新型コロナの影響でリモートワークを余儀なくされ、メンバーの年齢は干支一周かそれ以上くらい年の離れているので、サクッと聞きづらいところもあろうかと思う。ましてや私は今や髭面坊主なのだ。

まぁ開発に取り掛かる上での情報はある程度は整備していたし、docker-composeでサクッと開発自体は開始できるようになっているし、参加しているうちにわかってくるかもなぁ〜と思って放置していた部分もあって、👨‍🦲「どちらかというと技術力の底上げを頑張らないといかんだろう」と当初は思っていた。しかし、これやっぱり順序が違うかな?と思うようになってきた。

プロジェクトに対する情報格差を減らしたい

プロジェクトの概要とか、機能説明についてはプロジェクトに参加してもらう時点で一通りしているのだが、やはりコードを理解するには仕様の理解やライブラリの知識が必要となる。作る時点では、gemを導入で色々と検討したものがgitのログにあるし、DB設計もそれなりに行ってannotateでドキュメントを残したつもり…となっているが、やはり途中から参加したメンバーにとっては情報がいろんなところに散らばっていてわかりにくいだろうし、そもそもgitのログから探そう、という発想にもなりにくいだろう。

そこで、情報格差を減らす方策を行うことにした。

DBのカラムにコメントを追加した

これは最近取り組んでいたことでブログにもまとめてある。まぁ具体的にどういうことをしたかはそちらを見てもらえばいい。

patorash.hatenablog.com

patorash.hatenablog.com

patorash.hatenablog.com

patorash.hatenablog.com

patorash.hatenablog.com

RailsMVCアーキテクチャフレームワークなので、コメントを追加してannotateで出力するだけでもわりかしModelに関してのドキュメントは充実するのではないか?と思う。特にカラム名だけ見てもぱっと見で意味がわからないやつとか、enum使っていて数値の表す意味がわからないものとか。

リファクタリングデーを設定した

これは2回ほど行ったのだが、よい取組になったと思っている。最終的にはメンバー全員にプロジェクトの全体を把握してもらいたいわけなので、よく使われている機能から優先的に、実際に動かしてもらい仕様を把握してもらいつつ、コードを読んでもらってリファクタリングデー当日までにチケットを作ってもらっておいた。そして、当日はその中から取り掛かれそうなものに取り組んでもらう。あくまでもリファクタリングのため、仕様が変わるような修正は受け付けないけれど、アイデアはチケットとして残して検討後に対応することにしている。

1つの機能について、メンバー全員が同時に取り組むため、確認もしやすいし質問もしやすい。状況によってはペアプロに移行することもできる。いつもより、コミュニケーションが活発になったかなと思う。

実際のところ、大きなリファクタリングをするのは難しかったのだが、細かい使い勝手や、キャッシュできる箇所の選定や処理の共通化程度は行うことができた。

これは今後も定期的に行っていこうと考えている。

意識してチケットの粒度を下げた

これは1on1をしたときにアドバイスを貰ったこと。もともとは個々の裁量に任せたいという気持ちが強かったのだが、そもそもまだ個々で判断して動けるレベルに到達していない場合は、こちら側で粒度をコントロールして、より小さく、より具体的な情報を与えて導いていく。そうすることで、情報格差は減り、何をしていいかわからないということは減るのでメンバーは動きやすくなる…のだが、チケット管理の手間がかなり増えるので私の負担が増えることになる。が、今後成長してくれば、この投資コストに見合うようになる筈!と思って当面頑張って粒度を小さめにしていく。

Gemfileにコメントを付けた

ライブラリの概要を知らないと、同じような機能を持つgemを知らず知らずに追加してしまうことがあったり…。また、今後削除していきたいと考えていたgemを新規開発に使われてしまったりすることもあり得る。

これはやってみて思ったことだが、自分の中でも何のために入れたかうろ覚えになっていたgemがいくつかあったので、自分の中でも整理することができてよかった。調べる過程で、もうメンテナンスが放棄されているgemも見つかった…💀あとバージョンを固定したままになっているものがあったりとか。なるべくバージョン固定は外していきたいのだが、テストが落ちたりして固定したまま放置していたものなどがあった。リファクタリングでgemを削除していくときにもヒントになるので、やってよかった。

どんな感じで書いたか、一部を抜粋する。

source 'https://rubygems.org'

ruby '2.6.6'

gem 'rails', '6.0.3.5'
gem 'pg', '~> 1.2.3'
# ActiveRecordでPostGISを扱うために追加
gem 'activerecord-postgis-adapter'
# 簡潔な記述で綺麗なフォームを生成する
gem 'simple_form', '~> 4.1.0'
# テンプレートエンジンslimをrailsで使うためのライブラリ
gem 'slim-rails', '>= 3.0.1'
# 半角全角変換などに特化したgem
gem 'moji'
# Excelファイルを生成するためのgem。読込はできない。
gem 'caxlsx'
gem 'caxlsx_rails'
# 親子関係を管理するためのgem
gem 'ancestry'
# 検索を簡単に実装するためのgem
gem 'ransack'
# 論理削除実装用gem
gem 'kakurenbo-puti'

# ...省略

まとめ

まずは情報格差を減らし、プロジェクトへ参加しやすい道を作ってから。暗黙知形式知に変えていけば、学習曲線もよくなるだろう。そうなれば、自ずと技術力はついてくるようになる、はず…。そう考えて、今後も情報格差を減らしていくよう頑張る。