Rails4でhas_manyな関連オブジェクトの上限数を設定&テストする
ActiveRecordのhas_manyな関連オブジェクトに上限数を設定したかったのでやってみました。
ActiveRecord / Rails / Ruby
この記事ではBlog::Post#tags
の数を5つまでに制限する場合のサンプルコードを紹介します。
モデルでのValidation設定
まずモデル側でpost_tags
にLengthValidation
を設定します。
# app/models/blog/post.rb
class Blog::Post < ActiveRecord::Base
MAX_POST_TAGS_LENGTH = 5
has_many :post_tags
has_many :tags, through: :post_tags
validates :post_tags, length: {maximum: MAX_POST_TAGS_LENGTH}
end
ロケール辞書の設定
Validation対象のフィールド名やエラーメッセージを設定します。
# config/locales/models/blog/post/ja.yml
ja:
activerecord:
errors:
models:
blog/post:
attributes:
post_tags:
too_long: 'は%{count}個までしか登録できません'
models:
blog/post: ブログ記事
attributes:
blog/post:
id: 管理ID
post_tags: タグ
テストケースの追加
正しくValidationできることをテストします。
#spec/models/blog/post_spec.rb
describe Blog::Post, type: :model do
context 'with validations' do
it do
should validate_objects_length_of(:post_tags).
is_at_most(Blog::Post::MAX_POST_TAGS_LENGTH).
with_message("タグは#{Blog::Post::MAX_POST_TAGS_LENGTH}個までしか登録できません")
end
end
end
validate_objects_length_of
カスタムマッチャの追加
テストでさらっとvalidate_objects_length_of
マッチャを使いましたが、これはカスタムマッチャなので下記のコードをspec/support/matchers.rb
に追加します。
# spec/support/matchers.rb
RSpec::Matchers.define :validate_objects_length_of do |field|
def is_at_most(size)
@max_size = size
self
end
def with_message(message)
@message = message
self
end
match do |model|
model_factory_name = model.class.table_name.singularize
association_name = field
association =
model.
class.
reflect_on_all_associations(:has_many).
find { |i_association| i_association.name == association_name }
if association.nil?
@failure_appendix = "(#{association_name.inspect} has-many association does not exist)"
return false
end
factory_args = [association.table_name.singularize]
record = create(model_factory_name)
if @max_size
((record.send(association_name).size)..@max_size).each do |size|
unless record.valid?
@failure_appendix = "(invalid with #{size} #{'object'.pluralize(size)})"
return false
end
record.send(association_name) << build(*factory_args)
end
if record.valid?
size = @max_size + 1
@failure_appendix = "(valid with #{size} #{'object'.pluralize(size)})"
return false
end
end
if @message
if record.errors.full_messages != [@message]
@failure_appendix = "(with message #{record.errors.full_messages.inspect})"
return false
end
end
true
end
description do
description_prefix = "validate #{field} has a length of"
conditions = []
conditions << "at most #{@max_size}" if @max_size
conditions << "with message #{@message.inspect}" if @message
"#{description_prefix} #{conditions.join(', ')}"
end
failure_message { "expected to #{description}, but not#{@failure_appendix}" }
end
テストを実行すると
Blog::Post
with validations
should validate post_tags has a length of at most 5, with message "タグは5個までしか登録できません"
Finished in 0.24698 seconds (files took 5.23 seconds to load)
1 example, 0 failures
となって無事成功しました。