Madogiwa Blog

主に技術系の学習メモに使っていきます。

Ruby: JSON.parse時に任意のClassのオブジェクトとして取得する方法

JSON.parseの結果は基本的にはHashになると思うのですが、任意のClassのオブジェクトで取得したい場合に、JSON.parseの引数object_classを利用すると簡単に実現出来ることを知らなかったのでメモしておきます📝

やり方は以下の通りでdef []=(key, value)を持つ任意のClassを作成してあげて、JSON.parseの引数object_classにそのClassを渡して上げるだけです。

require 'json'

json = <<~JSON
[
  {
    "title": "foo 1",
    "body": "bar 1"
  },
  {
    "title": "foo 2",
    "body": "bar 2"
  }
]
JSON

class Article
  attr_accessor :title, :body

  def initializer(title, body)
    @title = title
    @body = body
  end

  def []=(key, value)
    instance_variable_set("@#{key}", value)
  end
end

p JSON.parse(json, object_class: Article)
# => [#<Article:0x00007fcad21f46c8 @title="foo 1", @body="bar 1">, #<Article:0x00007fcad21f44c0 @title="foo 2", @body="bar 2">]

Ruby便利ですね✨

参考

docs.ruby-lang.org

Skylightを使ってRailsアプリケーションのAPMを取得するMEMO📝

個人で運営しているRailsWebサービスAPMをSkylightというサービスを利用して取得してみたので、手順とかをMEMOしておきます📝

www.skylight.io

Skylightとは?

Skylight is a smart profiler for Ruby and Rails applications. It turns performance data into actionable insights, so you spend less time diagnosing and more time improving. Skylight

上記の記載の通りRubyRuby on Rails製のアプリケーションのパフォーマンス計測ツールです。

月間100,000リクエストまでは無料で使えますし、GitHubのアカウントで登録できます。

f:id:madogiwa0124:20210529180105p:plain

※2021/05/29時点の価格設定最新の価格はこちら

最初はNew Relicを使おうと思ったのですがアカウント登録時に会社名が必須になっていて個人利用はちょっとあれなのかなと思い断念した。。。

newrelic.com

Skylightを使ってAPMを取得する

Skylightを使うのは非常に簡単で、Skylightにアカウント登録後に表示されるメッセージ通りに、

Gemfileに以下を追加してbundle installを実行後

 gem "skylight"

<setup token>にメッセージに記載されているトークンを設定して、以下のコマンドを実行します。

 bundle exec skylight setup <setup token>

そうするとAPM取得用のTOKENが記載されたconfig/skylight.ymlが作成されるので、これをそのまま使用するか、環境変数等で管理したい場合はSKYLIGHT_AUTHENTICATIONにTOKENを設定してデプロイすればOKです🙆‍♂️

あとは環境にアクセスすると以下のような形でAPMを見ることができます✨

f:id:madogiwa0124:20210529181129p:plain

詳細を見るとどこに時間がかかってるのかも見れて便利ですね!

f:id:madogiwa0124:20210529181218p:plain

参考

takagi.blog

www.skylight.io

Ruby on Rails: Logrageを使ってRailsのログ出力をいい感じにするMEMO📝

個人のWebサービスにLogrageを入れてみたので導入方法とか使い方をメモしておきます📝

github.com

Logrageとは?

Lograge is an attempt to bring sanity to Rails' noisy and unusable, unparsable and, in the context of running multiple processes and servers, unreadable default logging output. GitHub - roidrage/lograge: An attempt to tame Rails' default policy to log everything.

だいぶ強い文言ですが、Railsのログ出力をいい感じにしてくれるgemです🚃

以下にREADMEに記載されている使用前と使用後のログを載せましたが、使用後のほうが1行で表示されており見やすいですね👀

使用前

