strip_attributes
strip_attributes copied to clipboard
Consider using the Attributes API instead of before_validation callbacks
Related to #34.
Investigate and perhaps consider using the new attributes API instead of a before_validation callback.
class Strippable < ActiveRecord::Type::String
def cast_value(value)
value.to_s.strip
end
end
class Something < ActiveRecord::Base
attribute :title, Strippable
end
Sounds like a good idea! I saw the discussion in #34 about using setters, I think whatever you'll end up with will be better than before_validation. The pain with before_validation is that I have to trigger the callbacks before I'm able to use a stripped version of an attribute.
I'm not sure how exactly the attributes API works, but it might give the option to use read_attribute_before_type_cast to access the pristine version of an attribute. Though I'd worry that using the attributes API would prevent me from using another type on a given attribute, but I assume that's why the issue says "investigate". ;)
Just made my own for this, since the current implementation is doing in place string manipulation, which won't work in ruby 3. I only overwrote cast, since I don't want it to run when loading from the database.
On using multiple filters: it's really easy to create a wrapper type, that calls them both. So I would leave that the application.
# frozen_string_literal: true
# Strips whitespace and make nil when string becomes empty.
class StrippedString < ActiveModel::Type::ImmutableString
# taken from strip_attributes gem
# Unicode invisible and whitespace characters. The POSIX character class
# [:space:] corresponds to the Unicode class Z ("separator"). We also
# include the following characters from Unicode class C ("control"), which
# are spaces or invisible characters that make no sense at the start or end
# of a string:
# U+180E MONGOLIAN VOWEL SEPARATOR
# U+200B ZERO WIDTH SPACE
# U+200C ZERO WIDTH NON-JOINER
# U+200D ZERO WIDTH JOINER
# U+2060 WORD JOINER
# U+FEFF ZERO WIDTH NO-BREAK SPACE
MULTIBYTE_WHITE = "\u180E\u200B\u200C\u200D\u2060\uFEFF"
MULTIBYTE_SPACE = /[[:space:]#{MULTIBYTE_WHITE}]/.freeze
def initialize(allow_empty: false)
super()
@allow_empty = allow_empty
end
def cast(value)
return unless value
value = super(value).gsub(/\A#{MULTIBYTE_SPACE}+|#{MULTIBYTE_SPACE}+\z/, "").freeze
value.blank? && !@allow_empty ? nil : value
end
end
Note that in your example you need to either use:
class Something < ActiveRecord::Base
attribute :title, Strippable.new(allow_empty: true)
end
or add this to an initializer
ActiveRecord::Type.register(:stripped_string, StrippedString)
and use it like this:
class Something < ActiveRecord::Base
attribute :title, :stripped_string, allow_empty: true
end
Are you still interested in this? I'm thinking about it. i (or someone else) could potentially PR... it's potentially fairly simple code; then again, we could also easily copy/paste fork (thanks for MIT license) your logic into an attribute-based thing in another gem, the code here is pretty simple.
Some features aren't really available anymore with an ActiveModel-attribute-based implementation. Like the if/unless stuff -- inside attribute casting, you don't have access to the containing record to make decisions based on it's state. And you have a bit more cumbersome API to specify, as you need to write an attribute wrapper for each attribute, instead of just easily writing strip_attributes (possibly with only and/or except).
So... it's got plusses and minuses. Maybe it's really a separate gem.
Would you care to update with your current thinking or plans on things?
Looking forward to this gem using the new normalize Rails method. But not sure if this gem even needs to exist because of it ... will be so easy to write your own wrapper.
@justinko "the new normalize Rails method" -- I haven't heard of that, and am not having luck googling for it. This is something built into rails, not a third-party something? Can you give me a link to something? Thanks!
Whoops, I got the name wrong!
https://github.com/rails/rails/pull/43945
One major difference is that:
Normalizations are also applied to matching keyword arguments of finder methods. This allows a record to be created and later queried using an unnormalized value.
I don't think I would always want that. For example, if normalization is converting the attribute from "" -> nil, then going forward, the finder methods would match the record by both "" and nil.
Oh, that is very interesting, thanks @justinko .
It does look like a generalized approach to what is being discussed here.
@petrgazarov , I see what you mean, but any appraoch based on the Rails Attributes API is probably going to work like this. Because Rails will always cast and serialize the value, per the type, when constructing the SQL.