Rails - REGEX - validating length of non whitespace / special characters - ruby-on-rails

I have a few fields with a length validation, which can be bypassed by entering n number of white spaces. I'm trying to write a method that validates the number of alpha numeric characters only (not whitespace or special characters).
I've gotten as far as the following:
validates :title,
presence: true,
length: { minimum: 4, maximum: 140 },
format: { with: /([A-z0-9])/ }
What I can't get is how to validate the length of the title that matches the format. For example I want to allow a title of 'The Beast', but only count 'TheBeast' in the character count. This will allow 'The Beast' and include the space in the length validation
Is there something built into rails that allows me to do this? Or if not what's the best way go about writing a custom method?
Thanks in advance

if you would have aux column like 'filtered_title' you could do:
before_save :filter_title
def filter_title
self.filtered_title = title.gsub(/[^0-9a-zA-Z]/, '') // strip unneeded chars
end
and your validator, but on a the new column
validates :filtered_title,
presence: true,
length: { minimum: 4, maximum: 140 },
format: { with: /([A-z0-9])/ }

To expand on #NeverBe's answer, I went with:
class AlphanumericLengthValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
minimum_length = options.fetch(:length, 100)
stripped_value = value ? value.gsub(/[^0-9a-zA-Z]/, '') : nil
message = "must be at least #{minimum_length} alphanumeric characters in length"
return if stripped_value&.length && stripped_value.length >= minimum_length
record.errors.add(attribute, message) if !stripped_value || stripped_value.length < minimum_length
end
end
Which allowed me to do:
validates :title, alphanumeric_length: { length: 8 }

Related

Using Rails built-in validator to validate hash with dynamic options