Started GET "/" for 127.0.0.1 at 2012-03-10 14:28:14 +0100
Processing by HomeController#index as HTML
  Rendered text template within layouts/application (0.0ms)
  Rendered layouts/_assets.html.erb (2.0ms)
  Rendered layouts/_top.html.erb (2.6ms)
  Rendered layouts/_about.html.erb (0.3ms)
  Rendered layouts/_google_analytics.html.erb (0.4ms)
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)

使用後

method=GET path=/jobs/833552.json format=json controller=JobsController  action=show status=200 duration=58.33 view=40.43 db=15.26

Logrageの使い方

Logrageを導入する

Logrageを単に導入するのは簡単で以下の通りGemfileに記載後にbundle installを実行し、

gem "lograge"

config/initializer配下に以下を記載したrubyファイルを配置します。

Rails.application.configure do
  config.lograge.enabled = true
end

これでLogrageによってフォーマットされたログが出力されるようになります✨

※デフォルトではLograge::Formatters::KeyValueを使ったフォーマットが行われます。   lograge/key_value.rb at master · roidrage/lograge · GitHub

ログ出力項目を追加する

ログ出力項目を追加する場合には以下のような形でconfig.lograge.custom_optionsに追加したい項目のhashを返却するlamda(引数にeventを取ってcallが定義されているインスタンスであれば何でも良さそう)を渡してあげます。

Rails.application.configure do
  config.lograge.enabled = true
  config.lograge.custom_options = ->(event) { { time: Time.current } }
end

以下のようにtime=2021-05-23 17:15:47 +0900が追加されています🕛

method=GET path=/jobs/833552.json format=json controller=JobsController  action=show status=200 duration=58.33 view=40.43 db=15.26 time=2021-05-23 17:15:47 +0900

独自に作成したフォーマッターを使用する

独自に定義したフォーマッターも使用するこができ、以下は私が作ったHTTPステータスコードを表すアイコンを表示するログフォーマッターです。

# frozen_string_literal: true

require 'lograge'

class Lograge::Formatters::MarkedKeyValue < Lograge::Formatters::KeyValue
  INFO_MARK = '🙂'
  SUCCES_MARK = '😃'
  REDIRECT_MARK = '😗'
  CLIENT_ERROR_MARK = '🤔'
  SERVER_ERROR_MARK = '😱'

  def call(data)
    result = super(data)
    mark = status_to_mark(data[:status].to_i)
    "#{mark} #{result}"
  end

  def status_to_mark(status)
    case status
    when 100...200 then INFO_MARK
    when 200...300 then SUCCES_MARK
    when 300...400 then REDIRECT_MARK
    when 400...500 then CLIENT_ERROR_MARK
    when 500...    then SERVER_ERROR_MARK
    else ' '
    end
  end
end

以下のような形でconfig.lograge.formatterインスタンスを渡して上げると、

require Rails.root.join('lib/lograge/formatters/marked_key_value')

Rails.application.configure do
  config.lograge.enabled = true
  config.lograge.formatter = Lograge::Formatters::MarkedKeyValue.new
  config.lograge.custom_options = ->(event) { { time: Time.current } }
end

以下のような形で出力されます🙌

😃 method=GET path=/api/feeds format=html controller=Api::FeedsController action=index status=200 duration=5.92 view=0.16 db=0.33 time=2021-05-23 16:21:12 +0900
😃 method=GET path=/feeds/533 format=html controller=FeedsController action=show status=200 duration=13.04 view=9.63 db=0.00 time=2021-05-23 16:21:24 +0900
😱 method=GET path=/api/feeds/533 format=html controller=Api::FeedsController action=show status=500 error='RuntimeError: ' duration=0.44 view=0.00 db=0.00 time=2021-05-23 16:21:24 +0900

おわりに

lograge、rackプロトコルライクな感じでフォーマッターも作りやすく良いですね✨

Ruby on Rails: Rails標準の`config_for`を使ってカスタム設定を管理する⚙

