👮

[Rails]ActiveModel::Serializerに引数を与えて任意のバリデーションチェックを行う

2022/04/11に公開

概要

ActiveModel::Serializer に引数を与えて任意のバリデーションチェックを行う方法を以下に示します。
基本的な ActiveModel::Serializer の使い方に関してはこの記事では触れません。

コード

以下のような User モデルを題材とします。

email, name はログインユーザー以外には見せたくないので、 masked_email, masked_name として空文字を返すようにしています。

app/models/user.rb

class class User < ActiveRecord::Base

  def masked_email
    ''
  end

  def masked_name
    ''
  end
end
CREATE SEQUENCE IF NOT EXISTS users_id_seq;

CREATE TABLE "users" (
    "id" int8 NOT NULL DEFAULT nextval('users_id_seq'::regclass),
    "email" varchar,
    "name" varchar,
    "profile_image_url" varchar,
    "screen_name" varchar,
    "created_at" timestamp NOT NULL,
    "updated_at" timestamp NOT NULL,
    PRIMARY KEY ("id")
);

Serializer は以下のように ActiveModel::Validations を include してバリデーションを定義しています。

引数は initialize でインスタンス変数にセットして、バリデーション invalid であれば、 raise_validation_error によって ActiveRecord::RecordInvalid を raise しています。

app/serializers/user_serializer.rb

class UserSerializer < ActiveModel::Serializer
  include ActiveModel::Validations

  attributes(
    :id,
    :email,
    :name,
    :profile_image_url,
    :screen_name,
    :created_at,
    :updated_at
  )

  validates :is_mask, boolean: true

  def initialize(object, options = {})
    super
    @options = options
    @is_mask = options[:is_mask]

    raise_validation_error if invalid?
  end

  def name
    return object.masked_name if is_mask

    object.name
  end

  def email
    return object.masked_email if is_mask

    object.email
  end

  private

  attr_reader :is_mask
end

引数、 is_mask が渡されている場合、 email, name をマスクして返すようにしています。

バリデーションに関して、 boolean を判定するバリデーションは以下のように実装しました。
これにより validates :is_mask, boolean: true で Boolean かどうか判定できるようになります。
nil を許容する場合は validates :is_mask, boolean: true, allow_nil: true とします。

app/validators/boolean_validator.rb

class BooleanValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if [FalseClass, TrueClass].include?(value.class)

    record.errors.add(attribute, ' はBoolean型で入力してください')
  end
end

使い方

1件の場合

serialized_user = ActiveModel::SerializableResource.new(
  User.first,
  serializer: UserSerializer,
  is_mask: true
)

複数件の場合

serialized_users = ActiveModelSerializers::SerializableResource.new(
  User.limit(3),
  each_serializer: UserSerializer,
  is_mask: true
)

型をキャストするパターン

引数を受け取って、バリデーション前に型をキャストするなら、以下のように initialize で行います。

  def initialize(object, options = {})
    super
    @options = options
    @is_mask = ActiveRecord::Type::Boolean.new.cast(options[:is_mask])

    raise_validation_error if invalid?
  end

デフォルト引数をセットするパターン

引数が渡されなかった場合のデフォルト引数をセットするなら、以下のように initialize で行います。

  def initialize(object, options = {})
    super
    @options = options
    @is_mask = options[:is_mask].nil? ? true : options[:is_mask]

    raise_validation_error if invalid?
  end

まとめ

以上、 ActiveModel::Serializer に引数を与えて任意のバリデーションチェックを行う方法でした。

バリデーションチェックを行うことで型や値のチェックができてアプリケーションがより堅牢に・表現力豊かになりますし、 ActiveModel::Validations を用いるとバリデーションを見慣れたスタイルでスマートに記述することができるので、積極的に使っていきたいです。

Discussion