🤔

定数なのに動的な値を渡せるらしい

2023/12/09に公開

久々の投稿です。(どうでもいい)

はじめに

ふと、文字列に動的な値を渡してメッセージを表示できないだろうか、と考えた。
Railsのソースコードにその答えはあった。

TL;DR

Rails本家に倣って、定数に対して動的に値を渡す(%{})ことが現状の最善策か。

Railsの該当ソースコード(一例)

https://github.com/rails/rails/blob/900dc3280932df4a614cfa06b59959f249e65ce1/activerecord/lib/active_record/enum.rb#L386-L394

早速使ってみる

例えば、以下のようなエラーを出力する処理があったとする。

exceptions.rb
class Exceptions
  BASE_MESSAGE = 'An %{error_type} occurred. Please try again later.'

  def self.raise_argument_error
    raise ArgumentError, BASE_MESSAGE % { error_type: 'ArgumentError' }
  end
end

こいつを実行すると以下のような結果が得られる。

irb(main):001> Exceptions.raise_argument_error
exceptions.rb:5:in `raise_argument_error': An ArgumentError occurred. Please try again later. (ArgumentError)

使ってみて

「おぉ〜すげえ〜」と思ったと同時に、また疑問に思った。

そもそも定数とはなんだ?値が変わらないからこその定数なのではないか?

そもそも定数とは

プログラミングにおいて定数(「ていすう」または「じょうすう」、英: constant)とは、変数同様プログラムのソースコードにおいて、扱われるデータを一定期間記憶し必要なときに利用できるようにするために、データに固有の名前を与えたものである。 ただし変数とは異なり、一度初期化するとその内容(値)を変更することはできない。よって、内容が変化しないことが保証される名前が必要なときに使用される。
引用: https://ja.wikipedia.org/wiki/定数_(プログラミング)

「内容が変化しないことが保証される名前」と定義すると、動的に値を割り当てることは、内容が変化することと同義なのではないか、とも取れる。

他に方法は?

I18n

I18n を応用した使い方もある。
例えば以下のような記述の仕方。

ja.yml
---
ja:
  # 省略
  errors:
    argument_error: "An %{error_type} occurred. Please try again later."
exceptions.rb
class Exceptions
  def self.raise_argument_error
    raise ArgumentError, I18n.t('errors.argument_error', error_type: 'ArgumentError')
  end
end

こいつを実行すると、同じ結果が得られる。

irb(main):001> Exceptions.raise_argument_error
/app/app/lib/exceptions.rb:3:in `raise_argument_error': An ArgumentError occurred. Please try again later. (ArgumentError)

しかし、I18nの本来の役割としては、以下の通りである(はず)

Railsアプリケーションのこれらの部分の「ローカライズ」とは、欲しい言語の文字列について翻訳済みの値(訳文)を定義することを指します。
引用: Railsガイド 国際化

そうなると、ここではあまり良い使い方とは言えないのではないか。

メソッド

これは言うまでもなく、メソッドの中で式展開をして動的に内容を変える。
文字列だけを持ったメソッドを定義するのは、違和感がある。

辿り着いた先は

結局、Railsに従って、定数に対して動的に値を渡すことが現状の最善策な感じがする🧐
良い案がある方、募集中です。。。

Discussion