HiveBrain v1.2.0
Get Started
← Back to all entries
patternrubyrailsMinor

Rails validating enum

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
railsenumvalidating

Problem

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:

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

Solution

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.

Code Snippets

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

Context

StackExchange Code Review Q#129775, answer score: 2

Revisions (0)

No revisions yet.