I am trying to normalize an attribute for both assignment and querying.
The intended way is using normalizes, the problem here is that the normalizer runs after the value has been type cast, which is preventing sensible normalization in my case.
The use case is I must convert the binary string (of varying length) "\x26\xF8\xAF\x32" or an integer 653_831_986 into the string form "26:F8:AF:32" and store that string in database.
It may both be queried and assigned by either binary string or integer value and that is where normalizes falls apart.
class Model < ApplicationRecord
normalizes :serial, with: lambda { |val|
# likely, but not guaranteed it was type-cast from Integer
if val.match?(/\A[1-9][0-9]*\z/)
str = val.to_i.to_s(16).upcase
str = "0#{str}" if str.length.odd?
str.chars.each_slice(2).map(&:join).join(":")
# already correctly formatted
elsif val.match?(\A([A-F0-9]{2}:)*[A-F0-9]{2}\z)
val
# treat as binary string
else
val.bytes.map { it.to_s(16).upcase.rjust(2, "0") }.join(":")
end
}
end
It is not possible to check whether the input value was an integer, because it is cast to string before normalization is applied. This forces me to use a heuristic (which may fail) to determine if the original value was an integer.
When using a custom setter, this does not solve the problem of querying, only assignment. Is it possible to apply normalization before type casting? Or is it possible to use a custom setter and also normalize the value for querying?