Madogiwa Blog

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

Ruby on Rails: ダイレクトルーティング(Direct routes)を使って外部サイトのURLをroutesで管理する

OAuthを利用した外部サイトへの認証時のリダイレクトやLPへの導線等、Webサービス内で外部サイトのURLを扱いたいときにRailsのダイレクトルーティング機能を使うとURLの管理をRoutesに統一できて便利だったのでMEMOしておきます📝

ダイレクトルーティング機能とは?

The direct method allows creation of custom URL helpers. https://guides.rubyonrails.org/5_1_release_notes.html#direct-resolved-routes

Rails 5.1で導入された、Railsのダイレクトルーティングは以下のような形でroutes内で外部サイトのURLをdirectの引数に名前、block引数にURL文字列を渡すことでfoo_urlのようなhelperを定義出来る機能です。

direct(:homepage) { "http://www.rubyonrails.org" }
homepage_url # => "http://www.rubyonrails.org"

以下のPRで導入されているので内部実装が気になる方はこちらから参照してください。

github.com

使い方

基本

普通に使う分には先程記載した以下の通りです。

direct(:homepage) { "http://www.rubyonrails.org" }
homepage_url # => "http://www.rubyonrails.org"

引数で任意のクエリ文字列を付与する

例えばhomepage_url(from: 'top')で実行したときにhttp://www.rubyonrails.org?from='top' のようなURLを生成したいとします。

通常のdirectを使用しないroutesだと以下のような形でかけますが、

foo_path(bar: 'baz')
#=> /foo?bar=baz

directを利用したRoutesの場合はblockが評価された結果がそのまま返却されるようなので、定義した文字列が帰ってしまいます。

direct(:homepage) { "http://www.rubyonrails.org" }
homepage_url(for: 'bar') # => "http://www.rubyonrails.org"

このようなケースでは以下のような形でblock引数を利用して引数で渡されたオプションをクエリ文字列に指定してあげると、

  direct(:homepage) { |options|
    uri = URI.parse("http://www.rubyonrails.org")
    uri.query = options.to_param unless options.empty?
    uri.to_s
  }

以下のようにいい感じの使い心地になります✨

Rails.application.routes.url_helpers.homepage_url(from: "top", to: "bar")
#=> "http://www.rubyonrails.org?from=top&to=bar"

おわりに

知らなかったですが、外部サイト含めてRoutesでまとめて管理出来るdirect便利ですね!!

参考

qiita.com

個人のWebサービスをRuby 3.0アップデートしたので対応したこととかMEMO

2020/12/25にRuby 3.0.0がリリースされました🎄🎅🎁✨

www.ruby-lang.org

Ruby on Rails(6.1.1)製の個人のWebサービスを今更ながらRuby 3.0にアップデートしました。

update後に一部CI等が落ちて対応した点があったので対応したことをMEMOしておきます📝

対応したこと

非推奨だったキーワード引数が動かなくなったので**で展開して渡すように

キーワード引数が通常の引数から分離されました。 原則として、2.7 で警告の出ていたコードは動かなくなります。詳細は別ドキュメントを参照してください。

# before
@feeds = Feed.preload(:last_entry, :tags).search(search_params).recent

# after
@feeds = Feed.preload(:last_entry, :tags).search(**search_params).recent

2.7に上げたときの対応漏れがあり、3.0.0で動かなくなるキーワード引数の挙動を使った実装が残っていたので対応しました。

対応方法はRubyの公式のドキュメントが出ていて、

www.ruby-lang.org

Here is the most typical case. You can use double splat operator (**) to pass keywords instead of a Hash.

今回は上記の通り典型的なケースだったので**を付与し展開して渡すようにしました。

標準添付ライブラリからbundled gemに変更になったのでGemfileに追加

Rssを読み込むような機能でRubyの標準添付ライブラリだったrssを利用していたのですが、

docs.ruby-lang.org

以下のライブラリが新たに bundled gems になりました。Bundler から利用する場合は Gemfile に明示的に指定してください。
rexml
rss

上記の通り、bundled gemに変更されGemfileに明示的に指定する必要があり、Gemfileに追記する対応をしました。

# Gemfile
gem 'rss' # add

また以下はbundled gemにならず標準添付ライブラリから削除されているので、同様にGemfileに追記したinstallするような対応が必要そうです。

以下のライブラリは標準添付ライブラリから削除されました。3.0 以降で使いたい場合は rubygems から利用してください。
sdbm
webrick
net-telnet
xmlrpc