Railsでカスタム設定を扱う場合、以下に記載したSettingslogicGlobalを使うことが多かったんですが、Rails標準のconfig_forを使っても同じようなことが出来たので、使い方とかをMEMOしておきます📝

github.com

github.com

ちなみに、Rails 6.1.3.2環境で試した内容のMEMOになります。

config_forとは?

config_forとは、Rails::Application#config_forに定義されているRails標準でカスタム設定を管理するための機能です。

Railsの設定オブジェクトをカスタマイズして独自のコードを設定するには、config.x名前空間かconfigの直下にコードを配置します。

  • config.super_debugger = true
  • Rails.configuration.super_debugger # => true

Rails::Application.config_forを使うと、設定ファイル全体を読み込むこともできます。

  • config.payment = config_for(:payment)

Rails アプリケーションを設定する - Railsガイド

上記の通り、config.fooとすることで独自にRails.configuration.fooとして独自にApplicaitonでグローバルに参照出来る値を定義出来るのですが、config_for(:for)とする事によって、config/foo.ymlに設定された内容を、そのまま定義することができます⚙

実際の処理の中身は以下のAPI documentから参照できます。

api.rubyonrails.org

config_forを使って設定値を管理する

管理用のyamlファイルを作成する

では実際にconfig_forを使って設定値を管理してみます。

今回はBASIC認証のuser/passwordをconfig_forで管理するようにしてみます。※credencialsを使ったほうが良い等もあるかもですが。。。

まずは設定値の管理用のyamlファイルを用意します。 ※後述の通りconfig_forで任意パスのファイルを読み込むことができますが、デフォルトの挙動は、config 直下の拡張子が.ymlのファイルを探して読み込まれます。

またyamlファイルはERBとして解釈されるため以下のような形で環境変数から値を読むこともできます。

# config/settings.yml
production:
  basic_auth:
    user: <%= ENV['BASIC_AUTH_USER'] %>
    password: <%= ENV['BASIC_AUTH_PASSWORD'] %>
development:
  basic_auth:
    user: <%= ENV['BASIC_AUTH_USER'] %>
    password: <%= ENV['BASIC_AUTH_PASSWORD'] %>
test:
  basic_auth:
    user: <%= ENV['BASIC_AUTH_USER'] %>
    password: <%= ENV['BASIC_AUTH_PASSWORD'] %>

作成したyamlファイルからconfig_forを使って設定する

設定値を参照出来るようにするには、config_forに引数として先程作成したファイルのファイル名をシンボルとして渡します。

module MyApp
  class Application < Rails::Application
    config.settings = config_for(:settings)

config直下では無く、任意の場所に作成した設定用のyamlファイルを読み込みたいときは以下のような形でPathnameインスタンスを渡してあげればOKです🆗

config.authentication = config_for(Rails.root.join('config/settings/authentication.yml'))

これで、以下のような形でBASIC認証に使用するuser/passwordを指定することできます✨

class ApplicationController < ApplicationController
  before_action :basic_auth

  private

  def basic_auth
    basic_auth_config = Rails.configuration.settings.basic_auth
    authenticate_or_request_with_http_basic do |user, password|
      user == basic_auth_config[:user] && password == basic_auth_config[:password]
    end
  end
end

おわりに

今までGemを使うことが多かったんですが、全然Rails標準のconfig_forを使っても良さそうですね、便利✨

Ruby on Rails: OK Computerを使ってヘルスチェックを行うときの導入方法とかMEMO

OK ComputerというGemを教えてもらい、個人サービスに入れてみたところ、適当に設定すると色々見れて便利だったので導入方法とかをメモしておきます📝

github.com

導入方法

OK Computerのインストール

Gemfileに以下を追加して、bundle installします。

gem 'okcomputer'

OK Computerのmount

OK Computerでの監視結果を見るためにroutesを定義します。

以下のような形でmountします。※atには任意のパスを設定してください。

Rails.application.routes.draw do
  mount OkComputer::Engine
