LoginSignup
dai4869
@dai4869 (dai)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

[Rails]空の配列が文字列として認識されてしまいます。。。

解決したいこと

Ruby on Railsで料理投稿のようなWebアプリをつくっています。
投稿機能の実装の際に、以下の問題が発生しました。

ストロングパラメーターで配列を扱う際に、空の配列が文字として認識されてしまい、
バリデーション(presence true)が機能せず困っております。
空の配列が認識されず、しっかりとバリデーションが機能する方法教えていただけると助かります。

発生している問題

テキストが空の状態で料理投稿を実行すると上記画像のような状態が発生してしまいます。
[""]がテキストとして認識されて、テキストエリアに表示されてしまいます。
想定する挙動としては、空で送信しているのでバリデーション(presence true)が働いて、エラーメッセージが表示される想定なのですが、[""]がテキストとして認識されバリデーションが機能していません。

該当するソースコード

foods_controller.rb
  def new
    @food = FoodIngredientRecipe.new
  end

  def create
    @food = FoodIngredientRecipe.new(food_params)
    if @food.valid?
      @food.save
      redirect_to root_path
    else
      render :new
    end
  end

  private
  def food_params
    params.require(:food_ingredient_recipe).permit(:title, :cook_time_id, :cost_id, :comment, :serving, mages: [], text: []).merge(user_id: current_user.id)
  end

フォームオブジェクトを使用して、複数テーブルに保存しています。
また配列としてデータを保存したくないのでループして一つ一つ保存しています。

food_ingredient_recipe.rb
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

  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
end
new.html.erb
 <div class="form">
   <%= f.text_area :text, name: "food_ingredient_recipe[text][]", class:"recipe-text", placeholder:"例) 玉ねぎは1cmの厚さの輪切りにし、小麦粉大さじ1をまぶす。" ,rows:"4" ,maxlength:"100" %>
 </div> 

自分で試したこと

強制的にエラーを発生させ、パラメータの中身等を確認したところ、パラメータとして送信はされているようです。
しかし、空の場合も[""]が送信されていることになってしまっているようです。

0

2Answer

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に対してエラーメッセージを追加することで、バリデーションヘルパーの振る舞いと同じようにエラーメッセージを取得できるようになります。

1

Your answer might help someone💌