はじめに
今回はActiveRecord::Base.connectionのcolumn_exists?メソッドの内部実装とユースケースについて書いてみたいと思います。
環境
Ruby 2.6.6
Rails 5.2.6
内部実装
まずは実装箇所を調べてみます。
$ bundle exec rails c
$ ActiveRecord::Base.connection.method(:column_exists?).source_location
=> ["/bundle/gems/activerecord-5.2.6/lib/active_record/connection_adapters/abstract/schema_statements.rb", 132]
実装箇所はこちらでした。
# Checks to see if a column exists in a given table.
#
# # Check a column exists
# column_exists?(:suppliers, :name)
#
# # Check a column exists of a particular type
# column_exists?(:suppliers, :name, :string)
#
# # Check a column exists with a specific definition
# column_exists?(:suppliers, :name, :string, limit: 100)
# column_exists?(:suppliers, :name, :string, default: 'default')
# column_exists?(:suppliers, :name, :string, null: false)
# column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
#
def column_exists?(table_name, column_name, type = nil, options = {})
column_name = column_name.to_s
checks = []
checks << lambda { |c| c.name == column_name }
checks << lambda { |c| c.type == type } if type
column_options_keys.each do |attr|
checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
end
columns(table_name).any? { |c| checks.all? { |check| check[c] } }
end
第一引数にテーブル名を、第二引数にカラム名を、それ以降はオプションで指定できるようになっていました。
column_options_keysはprivateメソッドになっており、
def column_options_keys
[:limit, :precision, :scale, :default, :null, :collation, :comment]
end
マイグレーション時に指定できるオプション(7種類もあったのか、、、)を配列で返すメソッドになっていました。
使い方とユースケース
使い方
実際に挙動を確認してみました。前提として、emailというカラムを持つUserモデルが定義されていて、default値が「""」、null: falseが設定されているものとします。
# emailカラムが定義されているかどうか
$ ActiveRecord::Base.connection.column_exists?(:users, :email)
=> true
# passwordカラムは定義されていないのでfalseが返る
$ ActiveRecord::Base.connection.column_exists?(:users, :password)
=> false
# emailカラムはstring型なので、第三引数にintegerを渡すとfalseが返る
$ ActiveRecord::Base.connection.column_exists?(:users, :email, :integer)
=> false
# emailカラムのデフォルト値は "" なので、trueが返る
$ ActiveRecord::Base.connection.column_exists?(:users, :email, :string, default: "")
=> true
# emailカラムは null: false なので、trueが返る
$ ActiveRecord::Base.connection.column_exists?(:users, :email, :string, null: false)
=> true
# default: "" と null: false の両方を指定してもOK
$ ActiveRecord::Base.connection.column_exists?(:users, :email, :string, null: false)
=> true
実務上で第三引数まで設定して呼び出すことは稀かと思いますが、柔軟なインターフェイスになっている印象です。
ユースケース
個人的には、フロントから渡ってきたパラメータに応じて、走査するテーブルを変えたい時に使えるかなと思っています。
例えば、複数のテーブルを用いて表示されているデータをソートしたい、となった場合、ソート対象のデータによっては走査するテーブルを分岐させなくてはいけないかもしれません、そのような場合に、column_exists?メソッドを用いて分岐を行うのがアリなのではないかと思いました。
まとめ
「ActiveRecord完全に理解した」い。(願望)