Reputation: 714
Originally asked this question about Regex for Usernames: Usernames that cannot start or end with characters
How can I achieve this with correct syntax for Ruby on Rails?
Here's my current User.rb validation:
validates_format_of :username, with: /\A[\w\._]{3,28}\z/i
This validation allows underscores and periods, but the goal is to not allow them at the start or end of a username.
I'm trying to achieve these rules with Rails regex:
Valid:
Spicy_Pizza
97Indigos
Infinity.Beyond
Invalid:
_yahoo
powerup.
un__real
no..way
Upvotes: 3
Views: 1090
Reputation: 28305
Although totally possible, as Wiktor's answer shows, my recommendation would be to not define this in a single regular expression, since:
"Invalid Format"
, which does not explain why it's invalid. The exercise is then left to the user to re-read the nontrivial format rules and understand why.Instead, I would recommend defining a custom validation class, which can perform each of these checks separately (via easy to understand methods), and add a different error message upon each check failing.
Something along the lines of:
# app/models/user.rb
class User < ApplicationRecord
validates :username, presence: true, username: true
end
# app/validators/username_validator.rb
class UsernameValidator < ActiveModel::EachValidator
def validate(record, attribute, value)
validate_length(record, attribute, value)
validate_allowed_chars(record, attribute, value)
validate_sequential_chars(record, attribute, value)
validate_first_and_last_chars(record, attribute, value)
end
private
def validate_length(record, attribute, value)
unless value.length >= 3 && value.length <= 28
record.errors[attribute] << "must be between 3 and 28 characters long"
end
end
def validate_allowed_chars(record, attribute, value)
unless value =~ /\A[._a-zA-Z0-9]*\z/
record.errors[attribute] << "must only contain periods, underscores, a-z, A-Z or 0-9"
end
end
def validate_sequential_chars(record, attribute, value)
if value =~ /[._]{2}/
record.errors[attribute] << "cannot contain two consecutive periods or underscores"
end
end
def validate_first_and_last_chars(record, attribute, value)
if value =~ /\A[._]/ || value =~ /[._]\z/
record.errors[attribute] << "cannot start/end with a period or underscore"
end
end
end
So for instance, you asked above: "What if I needed to extend this to allow lowercase letters only?" I think it's now quite obvious how the code could be updated to accommodate such behaviour, but to be clear - all you'd need to do is:
def validate_allowed_chars(record, attribute, value)
unless value =~ /\A[._a-z0-9]*\z/
record.errors[attribute] << "must only contain periods, underscores, a-z or 0-9"
end
end
You could also now, quite easily, write tests for these validation checks, and assert that the correct validation is being performed by verifying against the contents of the error message; something that is not possible when all validation failures result in the same error,
Another benefit to this approach is that the code can easily be shared (perhaps with some slight behavioural differences). You could perform the same validation on multiple attributes, or multiple models, perhaps with different allowed lengths or formats.
Upvotes: 3
Reputation: 626794
You may use
/\A(?=.{3,28}\z)[a-zA-Z0-9]+(?:[._][a-zA-Z0-9]+)*\z/
See the Rubular demo.
Details
\A
- start of string(?=.{3,28}\z)
- 3 to 28 chars other than line break chars up to the end of the string are allowed/required[a-zA-Z0-9]+
- one or more ASCII letters / digits(?:[._][a-zA-Z0-9]+)*
- 0+ sequences of:
[._]
- a .
or _
[a-zA-Z0-9]+
- one or more ASCII letters / digits\z
- end of string.Upvotes: 4