Railsのバリデーションにはpresence
のようなバリデーションヘルパーの他にカスタムバリデーションを作成できるので、カスタムバリデーションで今回の問題を解決できます。
RailsGuides カスタムバリデーション
https://railsguides.jp/active_record_validations.html#%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89
バリデーションの用件
まずはバリデーションの用件について検討します。
既に実装されているpresenceでは配列に要素が含まれているかの判定しか行われないため、""
(空文文字列)が入っている状態でもバリデーションが成功してしまっています。
ですので、今回は配列の要素に何かしらも文字列が含まれているかを判定していきます。
配列の要素に何かしらも文字列が入っていることを検証するためには、配列から空文字列を取り除いて配列に要素が残っているかチェックする方法、配列の中に空文字列が含まれているかチェックする方法などが考えられます。
どれを選択するかは渡されてくる配列と用件によって選んで良いと思います。
配列から空文字列を取り除いて配列に要素が残っているかチェック
text.reject(&:empty?).present?
Enumerableクラスのrejectメソッドを利用しています。各要素に対してempty?メソッドを実行しtrueを返す要素以外を返します。
https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/reject.html
rejectの返り値に要素が残っている場合はpresent?からtrueが返されるため文字列が入っていたと判断できます。文字列がない場合はfalseが返されます。
配列の中に空文字列が含まれているかチェックする方法
text.select(&:empty?).present?
今度は逆に空文字列が含まれているかをチェックしています。配列の中に空文字列が1つでも含まれている場合はtrueを返すため、1つも空文字列が含まれて欲しくない場合は利用できます。
カスタムバリデーション
カスタムバリデーションはバリデータークラスを作成し利用することもできますが、今回のフォームオブジェクトでのみ使用するのであればカスタムメソッドで良いと思います。
class FoodIngredientRecipe
include ActiveModel::Model
attr_accessor :title, :images, :cook_time_id, :cost_id, :comment, :user_id, :serving, :text
with_options presence: true do
validates :title
validates :images
validates :cook_time_id
validates :cost_id
validates :comment
validates :user_id
validates :serving
validates :text
end
validate :ensure_recipe_text
def save(amount)
food = Food.create(title: title, images: images, cook_time_id: cook_time_id, cost_id: cost_id, comment: comment, user_id: user_id, serving: serving)
text.length.times do |i|
recipe = Recipe.new
recipe.food_id = food.id
recipe.text = text[i]
recipe.save
end
end
private
def ensure_recipe_text
errors.add(:base, 'が空です。') if text.reject(&:empty?).present?
end
end
カスタムバリデーションはvalidate
メソッドにバリデーションで利用するメソッド名のシンボルを渡します。
今回は例では配列の中に何かしらの文字列が含まれていればバリデーションは成功、1つも文字列が含まれない場合はエラーとしました。
エラーに合致する場合はerrorsに対してエラーメッセージを追加することで、バリデーションヘルパーの振る舞いと同じようにエラーメッセージを取得できるようになります。