学習記録

たまには誰かの役にたったらいいな

Ruby on Rails (ActiveReocrd) モデルの上限値を取得する

概要

ActiveRecord でモデルの長さを取得する処理のメモ書き。

目的

Simple Form の 設定のファイルを、use に変えると、lengthバリデーションか、データベースのカラムの長さを設定してくれるらしい。

公式サイト

## Optional extensions
    # They are disabled unless you pass `f.input EXTENSION_NAME => true`
    # to the input. If so, they will retrieve the values from the model
    # if any exists. If you want to enable any of those
    # extensions by default, you can change `b.optional` to `b.use`.

    # Calculates maxlength from length validations for string inputs
    # and/or database column lengths
    b.optional :maxlength

    # Calculate minlength from length validations for string inputs
    b.optional :minlength

これと同じように、モデルのカラム長さを取得したい。

方法

前提

例えば、こんな感じのActiveRecordモデルがあったとする。

class Post < ApplicationRecord
    validates :title, presence: true, length: {minimum: 1, maximum: 20}
    validates :content, presence: true, length: {minimum: 1, maximum: 500}
end

そして、データベースはこんな感じで定義されているとする。

CREATE TABLE `posts` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) DEFAULT NULL,
  `content` text,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

バリデーションの長さ

バリデーションに指定された長さは、validators_onを使えば取得できる。下記に、rails consoleでの実行結果を記載する。他にも、例があったけど個人的に一番これが好みだった。

Loading development environment (Rails 5.2.4.3)
irb(main):001:0> Post.validators_on(:title).detect { |v| v.is_a?(ActiveModel::Validations::LengthValidator) }.options[:minimum]
=> 1
irb(main):002:0> Post.validators_on(:title).detect { |v| v.is_a?(ActiveModel::Validations::LengthValidator) }.options[:maximum]
=> 20
irb(main):003:0> Post.validators_on(:content).detect { |v| v.is_a?(ActiveModel::Validations::LengthValidator) }.options[:minimum]
=> 1
irb(main):004:0> Post.validators_on(:content).detect { |v| v.is_a?(ActiveModel::Validations::LengthValidator) }.options[:maximum]
=> 500

参考:ruby on rails - How to get the maximum length configured in an ActiveRecord validation? - Stack Overflow

このメソッドのソースコードはこちら。

def validators_on(*attributes)
  attributes.flat_map do |attribute|
    _validators[attribute.to_sym]
  end
end

SimpleForm ではここで使っている。

def attribute_validators
  object.class.validators_on(attribute_name)
end

データベースカラムの長さ

次に、データベースからカラムの長さを取得するには、column_for_attribute を使う。下記に、rails consoleでの実行結果を記載する。長さは、limitを使う。typeを使えば、型も取得できる。

irb(main):040:0> Post.column_for_attribute(:title).type
=> :string
irb(main):041:0> Post.column_for_attribute(:title).limit
=> 255
irb(main):043:0> Post.column_for_attribute(:content).type
=> :text
irb(main):044:0> Post.column_for_attribute(:content).limit
=> 65535

column_for_attribute の他に、type_for_attributeでも取得できる。これも、同様にtypeを使えば型、limitで長さを取得する。

irb(main):009:0> Post.type_for_attribute('title').type
=> :string
irb(main):010:0> Post.type_for_attribute('title').limit
=> 255
irb(main):011:0> Post.type_for_attribute('content').type
=> :text
irb(main):012:0> Post.type_for_attribute('content').limit
=> 65535

ソースコードはこちら。

# ActiveRecord / ModelSchema
# l.392 ~ l401
def type_for_attribute(attr_name, &block)
  attr_name = attr_name.to_s
  attr_name = attribute_aliases[attr_name] || attr_name

  if block
      attribute_types.fetch(attr_name, &block)
  else
      attribute_types[attr_name]
      end
end

# l.416 ~ l421
def column_for_attribute(name)
  name = name.to_s
  columns_hash.fetch(name) do
    ConnectionAdapters::NullColumn.new(name)
  end
end

Simple Form では、どちらでも対応できるようにか、どっちのメソッドも使っている。

ソースコードはこちら。

def find_attribute_column(attribute_name)
  if @object.respond_to?(:type_for_attribute) && @object.has_attribute?(attribute_name)
    @object.type_for_attribute(attribute_name.to_s)
  elsif @object.respond_to?(:column_for_attribute) && @object.has_attribute?(attribute_name)
    @object.column_for_attribute(attribute_name)
  end
end

このfind_attribute_column で取得し、Inputs/Baseクラス内で取得したインスタンスから、limitで上限値を取得する。これはSimpleForm でも同じ。

あとがき

DBのカラムの最大値が取れるのは、使えるところがありそう。あるはず。入力のmaxlength の一部だけこの値を指定するとか…。