end

これで/helth_checkにアクセスすると以下のようにOK Computerの監視結果を見ることができます。

f:id:madogiwa0124:20210509154727p:plain

監視項目の追加

OK Computerには以下の通りbuildinで色々な監視が用意されていて自分で追加することができます。

okcomputer/lib/ok_computer/built_in_checks at master · sportngin/okcomputer · GitHub

追加方法はconfig/initilalizer内にokcomputer.rbのようなファイルを用意して、その中で追加を行います。

# okcomputer.rb
OkComputer::Registry.register 'ruby version', OkComputer::RubyVersionCheck.new

私は以下のような形で設定してみました。

# okcomputer.rb
OkComputer::Registry.register 'redis', OkComputer::RedisCheck.new({})
OkComputer::Registry.register 'ruby version', OkComputer::RubyVersionCheck.new
OkComputer::Registry.register 'cache', OkComputer::GenericCacheCheck.new
OkComputer::Registry.register 'sidekiq latency', OkComputer::SidekiqLatencyCheck.new('default')
'SOURCE_VERSION'
OkComputer::Registry.register 'version', OkComputer::AppVersionCheck.new(env: env_name)

Basic認証の追加

OK ComputerではBasic認証を設定することもできます。 監視項目によっては外部から参照されるとリスクになる可能性がある場合には以下のような形で設定しておくと良さそうです。

# Basic認証
OkComputer.require_authentication(ENV['BASIC_AUTH_USER'], ENV['BASIC_AUTH_PASSWORD'])

おまけ:OK ComputerのViewをカスタマイズする

デフォルトだとちょっと見た目が寂しいので既存のOK ComputerのControllerにモンキーパッチを当てて多少見やすくする方法をおまけで載せておきます。

まずOK ComputerはRails Engineという仕組みでアプリケーションに組み込み、監視結果を表示しているので通常のRailsと同じでContollerでrenderするテンプレートを切り替えれば任意のViewを表示できます。

Rails Engineについてはこちら

guides.rubyonrails.org

OK Computerの監視項目の表示を行ってるコントローラーは以下で、その中でもrespondrenderを行っていることがわかるので以下のような感じのコントローラーを用意して、htmlの場合は作成するviewを表示するようにオーバーライドします。

# /lib/okcomputer/app/controllers
OkComputer::OkComputerController.class_eval do
  self.view_paths = 'lib/okcomputer/app/views'

  def respond(data, status)
    respond_to do |format|
      format.text { render plain: data, status: status, content_type: 'text/plain' }
      format.html { render "/#{action_name}", locals: { data: data, status: status }, content_type: 'text/html' }
      format.json { render json: data, status: status }
    end
  end
end

そしてアクションに合わせて以下のようなviewを作成し適当にスタイルを当ててあげます。※html5.2からはbody内の任意の箇所でstyleが記載できるようになったようです。

<!-- lib/okcomputer/app/views/index.html.erb -->
<h1>OkComputer ALL</h1>

<% status_class_name = data.collection.values.any?(&:failure_occurred) ? 'faild' : 'passed' %>
<% checks = data.collection.values %>

<h2 class="status">STATUS: <span class="<%= class_name %>"><%= status %></span></h2>
<table>
  <thead>
    <tr>
      <th>NAME</th>
      <th>PASS/FAIL</th>
      <th>MESSAGE</th>
      <th>TIME</th>
    </tr>
  </thead>
  <tbody>
    <% checks.each do |check| %>
      <% passfail = check.success? ? "passed" : "failed" %>
      <tr class="<%= passfail %>">
        <td class="registrant_name"><%= check.registrant_name %></td>
        <td class="passfail"><%= passfail.upcase %></td>
        <td class="message"><%= check.message %></td>
        <td class="time"><%= check.time ? sprintf('%.3f', check.time) : '?' %></td>
      </tr>
    <% end %>
  </tbody>
