Railsアプリケーション全体の定数の定義場所は「環境に依存するか」「アプリケーション固有か」で決める

業務で主に Ruby On Rails での開発を行っていますが、そこで得た知見や、失敗への対応などについて記します。

今回は「アプリケーション全体での定数の定義場所をどこにすべきか」についてです。

## (前提として) この記事での「定数」という用語について

以下、全て「定数」と記していますが、意図としては「アプリケーション全体で共有する固定値」の意味であり、定数以外にも「クラスメソッド」などで定義される場合も含みます。

## 状況

Railsアプリケーション全体で「定数」を定義する必要性が時々ありますが、おもには以下のような用途になります。

### アプリケーション全体の前提となるような値

これは Rails自体も一定のものを提供していて、たとえば `Rails.root` は「アプリケーション起動のベースとなるパス」を返します。他にも `Rails.logger` など、いくつか同様のものが存在します。

同様に、アプリケーションのどの部分からも参照したい値を、自ら定数として定義する場合が有ります

### 環境毎に割り当てられた値

アプリケーションの開発とリリースには「開発環境」「統合環境」「ステージング環境」「本番環境」など様々な環境で行われますが、それぞれの環境毎に値を取り替えて実行する場合もあります。

場合によっては DI(依存性の注入)の仕組みを用いてクラスやモジュール自体を取り替える場合も有りますが、単に「値だけ取り変えれば良い」という場合も多くあります。

これも Rails自体には `Rails.env.development?` や `Rails.env.production?` といった判定メソッドでの処理分岐の機能が用意されています。

## それは「アプリケーション全体での定数」として定義すべきなのか

このような定数はアプリケーションの様々な場所(各コンポーネント)から「参照したい値」として定義されていることになりますが、ここでは例示として「ModelとViewの両方で使う値」とします。

View側のコード例で書くと、以下のような感じです(恣意的な例です)。

```haml
-# Hamlでの記載
- Item.find_each do |item|
  %dl
    -# この CATEGORIES が「アプリケーション全体での定数」
    - CATEGORIES.each do |category|
      %dt= category
      %dt= item[category]
```

この「アプリケーション全体での定数」への参照の形式を図に書くと、以下のようになります。

![MoelとViewの双方から定数を参照.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1085125/f8d9caee-34bd-9e86-5a29-2ce0acfdfe17.png)

Modelと Viewで同じ「定数」を参照していますから、そちらに矢印(依存関係)が引かれています。一方、アプリケーション自体の機能として「Viewは Modelに依存している」構造になりますので、その間の(緑の)矢印も引かれています。

図からも分かるように、このような関係性にある場合には、必ずしも「同じ定数を参照する必要は無い」ことが分かります。

View側で定数を参照しているのは、「Model側からのデータに、さらに定数を組み合わせて処理しているから」であって、そもそも「Model側のデータが『定数を参照しなくても済む』ような状態になっていれば良い」ということになります。

同じく View側のコード例で書くと、以下のような感じです

```haml
- Item.find_each do |item|
  %dl
    - item.each_category do |category|
      %dt= category
      %dt= item[category]
```

View側から「アプリケーション全体での定数」への参照が無くなりました。

![Model側だけ定数を参照する.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1085125/f09d53e7-13d7-0e03-3aeb-9c4e0e86ec9c.png)

これはあくまでも一例ですが、このように「コンポーネント間の依存関係」があらかじめ存在する場合は、定数の参照もそちらに寄せてしまうことで、「アプリケーション全体での定数」を定義する必要性が無くなります。

## 「アプリケーション全体での定数」はどこで定義すべきなのか

いよいよ本題ですが、最終的に「やはり定義が必要になった」アプリケーション全体での定数をどこで定義すべきかについて、弊社では以下のような方針を検討しています。

\#|定義場所            |環境に依存するか? |アプリケーションに特化しているか?
---|--------------------|------------------|---------------------------------
A|modelsやservicesなど|×どの環境でも不変|○このアプリケーションのみでの利用を前提
B|config以下          |○.envでの変更可  |○このアプリケーションのみでの利用を前提
C|lib/exts以下        |×どの環境でも不変|×gemなどの形で分離が可能
D|(定義しない)        |○.envでの変更可  |×gemなどの形で分離が可能

**「環境に依存するか?」** は、「開発環境」「統合環境」「ステージング環境」「本番環境」などの実行環境によって値が変わるかを示します。

特に開発環境については、それぞれの実行環境の都合で適宜値を付け直すのが相応しく、弊社では [dotenv](https://github.com/bkeepers/dotenv) の仕組みでそれを実現しています。

その dotenv で定義された環境変数はあくまで `config` ディレクトリ以下でしか取り扱わず、アプリケーションから直接参照することは避けています(表の **`B`**)。これは、dotenv 以外のライブラリに切り替えた場合にも対応出来るようにするためです。

**「アプリケーションに特化しているか?」** は、その定数の使用箇所が「gemとして分離不可能か」という意味としています。

アプリケーションを開発する際、多少なりとも「そのアプリケーション固有では無い、他のアプリケーションでも再利用可能な実装」が生じます。

それらは時期に応じて gem などに分離しておけば、コンポーネント間の依存を無くし、以後の開発容易性に貢献するようになります。

それらのコンポーネントは `lib/exts` 以下に配置しているので、定数もそちらで定義するようにしています(表の **`C`**)。

残るは「環境に依存せず」「このアプリケーションのみでの利用」というものですが、それらは(dotenvなどで扱う必要は無いため)、app/models や app/services で定義されることを方針としています(表の **`A`**)。

結果的にこれらは「アプリケーション全体での定数」という扱いよりは、それらのモデルやサービスでの定義と見做されるようになります。

最後に、これらいずれでもなく、そもそも「アプリケーション全体での定数」として定義すべきではないものとして「環境に依存する」が「このアプリケーションのみでの利用でもない」ものを挙げています(表の **`D`**)。

これら両者の要件が両立するとは考えにくいので「定義しない」という扱いとしています。

## まとめ

「アプリケーション全体での定数」定義も、その用途や影響範囲を考慮し、「そもそも定数として定義すべきか」「定数として定義するのであれば、どの箇所で行うのか」という話でした。

皆様のアプリケーション開発の参考になりましたら幸いです。