Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

先日書いたように、マスターデータが混沌としてきていたのですが、もう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. よほどの理由がない限り、データベースでも外部キーは積極的にかけるほうが良いと考えます。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away