</table>
<!-- lib/okcomputer/app/views/show.html.erb -->
<h1><%= "OkComputer #{data.registrant_name}" %></h1>

<% status_class_name = data.failure_occurred ? 'faild' : 'passed' %>
<% checks = [data] %>

<h2 class="status">STATUS: <span class="<%= class_name %>"><%= status %></span></h2>
<table>
  <thead>
    <tr>
      <th>NAME</th>
      <th>PASS/FAIL</th>
      <th>MESSAGE</th>
      <th>TIME</th>
    </tr>
  </thead>
  <tbody>
    <% checks.each do |check| %>
      <% passfail = check.success? ? "passed" : "failed" %>
      <tr class="<%= passfail %>">
        <td class="registrant_name"><%= check.registrant_name %></td>
        <td class="passfail"><%= passfail.upcase %></td>
        <td class="message"><%= check.message %></td>
        <td class="time"><%= check.time ? sprintf('%.3f', check.time) : '?' %></td>
      </tr>
    <% end %>
  </tbody>
</table>

モンキーパッチはファイルパスとnamespaceを合わせるのが難しく、auto loadの対象になるとエラーになるので対象にならないように無効化しときます。

# NOTE: zeitwerkの期待するファイルパスとnamespaceを含めたclass名を一致させることが厳しいので、
# okcomputerへのmonkey patchを入れているapp配下はautoloadの対象外にする
Rails.autoloaders.main.ignore("#{Rails.root}/lib/okcomputer/app")

最後にapplication.rbで以下のように作成したControllerをloadするようにしてあげると、

    # Rails engineで導入されているokcomputerのcontrollerのオーバーライド
    # https://edgeguides.rubyonrails.org/engines.html#overriding-models-and-controllers
    config.to_prepare do
      load "#{Rails.root}/lib/okcomputer/app/controllers/ok_computer_controller.rb"
    end

ちょっと見やすくできました✨

f:id:madogiwa0124:20210509160913p:plain

参考

qiita.com

html5.2ではstyle要素をbody内に書けるようになりました - webのあれこれ

t.co

Vue✕TypeScriptなプロジェクトにJestを導入する方法MEMO👢

Vue、TypeScriptが導入時されているプロジェクトにJestを導入したので、そのあたりの手順をMEMOしておきます📝

前提として環境は以下の通りです

  • node: 14.16.1
  • yarn: 1.22.4
  • vue: 2.6.12
  • typescript: 4.2.4
  • jest: 26.6.3

基本的には以下のドキュメントに従って導入しました📕

vue-test-utils.vuejs.org

関連ライブラリのinstall

まずは必要なライブラリをinstallします。

$ yarn add -D jest ts-jest babel-jest babel-core@bridge @vue/test-utils vue-jest @types/jest

それぞれの概要は以下の通り

  • jest: Jest本体
  • ts-jext: .tsのコードをテストする場合に必要
  • babel-jest: JestはWebpackでbuildするわけではないのでJestでもBabelを利用する場合に必要
  • babel-core@bridge: 後述しますが無いとエラーになるので。。。
  • @vue/test-utils: 単一ファイルコンポーネントのテスト用のAPIを利用するためのライブラリ
  • vue-jest: .vueのコードをテストする場合に必要
  • @types/jest: Jestの型定義ファイル

Jestの設定ファイル作成

Jestの設定ファイルは以下のコマンドで作成できます。

$ jest --init

いくつか質問されるので自身の環境に合わせて設定します⚙

生成されたファイルに関して私は以下のことを行いました👷‍♂️

  • moduleFileExtensionsにテスト対象となりうる拡張子を指定
  • moduleNameMapperをWebpackの設定ファイルと合わせて修正しaliasの解決ができるように
  • testMatchをテスト対象のファイルに合わせて正規表現を見直してfoo.spec.tsのみが対象となるように
  • testPathIgnorePatternsにライブラリ関連のディレクトリを指定しテスト対象外に
  • transformでファイル別に使用するライブラリを指定

