発端: concernモジュールの命名をどうするか
ここではActiveSupport::Concernをextendしたモジュールのことをconcernモジュールやconcernと呼ぶ。
「Concernに何を実装すべきかは…非常に曖昧」1である。さらに、Railsアプリのメインであるルーティングからモデルまでは命名規約がかっちり決まっているが、concernに明示的な規約はない。concernも基本的にはRubyのモジュールなので、Enumerable
やComparable
のような組み込みモジュールと似たような命名にするべきだろうか。実際、concernの名前に動詞+ableを使うのはよくやると思う。
Rubyのプレーンなモジュールとの違いとして、ActiveSupport::Concernにはincluded
やclass_methods
が存在する。これらの機能があることで、concernはコールバックの定義やクラスマクロの定義に使われるようなフォースが働いているように見える。そこで、そのような特徴を踏まえたうえでconcernならではの命名があるか考えてみる。
横断的関心事
concernとはいったいなんだったのかというのをふりかえってみると、アスペクト指向プログラミング(AOP)における横断的関心事(cross-cutting concern)が起源の1つといえそう2。
AOPにおける横断的関心事についてはこちらが詳しい。
横断的関心事の分類の1つとして静的か動的かというものがある。静的な横断的関心事はインタータイプ宣言の機能を担う。AspectJなどではクラスに新たなメソッドやプロパティを追加する仕組みのことをインタータイプ宣言と呼んでいるようだ。一方、動的な横断的関心事はポイントカット+アドバイスの機能を担う。こちらは、コードを実行すると発生する特定のイベント(メソッド呼び出しなど)にあわせてアドバイスと呼ばれるコードを実行する。
それぞれ、concernだと次のようなものにあたるといえそう。
- 静的な横断的関心事は、スコープやクラスマクロを追加するconcern
- 動的な横断的関心事は、コールバックを追加するconcern
concernの事例
Basecampの事例
concernといえば、DHHがかつて示していたBasecampのコード例が有名。
I heard you liked concerns, so I put a few of them in Recording 😄 (This is the meta-data/meta-capabilities class for all concrete content classes in Basecamp). pic.twitter.com/XfXAdyXzJk
— DHH (@dhh) February 15, 2018
This is now in rails/master 👍. Promise I'll still do a video on how we use this pattern in Basecamp 3 and HEY, but here are a few teasers. https://t.co/FkQU66ETO3 pic.twitter.com/Cua3jyOtEu
— DHH (@dhh) 2020年5月23日
これ自体の賛否は置いておくとして、includeしているモジュールがすべてcocnernモジュールだと仮定すると、その名前には次のようなバリエーションがある。
- 名詞
- 単数形、複数形
- 動詞
- 接尾辞がable
- スコープやメソッドを追加するために使われているように見える
- 現在形(原型と三単現両方ある?)
- 過去分詞
- 接尾辞がable
想定しうるバリエーションがあらかた存在する状況で、これだけだと明確なルールが見出しにくい。
api.rubyonrails.orgの事例
ActiveSupport::ConcernはFoo
モジュールのような単純な例だけだったので、concerning
のほうを見ると、EventTracking
という名詞のconcernが紹介されている。このconcernはメソッドを追加しつつコールバックも定義している。
AOPの観点でconcernを作る
実事例を見つつ、さきほど書いたAOPの静的・動的横断的関心事によるconcernの分類に基づくと、ある程度納得感のある名前を持つconcernが作れそうに見える。
モジュールやクラスが静的な横断的関心事にあたるconcernをincludeすると、その時点でconcernが定義しているクラスマクロ、スコープ、またインスタンスメソッドが使えるようになる。つまり、それらを利用するか否かにかかわらず使えるようにはなる。とすると、Rubyのプレーンなモジュールと同じく動詞+ableにするのがよさそうに見える。
たとえば、パーフェクトRuby on Railsに載っているようなTaggable
なら付与したタグのためにtagged_with
スコープやtags
などの関連が追加されるだろうし、UserSessionIssueable
ならログインセッションをセットアップするメソッドlogin_as_user
が追加されるだろう。
module Taggable extend ActiveSupport::Concern included do has_many :tags #, ... end class_methods do def tagged_with # ... end end end module UserSessionIssueable extend ActiveSupport::Concern def login_as_user # ... end end
一方、モジュールやクラスが動的な横断的関心事にあたるconcernをincludeすると、その時点でconcernが持つコールバックを必要に応じて自動で実行するようになる。「できるようになる」よりは「する」なので、concernのモジュール名は動詞の現在形にしておくのがよさそう。また、アトリビュートも追加されて、なんらかの状態を持たされるなら過去分詞にするといいかもしれない。
たとえば、NotifyImportantOperation
なら重要な操作をどこかに自動で通知するコールバックを定義するだろうし、Obfuscated
なら特定のアトリビュートを自動で難読化したものを新たにアトリビュートとして持つようにできるだろう。
module NotifyImportantOperation extend ActiveSupport::Concern included do before_action :notify, only: [:create, :destroy] end def notify # ... end end module Obfuscated extend ActiveSupport::Concern included do attr_reader :obfuscated after_validation :set_obfuscated end def set_obfuscated # @obfuscated = ... end end
うまく命名できるconcernになっているかどうか考えることは、大きすぎたりモジュールとしての意味のまとまりが希薄なconcernを作らないようにするのにも役立ちそうだ。
今回のAOPの考えかただと、concernに名詞で命名するとしっくりくる状況があまり思いつかなかった。一方で、EventTracking
の例のように関連とコールバック両方を定義するようなときは、利用可能なメソッドの定義と自動で実行されるコールバックの両方が手に入るので、上記の命名方法よりは適切に名詞で命名したほうがわかりやすいというのはあるかもしれない。
-
『パーフェクトRuby on Rails【増補改訂版】』13-1-3↩