简体   繁体   中英

How do i specify and validate an enum in rails?

I currently have a model Attend that will have a status column, and this status column will only have a few values for it. STATUS_OPTIONS = {:yes, :no, :maybe}

1) I am not sure how i can validate this before a user inserts an Attend? Basically an enum in java but how could i do this in rails?

Now that Rails 4.1 includes enums you can do the following:

class Attend < ActiveRecord::Base
    enum size: [:yes, :no, :maybe]
    # also can use the %i() syntax for an array of symbols:
    # %i(yes no maybe)
    validates :size, inclusion: { in: sizes.keys }
end

Which then provides you with a scope (ie: Attend.yes , Attend.no , Attend.maybe for each a checker method to see if certain status is set (ie: #yes? , #no? , #maybe? ), along with attribute setter methods (ie: #yes! , #no! , #maybe! ).

Rails Docs on enums

Create a globally accessible array of the options you want, then validate the value of your status column:

class Attend < ActiveRecord::Base

  STATUS_OPTIONS = %w(yes no maybe)

  validates :status, :inclusion => {:in => STATUS_OPTIONS}

end

You could then access the possible statuses via Attend::STATUS_OPTIONS

This is how I implement in my Rails 4 project.

class Attend < ActiveRecord::Base
    enum size: [:yes, :no, :maybe]
    validates :size, inclusion: { in: Attend.sizes.keys }
end

Attend.sizes gives you the mapping.

Attend.sizes # {"yes" => 0, "no" => 1, "maybe" => 2}

See more in Rails doc

You could use a string column for the status and then the :inclusion option for validates to make sure you only get what you're expecting:

class Attend < ActiveRecord::Base
    validates :size, :inclusion => { :in => %w{yes no maybe} }
    #...
end

What we have started doing is defining our enum items within an array and then using that array for specifying the enum, validations, and using the values within the application.

STATUS_OPTIONS = [:yes, :no, :maybe]
enum status_option: STATUS_OPTIONS
validates :status_option, inclusion: { in: STATUS_OPTIONS.map(&:to_s) }

This way you can also use STATUS_OPTIONS later, like for creating a drop down lists. If you want to expose your values to the user you can always map like this:

STATUS_OPTIONS.map {|s| s.to_s.titleize }

对于 ActiveModels 中的枚举,您可以使用此 gem Enumerize

After some looking, I could not find a one-liner in model to help it happen. By now, Rails provides Enums, but not a comprehensive way to validate invalid values.

So, I opted for a composite solution: To add a validation in the controller, before setting the strong_params , and then by checking against the model.

So, in the model, I will create an attribute and a custom validation:

attend.rb

enum :status => { your set of values }
attr_accessor :invalid_status

validate :valid_status
#...
private
    def valid_status
        if self.invalid_status == true
            errors.add(:status, "is not valid")
        end
    end

Also, I will do a check against the parameters for invalid input and send the result (if necessary) to the model, so an error will be added to the object, thus making it invalid

attends_controller.rb

private
    def attend_params
        #modify strong_params to include the additional check
        if params[:attend][:status].in?(Attend.statuses.keys << nil) # to also allow nil input
            # Leave this as it was before the check
            params.require(:attend).permit(....) 
        else
            params[:attend][:invalid_status] = true
            # remove the 'status' attribute to avoid the exception and
            # inject the attribute to the params to force invalid instance
            params.require(:attend).permit(...., :invalid_status)
       end
    end

To define dynamic behavior you can use in: :method_name notation:

class Attend < ActiveRecord::Base
  enum status: [:yes, :no, :maybe]
  validates :status, inclusion: {in: :allowed_statuses}

  private

  # restricts status to be changed from :no to :yes
  def allowed_statuses
    min_status = Attend.statuses[status_was]
    Attend.statuses.select { |_, v| v >= min_status }.keys
  end
end

You can use rescue_from ::ArgumentError .

rescue_from ::ArgumentError do |_exception|
  render json: { message: _exception.message }, status: :bad_request
end

Want to place another solution.

#lib/lib_enums.rb
module LibEnums
  extend ActiveSupport::Concern

  included do
        validate do
        self.class::ENUMS.each do |e|
          if instance_variable_get("@not_valid_#{e}")
            errors.add(e.to_sym, "must be #{self.class.send("#{e}s").keys.join(' or ')}")
          end
        end
      end

        self::ENUMS.each do |e| 
          self.define_method("#{e}=") do |value|
            if !self.class.send("#{e}s").keys.include?(value)
              instance_variable_set("@not_valid_#{e}", true)
            else
              super value
            end
          end
        end
    end
end
#app/models/account.rb
require 'lib_enums'
class Account < ApplicationRecord
  ENUMS = %w(state kind meta_mode meta_margin_mode)
  include LibEnums
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM