2
\$\begingroup\$

The Ruby on Rails framework doesn't allow you to validate enums submitted through a form in any sane manner. Evidently, enums are meant to be used to maintain internal application state, and are not supposed to be user-facing. Well, in my case it doesn't matter too much, but I still want validations *just in case*.

Here is a widely-used enum:

class Language < ActiveRecord::Base
    self.abstract_class = true

    enum language: {
        arabic: 0,
        chinese: 1,
        english: 2,
        french: 3,
        german: 4,
        italian: 5,
        japanese: 6,
        russian: 7,
        spanish: 8,
        other: 9
    }
end

In my schema, the field using this enum is a text array: t.text "audio_languages", default: [], allowing multiple languages to be selected through the form.

In the form, the values are added as a list of check boxes:

HTML form

In the controller, the values for that field come in as:

"audio_languages"=>["English", ""]

Notice the last array entry, "". That's just how it works I guess, nothing I've tried gets rid of that, so I have to handle it in the validation.

In the validation, a language needs to be chosen, and the chosen language has to be present in the language enum:

def validate_audio_languages

    # Remove the empty string, which is an artifact of form submission of an array
    # field, during validation
    audio_languages.reject! { |l| l.empty? }

    if !audio_languages.any?
        errors.add(:audio_languages, "need to select a language")
    end

    # If any of the audio languages is invalid, add the error and break
    audio_languages.each do |al|
        valid = false
        Language.languages.each do |l, i|
            valid = true if (al.downcase == l.downcase)
        end

        if !valid
            errors.add(:audio_languages, "not a valid language selection")
            break
        end
    end
end

I'm concerned about the length of the function more than anything. Surely there's an easier way to do this kind of simple validation, especially to validate whether or not a value is actually in the enum. Any help in dealing with this issue is much appreciated!

\$\endgroup\$
2
\$\begingroup\$

You can use Ruby's array intersection and check for matches. The empty string passed in your params is irrelevant.

def validate_audio_languages 
    if audio_languages.any?
        enums = Language.languages.map(&:first)
        matches = enums & audio_languages.map!(&:downcase)
        matches.present? ? matches : errors.add(:audio_languages, "not a valid language selection")
    else
        errors.add(:audio_languages, "need to select a language")
    end
 end

You can shave off a couple more lines at the expense of readability, but it pays to keep things simple and readable.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.