ちなみにbundled gemとかdefault gemとかの話は以下がわかりやすかったです💎

blog.n-z.jp

おわりに

Rubyのメジャーアップデートということでしたが、個人の規模が小さいアプリケーションということもあり、そこまでハマるようなこともなくバージョンが挙げられました🙏✨

Ruby 3.0はrbsを始めとした静的型付け系の機能が入っていたり、パフォーマンス向上 etc...様々な改善がされているなか、利用者がスムーズにアップデートできるというのは非常にありがたいですね🙇‍♂️

Deviseで内部でセキュアなパスワードを設定してアカウントを作成するMEMO

管理画面のアカウント等、管理者が生成して利用者にアカウントを配布するような運用はあると思うのですが、管理者がアカウントを作成する際にパスワードまで指定するようにしてしまうと、管理者によるなりすましの懸念があります。。。

そのためアカウント作成時には利用者のメールアドレスのみを指定して、パスワードは内部で生成した予測不能な文字列を設定したほうがセキュアです。

やり方は以下のような形で、Devise.friendly_tokenが渡された引数の文字数で予測不能なセキュアな文字列を生成してくれます。  

generated_password = Devise.friendly_token(12)
admin = Admin.create!(:email => email, :password => generated_password)

内部的にはSecureRandom を使ってURLSafeな文字列を生成して、紛らわしい文字列は置換しているようですね👀

  def self.friendly_token(length = 20)
    # To calculate real characters, we must perform this operation.
    # See SecureRandom.urlsafe_base64
    rlength = (length * 3) / 4
    SecureRandom.urlsafe_base64(rlength).tr('lIO0', 'sxyz')
  end

github.com

Deviseは便利なメソッドがありますね✨

参考

How To: Automatically generate password for users (simpler registration) · heartcombo/devise Wiki · GitHub

Ruby on Rails: 6.1.0の新機能strict_loadingを使ってN+1を防ぐMEMO

Rails 6.1.0で導入されたstrict_loadingを使うと手軽にN+1のチェックが出来て便利そうなのでMEMO📝

基本的には以下のような形でActiveRecord::Releationを作るメソッドチェイン内でstrict_loadingを呼び出すようにすると、

  def index
    @feeds = Feed.strict_loading
                 .search(params.dig(:query, :keyword)).recent
                 .pager(page: params[:page], per: PER_PAGE)
    response = @feeds.map { |feed| ::Api::Feed.new(feed: feed).attributes }
    render json: response
  end

eager_loadingされてないと以下のようなエラーが発生します🚨

ActiveRecord::StrictLoadingViolationError (`Entry` called on `Feed` is marked for strict_loading and cannot be lazily loaded.):
  
