Skip to content

Instantly share code, notes, and snippets.

@romanbsd
Last active February 8, 2026 11:45
Show Gist options
  • Select an option

  • Save romanbsd/5209086d40e0e51f4fd86276d5743b02 to your computer and use it in GitHub Desktop.

Select an option

Save romanbsd/5209086d40e0e51f4fd86276d5743b02 to your computer and use it in GitHub Desktop.
# app/models/concerns/safe_enum_assignment.rb
module SafeEnumAssignment
extend ActiveSupport::Concern
class_methods do
# Call AFTER your enum declarations.
#
# Example:
# enum :color, { red: 0, green: 1 }
# enum :size, { small: 0, large: 1 }
# safe_enum_assignment!
#
# Options:
# on_invalid: :nil -> set attribute to nil (default)
# :ignore -> keep current value (useful on updates)
# add_error: true/false (default true) -> add validation errors when invalid values were assigned
# message: "..." or a Proc (enum_name, value) -> string
def safe_enum_assignment!(on_invalid: :nil, add_error: true, message: nil)
raise "No enums defined on #{name}" if defined_enums.blank?
# store config per-model
@_safe_enum_assignment_config = {
on_invalid:,
add_error:,
message:,
}
# holds the raw invalid values for this instance (not persisted)
define_method(:_invalid_enum_inputs) do
@_invalid_enum_inputs ||= {}
end
# define safe setters for each enum
defined_enums.each_key do |enum_name|
setter = :"#{enum_name}="
next if method_defined?(setter, false) # don't override a custom setter you wrote
define_method(setter) do |value|
super(value)
rescue ArgumentError => e
# remember what was attempted (for error message / debugging)
_invalid_enum_inputs[enum_name.to_s] = value
cfg = self.class.instance_variable_get(:@_safe_enum_assignment_config) || {}
case cfg[:on_invalid]
when :ignore
# do nothing; keep previous value
# (note: super already failed, so attribute remains unchanged)
else # :nil
super(nil)
end
end
end
return unless add_error
validate do
invalids = _invalid_enum_inputs
next if invalids.blank?
cfg = self.class.instance_variable_get(:@_safe_enum_assignment_config) || {}
msg_cfg = cfg[:message]
invalids.each do |enum_name, bad_value|
msg =
case msg_cfg
when Proc
msg_cfg.call(enum_name.to_sym, bad_value)
when String
msg_cfg
else
"is not a valid #{enum_name}"
end
errors.add(enum_name.to_sym, msg)
end
invalids.clear
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment