Rails
MySQL
ActiveRecord
データベース
テーブル定義

Railsで、テーブルのメタデータを取得する

先日書いたように、マスターデータが混沌としてきていたのですが、もう1つの対策として、「マスターデータやテーブル定義を確認できるツールを作ろう」という考えも出てきました。

そして調べたところ、Rails内部からデータベースの定義情報にアクセスすることも十分可能だったので、「別に確認ツールを作るより、そのままRailsに乗っかってしまったほうが手っ取り早い」と判断して、Rails内部にそのような開発ツールを構築しました。ここでは、そのツール作成に使った、メタデータの取得手段をまとめておきます。

なお、バックエンドとしてはRDBMS、特にMySQLを前提としています。MongoidなどActiveRecord以外では役に立たないでしょうし、RDBMSが違う場合にも細部が異なってくるかもしれません。

2通りの取得方法

RDBMSで作ったテーブルをRailsのモデルとして使う場合、改めて言うまでもありませんが、同じデータ群に「RDBMSのテーブル」と「ActiveRecordのモデル」という2つの側面が生じます。なので、メタデータの取得に際しても、「RDBMSのテーブルとして取得する」方法と、「ActiveRecordのモデルから取得する」という2通りの方法が存在することがあります。ただし、状況によっては両者で意味合いが異なってくることがあります。

テーブル(モデル)のリスト

まずは、処理対象となるテーブル・モデルのリストを取得する方法です。

RDBMS経由

ActiveRecord::Base.connection.tables # => テーブル名が文字列で入った配列が返ってくる

Railsから行う場合と比較して、メリットは「Railsのモデルがないテーブルも問題なく取得できる」、デメリットは「標準的でない名前のモデルを使っている場合、モデルクラスが取れない」という点です。

ActiveRecord経由

ActiveSupportでClass#descendantsが入っていますので、これを使って取得できます。

# 抽象クラスは除外
ApplicationRecord.descendants.reject(&:abstract_class?)

メリットとしては、「そのままActiveRecordとして使えるクラスが手に入る」「STIなどで、標準的でない名前になっているモデルも取得可能」ということがあります。一方で、config.eager_load=falseの場合に、その時点で読み込まれていた分しか拾えない点には要注意です。

列の取得

テーブルが分かれば、どんな列があるのかも取得したくなります。モデル/テーブルの指定方法を除けば、列に関してはほぼ同じものが取れます。

# DBから
ActiveRecord::Base.connection.columns(:some_models)

# モデルから
SomeModel.columns

取れたColumnオブジェクトには、name(列名)、sql_type(SQLでの型)1null(NULLを入れられるか)、default(デフォルト値)などの情報があります。

インデックスの取得

Railsの世界からインデックスを直接アクセスする用事がないこともあってか、インデックスに関する情報はDB経由で取るしかありません。

ActiveRecord::Base.connection.indexes(:some_models)

これでIndexDefinitionオブジェクトの配列が得られます。オブジェクトにあるメソッドとしては、name(インデックス自体の名前)、columns(かかっている列名の配列)、unique(ユニークインデックスかどうか)、using(インデックスの種類)などがあります。

リレーションの取得

リレーションに関しては、DB経由とモデル経由で取れるものが大きく異なってきます。

RDBMS経由

やはり、ActiveRecord::Base.connectionから呼び出します。

ActiveRecord::Base.connection.foreign_keys(:some_models)

これは、データベースに対しての取得なので、「DB上で外部キーをかけたもの2」、そして「自分のテーブルに制約がかかるもの(つまり、belongs_toなもの)」しか取れません。

ActiveRecord経由

SomeModel.reflect_on_all_associations

こちらはDBを読むわけではなく、モデルに宣言されたhas_manybelongs_tohas_many through:などのリスト、という位置づけです。klassで相手先クラスが取得できます。

脚注


  1. MySQLにBoolean型がないので、それはtinyint(1)で代用しています。 

  2. よほどの理由がない限り、データベースでも外部キーは積極的にかけるほうが良いと考えます。