app/models/api/feed.rb:5:in `initialize'
app/controllers/api/feeds_controller.rb:8:in `new'
app/controllers/api/feeds_controller.rb:8:in `block in index'
app/controllers/api/feeds_controller.rb:8:in `map'
app/controllers/api/feeds_controller.rb:8:in `index'

以下のようにpreloadを入れるとエラーが発生しなくなります✨

  def index
    @feeds = Feed.strict_loading.preload(:last_entry, :tags)
                 .search(params.dig(:query, :keyword)).recent
                 .pager(page: params[:page], per: PER_PAGE)
    response = @feeds.map { |feed| ::Api::Feed.new(feed: feed).attributes }
    render json: response
  end

Model単位でデフォルトを指定することもできるので、ApplicationRecordに設定を入れておいて、N+1を許容するときだけFeed.strict_loading(false)みたいな形で明示的にわかるようにするといい感じな気もしました👍

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  self.strict_loading_by_default = true
end

今まではbulletでこういうのを検知していましたが、Rails標準でN+1を検知できる仕組みが出来たのは便利ですね✨

参考

qiita.com

Rails6.1で新しく入る機能について - Speaker Deck

github.com

クックパッドマートさんのエンジニア採用試験が公開されているみたいなのでやってみた🥞

以下のTweetを見かけて気になり、勉強がてら自分でも解いてみたので自分の回答がいい感じかはわからないですがメモしておきます📝

以下の環境でやってみました💻

$ ruby -v
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin18]

コードはGitHubに公開してみたのでこちらまで🐙

github.com

Q1 大きさは同じで重さが異なる商品が複数あるとします。この商品N個を、以下の条件にそって...

大きさは同じで重さが異なる商品が複数あるとします。この商品N個を、以下の条件にそって3つのトラックに分配するアルゴリズムを実装してください。

  1. すべての商品は同一の大きさ、重さの箱に入り、箱は個別のIDを持つものとする
  2. プログラム実行時は、コマンドライン引数で「箱ID」と「重さ」の情報を与え、プログラムの結果には各トラックに積載する「箱ID」を出力してください。たとえば 1:50 の文字列をコマンドライン引数で渡したときは、箱ID=1, 重さ=50kg の商品とする
  3. 商品は箱に入った状態で列となって連続で運び込まれ、重さは持ち上げるまでわからず、尚且つ同時に1つしか持ち上げられない
  4. それぞれのトラックには、なるべく重さが均等になるように分配する必要がある
  5. それぞれのトラックの積載可能重量に制限はない

実行例

$ ruby solve.rb 1:50 2:30 3:40 4:10
truck_1:1
truck_2:2,4
truck_3:3

自分の実装の実行結果

$ ruby exam_1/main.rb 1:50 2:30 3:40 4:10
truck_1:1
truck_2:2,4
truck_3:3

以下のような感じで実装してみました。

  • 実行時に渡されたid:重さ形式で渡された引数をsplit(':')でidとweightに分割し、idとweightをもとに荷物オブジェクトを生成
  • 3台のトラックのオブジェクトを生成
  • 荷物オブジェクトの配列をトラックに割り当てる
    • 重量は持ち上げるまでわからないということなので、とりあえず一番積載量の少ないトラックに乗せるようなロジックで実装してみた・・・!
  • 整形して結果を出力

荷物のトラックへの割当処理を行うクラス

class TruckAllocator
  def self.call(baggages:, trucks:)
    new(baggages: baggages, trucks: trucks).call
  end

  def initialize(baggages:, trucks:)
    @baggages = baggages
    @trucks = trucks
  end

  attr_reader :baggages, :trucks

  def call
    allocate
    result_text
  end

  private

  def result_text
    trucks.map(&:info_text)
  end

  def allocate
    baggages.each { |baggage| target_truck.baggages << baggage }
  end

  def target_truck
    # NOTE: 積載重量が一番少ないトラックをターゲットにする
    trucks.min { |a, b| a.weight <=> b.weight }
  end
end

Q2 任意の数のモンスターがいます。APIサーバーにそのうちの2匹を指定すると...

任意の数のモンスターがいます。APIサーバーにそのうちの2匹を指定すると、対戦をさせた結果を得ることができます。モンスターの強さは決まっていて、同じモンスター同士であれば、対戦の結果は常に変わりません。また、三すくみのような状態は考えないものとします。このAPIサーバーをつかって、モンスターを強い順に並べてください。

APIレスポンスの例

$ curl https://ob6la3c120.execute-api.ap-northeast-1.amazonaws.com/Prod/battle/dragon+griffin
{"winner":"dragon","loser":"griffin"}

実行例

ruby solve.rb griffin vampire dragon troll medusa

自分の実装の実行結果

$ ruby exam_2/main.rb griffin vampire dragon troll medusa
==== RESULT ====
No.1: troll
No.2: dragon
No.3: medusa
No.4: griffin
No.5: vampire

以下のような感じで実装してみました。

  • 実行時に渡されたモンスター(文字列)の配列を取得
  • 渡されたモンスターの配列をもとに順位付け
    • 最初のモンスターと次のモンスターを戦わせていき、最終的に勝ち残ったモンスターを1位で確定させて、2位以降も同様に行うような、勝ち抜け方式で順位付けするような形で実装してみましたが(バブルソートライクっぽい)、もうちょっといい方法がありそうな・・・!
  • 整形して結果を出力

以下が渡されたモンスターの配列をもとに順位付けを行うClassです。

require_relative './judge'

class Arena
  def initialize(monsters)
    @monsters = monsters
    @challengers = monsters.dup
    @results = []
  end

  attr_reader :monsters, :challengers, :results

  def call
    knockout_competition
    results_info
  end

  private

  # NOTE: 勝ち抜け方式でモンスターの順位付けを行うメソッド
  # ※ deleteを使っているので重複したモンスター(文字列)が渡された場合、名寄せされる
  # 例) dragon, dragon, troll => troll, dragon
  def knockout_competition
    until challengers.empty?
      champion = battle(challengers)
      results << champion
      challengers.delete(champion)
    end
    results
  end

  def battle(monsters)
    # NOTE: モンスターが1体の場合は、不戦勝で渡されたモンスターを勝ちとする
    return monsters[0] if monsters.length < 2
    judge = ->(winner, monster) { Judge.new(winner, monster).call['winner'] }
    champion, challengers = monsters[0], monsters[1..-1]
    challengers.inject(champion) { |winner, monster| judge.call(winner, monster) }
  end

  def results_info
    text = "==== RESULT ====\n"
    results.each_with_index do |result, index|
      text << "No.#{index + 1}: #{result}\n"
    end
    text
  end
end

以下がモンスター同士の勝敗判定を行うClassです。開発時にAPIにリクエストを投げるのは検証にも時間がかかるのと先方のサーバーに負荷を書けてしまうので、ローカルで検証出来る開発用ロジックも用意して切り替え可能にしました。

require_relative './judge/api_strategy'
require_relative './judge/develop_strategy'

class Judge
  def initialize(blue, red)
    @red = red
    @blue = blue
  end

  attr_reader :blue, :red

  def call
    DevelopStrategy.call(red, blue) # NOTE: 開発用のMockストラテジ
    # ApiStrategy.call(red, blue) # NOTE: APIを実際に呼び出すストラテジ
  end
end

Q3 APIサーバーのアプリケーションに性能改善を目的としてキャッシュを導入...

APIサーバーのアプリケーションに性能改善を目的としてキャッシュを導入するとします。アプリケーションのどの部分に、どのような手法でキャッシュを導入するのか記述したうえで、なぜその手法を導入するのか、メリットやデメリットを挙げて説明してください。

今回のQ2のケースで考えるとして、以下のようなキャッシュ等が考えられるかなと思いましたけど、どうなんだろう・・・🤔

  • モンスター同士の対戦結果を対戦するモンスター名のペアをキーにKVストア等にキャッシュ
    • メリット: DBへのアクセス + 勝敗判定処理の時間を削減
    • デメリット: モンスターのパラメータ変更等による同一モンスターの勝敗結果が変わる場合にキャッシュのリフレッシュが必要。KVストア等を導入する + 運用していくコストが掛かる。
  • モンスター単位の強さの計算結果をテーブルにキャッシュしそれを利用して判定を行う
    • メリット: 勝敗判定前にモンスターの強さを計算する処理を行っている場合には、その処理時間を削減
    • デメリット: モンスターのパラメータ変更等、強さに影響がある変更が入った場合には再計算が必要

おわりに

あまりアルゴリズムとかはちゃんと勉強したことがないので、あまり自信ないですが、こういう企業が採用に使っている問題を使って勉強出来ると一石二鳥感があってありがたいですね🙏

cookpad-mart-careers.studio.site

OmniAuthのDeveloper Strategyを使って開発環境でOmniAuthを使った認証を試すMEMO

Twitter等の外部アカウントの認証を開発環境で試すのは秘匿情報を本番と開発環境で分けたり等、色々考慮することが多くて面倒だと思うのですが、OmniAuthのDeveloper Strategyを使うと簡単にOmniAuthを使った認証処理を試せそうなので使い方とかをMEMOしておきます📝

試しているOmniAuthのバージョンはomniauth (1.9.1)Railsrails (6.1.0)です。

Developer Strategyとは?

OmniAuthのREADMEにも記載されている通り、OmniAuthに内蔵された開発用のStrategyです。Developer Strategyを使っておいて、のちのち他のTwitter等のStrategyに切り替えるといったことが簡単に出来るようなもののようです。

One strategy, called Developer, is included with OmniAuth and provides a completely insecure, non-production-usable strategy that directly prompts a user for authentication information and then passes it straight through. You can use it as a placeholder when you start development and easily swap in other strategies later. https://github.com/omniauth/omniauth#an-introduction

Developer Strategyを使う準備

Developer Strategyを使うには他と同様にinitializerで以下を実行します。fieldsuid_fieldの値は任意の値に変更してください。

Rails.application.config.middleware.use OmniAuth::Builder do
  if !Rails.env.production?
    provider :developer, fields: [:email, :nickname, :user_id], uid_field: :user_id
  else
    # 本番で利用するtwitter等のその他認証プロバイダ用の設定を記述
  end
end

そしてCallbackを受け取れるようにroutesにpost '/auth/:provider/callback', to: 'sessions#create'を追記しときます。

Rails.application.routes.draw do
  get '/', to: 'posts#index'
  post '/auth/:provider/callback', to: 'sessions#create'
end

Developer Strategyのログインフォーム

http://localhost:3000/auth/developerにアクセスすると以下のフォームが表示されます。

f:id:madogiwa0124:20210102165336p:plain

<form method="post" action="/auth/developer/callback" novalidate="noValidate">
  <label for="email">Email:</label>
  <input type="text" id="email" name="email">
  <label for="nickname">Nickname:</label>
  <input type="text" id="nickname" name="nickname">
  <label for="user_id">User id:</label>
  <input type="text" id="user_id" name="user_id">
  <button type="submit">Sign In</button> 
</form>

このフォームはinitializerで定義したfieldsを元に動的にOmniAuthがフォームのVIEWを生成してくれます😳

実際に入力してSign Inを押すと入力値をパラメータにPOST "/auth/developer/callback"のリクエストが発生します。

Started POST "/auth/developer/callback" for 172.18.0.1 at 2021-01-02 16:46:31 +0900
I, [2021-01-02T16:46:31.340986 #1]  INFO -- omniauth: (developer) Callback phase initiated.
Processing by SessionsController#create as HTML
  Parameters: {"email"=>"test@example.com", "nickname"=>"test", "user_id"=>"111", "provider"=>"developer"}

Developer Strategyのログイン処理サンプル

あらかじめroutesでコールバックを受け取るpost '/auth/:provider/callback', to: 'sessions#create'を定義しているので、SessionsController#createに認証情報を取得してユーザーを作成 or 取得、セッションに格納するようなログイン処理を入れてあげればOmniAuthを使った認証を開発環境で簡単に試すことが出来ます✨

class ApplicationController < ActionController::Base
  def current_user
    @current_user ||= User.find_by(id: session[:user_id])
  end
end

class SessionsController < ApplicationController
  skip_before_action :verify_authenticity_token, only: :create

  def create
    auth_hash = request.env['omniauth.auth'] # 入力された認証情報を取得できる
    # 取得情報を元にユーザーを取得 or 作成するようなメソッドを定義しといてUserIdをsessionに保存
    session[:user_id] = User.find_or_create_from_auth(auth_hash).id 
    redirect_to '/'
  end
end

request.env['omniauth.auth']には以下のような形で入力値が入ってます🙌

# request.env['omniauth.auth']の値
{"provider"=>"developer",
 "uid"=>"111",
 "info"=>{"email"=>"test@example.com", "nickname"=>"test", "user_id"=>"111"},
 "credentials"=>{},
 "extra"=>{}}

OmniAuthのDeveloper Strategy、あまり使ったことなかったのですが非常に便利ですね✨

参考

www.rubydoc.info

RubyonRails: WebpackerなRailsアプリケーションでSourceMapをRollbarにアップロードするMEMO

WebpackerでRailsアプリケーションでエラー管理にRollbarを使用しているときに、デフォルトだとエラーが発生してもminify等がされたjsにリンクされてしまい何が起きているのか、よくわかりません。。。

そんなときにSourceMapをRollbarにアップロードするとエラー発生箇所が特定しやすくなるので、そのへんのやり方をメモしておきます📝

ちなみに今回の環境は以下です。

  • rails: 6.0.3.4
  • webpacker: 5.2.1

また前提として以下のドキュメントの通りBrowserJsのRollbarの設定は完了しているものとします。

docs.rollbar.com

SourceMapとは

ソースマップ は変換後のソースと元のソースを関連付けるファイルであり、ブラウザーが元のソースを再構成して、そのソースをデバッガーに提供できます。

ソースマップを使用する - 開発ツール | MDN

Webpackを利用したminified等、jsのソースコードは実際に実行されるときには元のソースコードから変換されていることが多いので、元のソースコードを生成するための情報をブラウザに提供するものといった感じと理解しました👀

SourceMapをアップロードする

SourceMapをアップロードするにはRollbarの特定のEndpointにminifiedしているjsのファイルごとにリクエストを投げる必要があります。

docs.rollbar.com

単一のjsにまとめているような運用なら以下のようなCI系の機能を使えば簡単に出来そうなのですが、Webpackerのデフォルトのようにエンドポイントごとにentryを分けるような設定の場合には、entryごとにリクエストを投げる必要があるので、結構たいへんです😓

github.com

circleci.com

そんなときに役に立つのがrollbar-sourcemap-webpack-pluginです📦

github.com

this.getAssets(compilation)でソースファイルとSourceMapを集めて、それぞれRollbarにUploadしてくれます📮

// [https://github.com/thredup/rollbar-sourcemap-webpack-plugin/blob/master/src/RollbarSourceMapPlugin.js#L131-L141]

  uploadSourceMaps(compilation) {
    const assets = this.getAssets(compilation);

    /* istanbul ignore next */
    if (assets.length > 0) {
      process.stdout.write('\n');
    }
    return Promise.all(
      assets.map(asset => this.uploadSourceMap(compilation, asset))
    );
  }

rollbar-sourcemap-webpack-pluginは以下のような形でinstallします。

$ yarn add rollbar-sourcemap-webpack-plugin

私は、以下ような感じにconfig/webpack/production.jsを修正しました。 各設定値の詳細はREADMEを参照してください📝

process.env.NODE_ENV = process.env.NODE_ENV || "production";

const RollbarSourceMapPlugin = require("rollbar-sourcemap-webpack-plugin");

// NOTE: `post_server_item`以上のscopeを持つtokenでないと権限エラーになる。
const token = process.env.ROLLBAR_POST_SERVER_ITEM_ACCESS_TOKEN;
// NOTE: 最新のcommit hashを設定
const codeVersion = process.env.SOURCE_VERSION;
// NOTE: minifiedされたソースコードの配置先
const publicPath = process.env.PUBLIC_PATH + "/packs";
const RollbarSourceMapPluginConfig = require("./plugins/rollbarSourceMapConfig");
const environment = require("./environment");

RollbarSourceMapPluginConfig = new RollbarSourceMapPlugin({
  accessToken: token,
  version: codeVersion,
  publicPath: publicPath,
  ignoreErrors: true, // NOTE: uploadに失敗してもdeployが失敗しないようにエラーを無視
});

// NOTE: SourceMapが無効になっているとupload時に以下のエラーが発生するっぽいので`hidden-source-map`を設定
// Rollbar: Error: Source map missing property 'names'
environment.config.merge({
  devtool: "hidden-source-map",
});

environment.plugins.prepend("RollbarSourceMapPlugin", RollbarSourceMapPluginConfig);

process.env.SOURCE_VERSIONにはdeploy時の最新のcommit hashを SourceMapをuploadするwebpackのbuild前に設定しておく必要があります。

CI系のサービスを利用してdeployしている場合には、すでに環境変数に入っているものもあるようです👀

CircleCI

CIRCLE_SHA1 The SHA1 hash of the last commit of the current build. https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables

GitHub

GITHUB_SHA The commit SHA that triggered the workflow. For example, ffac537e6cbbf934b08745a378932722df287a53. https://docs.github.com/en/free-pro-team@latest/actions/reference/environment-variables#default-environment-variables

Heroku

SOURCE_VERSION For git-push builds, this is the git commit SHA-1 of the source being built. https://devcenter.heroku.com/changelog-items/630

これで本番のWebpackのビルド時にSourceMapが送信されるようになりました🎉

RollbarにアップロードしたSourMapは、Project > Settings > Source Mapsで見ることが出来ます。

UploadしたSourMapを利用するように既存のRollbarの設定を修正

SourceMapがアップロードされるようになったら、次はError通知時にSourceMapが利用されるように設定を変更していきます⚙

変更した設定が以下の通りです、client部分が追加した設定になります。 詳細は以下のURLから公式ドキュメントを確認してください📝

https://docs.rollbar.com/docs/source-maps#2-configure-the-rollbarjs-sdk-to-support-source-maps

Rollbarはpayload.client.code_versionの値とアップロードされたSourceMapのversionで紐付けているようです🤝

const token = process.env.ROLLBAR_POST_CLIENT_ITEM_ACCESS_TOKEN;
const railsEnv = process.env.RAILS_ENV;
const codeVersion = process.env.SOURCE_VERSION;
const rollbarParamsValidation = () => !(railsEnv === "test") && token;

if (rollbarParamsValidation()) {
  let _rollbarConfig = {
    accessToken: token,
    captureUncaught: true,
    captureUnhandledRejections: true,
    payload: {
      environment: railsEnv,
      client: {
        javascript: {
          source_map_enabled: true,
          code_version: codeVersion,
          guess_uncaught_frames: true,
        },
      },
    },
  };

// Rollbar Snippet
// ...
// End Rollbar Snippet

これで以下のような感じでSourceMapを活用して、minified前の実際にエラーの発生したfileの行数で見ることが出来るようになりました🙌

f:id:madogiwa0124:20201129180228p:plain

参考

engineer.crowdworks.jp

qiita.com