I have two models, CharacterSheet and Sheet, both of which have the column properties. Sheet's properties column is used to store key-value pairs of property names and their validation (which are in idiomatically correct Ruby validates syntax).
Sheet.create!(name: 'A Test Sheet',
properties: {
prop1: { numericality: { only_integer: true }, length: { maximum: 3 } },
prop2: { numericality: { only_integer: true }, length: { minimum: 1 } },
prop3: { numericality: { only_integer: true } }
})
CharacterSheet stores key-value pairs of property names and their actual values, validated by the appropriate Sheet key-value pair.
CharacterSheet.create(sheet_id: 1,
name: 'A Test CharacterSheet',
properties: {
prop1: 123,
prop2: 234,
prop3: 345
})
Ideally, I want to use the validation requirements stored in Sheet.properties to use Rails' built-in validation methods, so I don't have to rewrite all of that validation myself. However, I can't find an appropriate method to hook into. self.validates doesn't seem to serve my purposes since it seems like (from what I could tell) it requires a valid column name, which it then uses to pull the value, instead of allowing a value to be passed in directly.
So the first problem I see is that you'll need also to convert what it means, numericality and how to fetch that property, and only_integer and what does that mean.
I would advise you to use something like
prop_name: { type_of_validation: 'regexp', validator: 'Regex-representation' }
prop_name2: { type_of_validation: 'type', validator: 'Hash' }
the regex representation will need to be double-escaped.
because then you can do
loaded_sheet = Sheet.find(csheet.sheet_id).properties.as_json.with_indifferent_access
csheet.properties.as_json.with_indifferent_access.each do |k,v|
if loaded_sheet[k.to_sym][:type_of_validation] == 'regexp'
regex = Regexp.new loaded_sheet[k.to_sym][:validator]
return false unless v =~ regex
elsif loaded_sheet[k.to_sym[:type_of_validation] == 'type'
return false unless v.is_a?(loaded_sheet[k.to_sym][:validator].constantize)
end
end

How can I validate an attribute to be between various noncontinuous ranges?

I'm wanting to validate that my height attribute is within a bunch of different ranges. So my attempt was something like what I did below... however this is incorrect. How should this be done? Thanks!
validates :height, :numericality => { in: { 5020..5028, 5030..5038, 5040..5048, 5050..5058, 5060..5068, 5070..5078, 5080..5088, 5090..5098, 5100..5108, 5110..5118,
6000..6008, 6010..6018, 6020..6028, 6030..6038, 6040..6048, 6050..6058, 6060..6068, 6070..6078, 6080..6088, 6090..6098, 6100..6108, 6110..6118,
7000..7008, 7010..7018, 7020..7028, 7030..7038, 7040..7048, 7050..7058, 7060..7068, 7070..7078, 7080..7088, 7090..7098, 7100..7108, 7110..7118 } }
You can put that in a custom validate method:
class YourModel < ActiveRecord::Base
VALID_HEIGHT_RANGES = [5020..5028, 5030..5038, 5040..5048, 5050..5058, 5060..5068, 5070..5078, 5080..5088, 5090..5098, 5100..5108, 5110..5118, 6000..6008, 6010..6018, 6020..6028, 6030..6038, 6040..6048, 6050..6058, 6060..6068, 6070..6078, 6080..6088, 6090..6098, 6100..6108, 6110..6118, 7000..7008, 7010..7018, 7020..7028, 7030..7038, 7040..7048, 7050..7058, 7060..7068, 7070..7078, 7080..7088, 7090..7098, 7100..7108, 7110..7118]
validate :height_in_valid_range
private
def height_in_valid_range
VALID_HEIGHT_RANGES.each do |range|
unless range.include? height
errors.add :height, "not in valid range"
break
end
end
end
end

How can I allow a regex to be blank?

I'm trying to make a form that accepts only a valid email, or a blank email. I think it will be something along these lines:
EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i OR ""
validates :email, format: { with: EMAIL_REGEX }
or maybe
EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
BLANK_REGEX =
validates :email, format: { with: EMAIL_REGEX OR BLANK_REGEX }
but I can't figure out the proper syntax. Does anyone know the right way to do this?
The approach pointed by #avinash-raj is perfect. However you can use allow_blank: true in your validates. Your code should be like this:
validates :email, format: { with: /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i}, allow_blank: true
Just make your regex optional to make your regex to match blank email also.
EMAIL_REGEX = /\A(?:[\w+\-.]+#[a-z\d\-.]+\.[a-z]+)?\z/i
OR
EMAIL_REGEX = /^(?:[\w+\-.]+#[a-z\d\-.]+\.[a-z]+)?$/i
TO make a regex optional, enclose the whole regex inside a non-capturing group (?:...) and then add a ? next to that group.

Trying to write REGEX for username validation in Rails

I'm trying to write a regular expression in Ruby (Rails) so that a username's characters only contains numbers and letters (also no spaces).
I have this regex, /^[a-zA-Z0-9]+$/, but it doesn't seem to be working and I get an error in Rails that says "The provided regular expression is using multiline anchors (^ or $), which may present a security risk. Did you mean to use \A and \z, or forgot to add the :multiline => true option?"
My full code for this implementation in my user.rb model is:
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_USERNAME_REGEX = /^[a-zA-Z0-9]+$/
validates :username, presence: true, length: { maximum: 20 },
format: { with: VALID_USERNAME_REGEX },
uniqueness: { case_sensitive: false }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
end
What am I doing wrong and how I can fix this regex so that it only is valid for numbers and letters and no spaces? Thanks.
Short answer: use /\A[a-zA-Z0-9]+\z/ instead (as VALID_EMAIL_REGEX is using).
Long answer: the ^ and $ anchors will match the beginning and end of a line in a string. That means that if your string consists of multiple lines of alphanumeric characters they will match:
/^[a-zA-Z0-9]+$/ =~ "Ana\nBob\nClara\nDaniel" #=> 0 (matches)
The \A and \z on the other hand will match the beginning and end of a string, hence it will prevent a possible attack by a user sending a multiline string (like in the previous example).
/\A[a-zA-Z0-9]+\z/ =~ "Ana\nBob\nClara\nDaniel" #=> nil (doesn't match)
/\A[a-zA-Z0-9]+\z/ =~ "Erika" #=> 0 (matches)
All you have to do is follow the error message. Replace ^ (start of line anchor) with \A (start of string anchor), and $ (end of line anchor) with \z (end of string anchor). Other than that, your regex works as is.
\A[a-zA-Z0-9]+\z
Rails has this security concern because unlike some languages, ^ and $ only match the beginning/end of a single line, rather than the entire string.
This illustrates an example of this possible exploit:
str = "malicious_code()\naValidUsername"
str.match(/^[a-zA-Z0-9]+$/) # => #<MatchData "aValidUsername">

Setting single coordinates for RGeo Point

RGeo provides built in methods for POINT features, for example getter methods lat() and lon() to pull latitude and longitude values from a POINT object. Unfortunately, these don't work as setters. For example:
point = RGeo::Geographic.spherical_factory(:srid => 4326).point(3,5) // => #<RGeo::Geographic::SphericalPointImpl:0x817e521c "POINT (3.0 5.0)">
I can do this:
point.lat // => 5.0
point.lon // => 3.0
But I can't do:
point.lat = 4 // => NoMethodError: undefined method `lat=' for #<RGeo::Geographic::SphericalPointImpl:0x00000104024770>
Any suggestions as to how to implement setter methods? Would you do it in the Model or extend the Feature class?
I'm the author of RGeo, so you can consider this answer authoritative on that basis.
In short, PLEASE AVOID DOING THIS. RGeo objects intentionally have no setter methods because they are meant to be immutable objects. This is so that they can be cached, used as hash keys, used across threads, etc. Some of the RGeo calculations assume that the value of a feature object will never change, so making changes like this could have unexpected and unpredictable consequences.
If you really want a "changed" value, create a new object. For example:
p1 = my_create_a_point()
p2 = p1.factory.point(p1.lon + 20.0, p2.lat)
I have found something that works, although there might be more elegant solutions.
In my Location model I have added theses methods:
after_initialize :init
def init
self.latlon ||= Location.rgeo_factory_for_column(:latlon).point(0, 0)
end
def latitude
self.latlon.lat
end
def latitude=(value)
lon = self.latlon.lon
self.latlon = Location.rgeo_factory_for_column(:latlon).point(lon, value)
end
def longitude
self.latlon.lon
end
def longitude=(value)
lat = self.latlon.lat
self.latlon = Location.rgeo_factory_for_column(:latlon).point(value, lat)
end
I ended up doing something like this in my model:
class MyModel < ActiveRecord::Base
attr_accessor :longitude, :latitude
attr_accessible :longitude, :latitude
validates :longitude, numericality: { greater_than_or_equal_to: -180, less_than_or_equal_to: 180 }, allow_blank: true
validates :latitude, numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90 }, allow_blank: true
before_save :update_gps_location
def update_gps_location
if longitude.present? || latitude.present?
long = longitude || self.gps_location.longitude
lat = latitude || self.gps_location.latitude
self.gps_location = RGeo::Geographic.spherical_factory(srid: 4326).point(long, lat)
end
end
end
Then you can just update the position like so:
my_model.update_attributes(longitude: -122, latitude: 37)
I didn't load up longitude/latitude in an after_initialize block because in my app we never need to read the data, only write it. You could easily add that though.
Credit to this answer for the validations.

Resources