Last active
February 8, 2026 11:45
-
-
Save romanbsd/5209086d40e0e51f4fd86276d5743b02 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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