最終的な設定ファイルは以下のような形になりました📝

module.exports = {
  // Automatically clear mock calls and instances between every test
  clearMocks: true,

  // An array of file extensions your modules use
  moduleFileExtensions: ["js", "ts", "vue"],

  // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
  moduleNameMapper: {
    "^@js(.*)$": "<rootDir>/app/javascript/$1",
    "^@css(.*)$": "<rootDir>/app/javascript/stylesheets/$1"
  },

  // The glob patterns Jest uses to detect test files
  testMatch: ["**/?(*.)+(spec).[tj]s"],

  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
  testPathIgnorePatterns: ["/node_modules/", "/vendor/bundle/"],

  // A map from regular expressions to paths to transformers
  transform: {
    "^.+\\.js$": "babel-jest",
    "^.+\\.ts$": "ts-jest",
    ".*\\.(vue)$": "vue-jest",
  },
};

設定ファイルの各種設定値の詳細はこちら

jestjs.io

Jest実行用のコマンドをpackage.jsonに追加

私は以下のようなコマンドを用意しました👷‍♂️

  "scripts": {
    "test": "jest --verbose",
    "test:coverage": "yarn test --coverage"
  }
  • --verbose:テストごとの結果を表示する
  • --coverage:実行後にカバレッジレポートを表示

CLI時のオプションの詳細はこちら

jestjs.io

適当なテスト用のコードを作成して正常に実行できればOKです🙆‍♂️

import { mount } from "@vue/test-utils";
import Component from "@js/components/Component.vue";

describe("components/Component.vue", () => {
  it("snapshot", () => {
    const wrapper = mount(Component);
    expect(wrapper.element).toMatchSnapshot();
  });
});
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 updated, 1 total
Time:        2.014 s
Ran all test suites.
✨  Done in 3.20s.

CIでJestを実行する

以下のようなgithub actions用のyamlを用意してpush時にJestによるテストを実行するようにしました🤖

name: node_test

on: [push]

jobs:
  run-node-test:
    runs-on: ubuntu-latest
    env:
      TZ: Asia/Tokyo
    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-node@v1
      with:
        node-version: '14'
    - name: cache node deps
      uses: actions/cache@v1
      with:
        path: node_modules
        key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }}
        restore-keys: |
          ${{ runner.os }}-node_modules-
    - name: install node deps
      run: yarn install
    - name: test js
      run: yarn test:coverage

導入時に発生したエラー解消ログ

実行時にSyntaxError: Cannot use import statement outside a moduleが発生する

以下のようなエラーが発生

  ● Test suite failed to run
    Jest encountered an unexpected token
  Details:
    SyntaxError: Cannot use import statement outside a module

ts-jestを導入し、

yarn add -D ts-jest

jest.config.jstransform"^.+\\.ts$": "ts-jest"を設定

  transform: {
    "^.+\\.js$": "babel-jest",
    "^.+\\.ts$": "ts-jest",
    ".*\\.(vue)$": "vue-jest",
  },

参考

github.com

実行時にCannot find module 'babel-core'が発生する

以下のようなエラーが発生

  ● Test suite failed to run
    Cannot find module 'babel-core'

babel-core@bridgeをinstallして解決

yarn add --D babel-core@bridge

参考

qiita.com

実行時にSyntaxError: Unexpected token } in JSON at position 545が発生する

以下のようなエラーが発生

  ● Test suite failed to run
    Jest encountered an unexpected token
    SyntaxError: Unexpected token } in JSON at position 545
        at JSON.parse (<anonymous>)
      at parse (node_modules/tsconfig/src/tsconfig.ts:195:15)

ts-configJSONファイルとしては不正なカンマが入っていたため削除

before

    "paths": {
      "@js/*": ["app/javascript/*"],
      "@css/*": ["app/javascript/stylesheets/*"],
    }

after

    "paths": {
      "@js/*": ["app/javascript/*"],
      "@css/*": ["app/javascript/stylesheets/*"]
    }

参考

zenn.dev

おまけ:Jestで時間を固定する

jest-date-mockが便利✨

github.com

以下のような形でadvanceToを使って引数で渡した時間に固定できる🕛

import { advanceTo } from "jest-date-mock";
advanceTo(new Date("2021-05-03T15:35:47+09:00"));

参考

qiita.com

おわりに

Jestを導入して、SnapShotは使ったコンポーネントユニットテストができるようになると、比較的低コストでコンポーネントのテストができていいですね✨

Ruby on Rails: Rails 6.1でroutes.rbを複数ファイルに分割する

以下のPRで導入されたRails 6.1から使える機能を使うといい感じでroutesを複数ファイルに分割できたのでやり方をメモしておきます📝

github.com

やり方

以下のようなroutesをnamespace毎に分割したいときには、

Rails.application.routes.draw do
  root to: 'boards#new'
  resources :feeds, only: [:index, :show, :new, :create]
  resources :boards, only: [:show, :new, :index], constraints: { format: :html }
  get '/boards/:id', to: 'rss/boards#show', constraints: lambda { |req| req.format == :rss }
  get '/mypage', to: 'mypage#show'

  namespace :api do
    resources :feeds, only: [:index, :show]
    resources :feed_entries, only: [:index], controller: 'feed/entries'
    resources :entries, only: [:index]
    resources :boards, only: [:create, :show, :index]
    resources :tags, only: [:index]
  end

  namespace :admin do
    root to: 'boards#index'
    resources :boards
    resources :board_feeds
    resources :feeds
    require 'sidekiq/web'
    mount Sidekiq::Web, at: '/sidekiq'
  end
end

以下のようにcoufig/routes配下にファイルを分けて、

# config/routes/admin.rb
Rails.application.routes.draw do
  namespace :admin do
    root to: 'boards#index'
    resources :boards
    resources :board_feeds
    resources :feeds
    require 'sidekiq/web'
    mount Sidekiq::Web, at: '/sidekiq'
  end
end
# config/routes/api.rb
Rails.application.routes.draw do
  namespace :api do
    resources :feeds, only: [:index, :show]
    resources :feed_entries, only: [:index], controller: 'feed/entries'
    resources :entries, only: [:index]
    resources :boards, only: [:create, :show, :index]
    resources :tags, only: [:index]
  end
end

config/routes.rbで以下のようにdraw :filenameとしてあげればOKです🙆‍♂️

Rails.application.routes.draw do
  root to: 'boards#new'
  resources :feeds, only: [:index, :show, :new, :create]
  resources :boards, only: [:show, :new, :index], constraints: { format: :html }
  get '/boards/:id', to: 'rss/boards#show', constraints: lambda { |req| req.format == :rss }
  get '/mypage', to: 'mypage#show'

  draw :api
  draw :admin
end

drawメソッドの中身は以下のような形になっていて、引数で渡されたfile名で探索して、instance_evalで分割したファイル内の処理を実行することで、分割したファイル内の処理をroutes.rbで実行することで、routesを定義しているようですね👀

# actionpack/lib/action_dispatch/routing/mapper.rb
        def draw(name)
          path = @draw_paths.find do |_path|
            File.exist? "#{_path}/#{name}.rb"
          end

          unless path
            msg  = "Your router tried to #draw the external file #{name}.rb,\n" \
                   "but the file was not found in:\n\n"
            msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n")
            raise ArgumentError, msg
          end

          route_path = "#{path}/#{name}.rb"
          instance_eval(File.read(route_path), route_path.to_s)
        end

おわりに

サービスが大きくなるとroutesが肥大化してきて辛くなってくると思うのですが、標準機能でこの辺に楽に対応できるのは良いですね✨

参考

tech.toreta.in