Stripping commas from Integers or decimals in rails - ruby-on-rails

Is there a gsub equivalent for integers or decimals? Should gsub work with integers? Basically I'm just trying to enter decimal into a ruby form and what the user to be able to use commas. For example, I want the user to be able to enter 1,000.99.
I've tried using
before_save :strip_commas
def strip_commas
self.number = self.number.gsub(",", "")
end
but get the following error "undefined method `gsub' for 8:Fixnum" where "8" is replaced with whatever number the user enters.

If your field is a Fixnum, it will never have commas, as Rails will have to convert the user input into a number in order to store it there.
However, it will do that by calling to_i on the input string, which is not what you want.
overriding the normal setter to something like
def price=(num)
num.gsub!(',','') if num.is_a?(String)
self[:price] = num.to_i
end
Not tested, but something resembling this should work...
You need to get at the commas while the input is still a string.
Edit:
As noted in comments, if you want to accept decimals, and create something not an integer, you need a different conversion than String.to_i. Also, different countries have different conventions for numeric punctuation, so this isn't a complete solution.

try self.number.gsub(/\D/, ''). That is, remove everything that isn't a digit. Regexen make no distinction between integers, floats, decimals, etc. It's all strings. And Rails won't convert it correctly for you, because it just calls #to_i or #to_f on it.
EDIT:
actually: self.number.gsub(/[^\d\.]/, '').to_f: everything that isn't a digit or decimal point, and convert it to a float.

Related

Difficulty applying a Regex to a Rails View. Should I make it a helper method?

I am trying to apply the following regex to one of my views:
^([^\s]+)\s+
This is to remove any string of consecutive non-whitespace characters including any white space characters that follow from the start of the line (remove everything except the first word). I have input it on Rubular and it works.
I was wondering how I would be able to apply it to my rails project. Would I create a rails helper method? So far I have tested it in irb and it is not returning the right value:
I would like to know how I can fix my method and if making it a helper method is the right approach. Thank you very much for your help guys!
The =~ operator matches the regular expression against a string, and it returns either the offset of the match from the string if it is found, otherwise nil.
You could either try it with String.match and work with the match data.
like
str.match(^([^\s]+)\s+)
or you don't use regex for readability. Split the string on spaces and return and array of the words and take the first one, like:
str.split(' ').first

Rails 6 number field with comma

When a user inputs a number value into a field that contains a comma, for example: 1,000,000. When the form is submitted and saved, the value becomes 1. The column type is a t.bigint
How do I prevent this across all my numeric fields?
The key thing about the thousand delimiter is that it will always be followed by three digits. So a regex lookahead would make sense. Also I'd do this processing in the controller rather than the model as it's a function of the input from the form.
So if I had an object Foo with a some_number attribute, in the foos controller I'd do something like:
def foo_params
params.require(:foo).permit(:some_number).tap do |foo|
foo[:some_number] = foo[:some_number]&.split(/[\.\,](?=\d{3})/).join
end
end
That will convert "3,000" and "3.000" into "3000", but leave "3.12" as "3.12".
It will also convert "23,345,555,444.3" into "23345555444.3"
I don't know if it's the best way to do this, but I usually use a custom setter, lets say your column is total, then I do something like:
def total=(value)
value = value.gsub(/[\.,],'') if value.is_a?(String) # remove , and .
write_attribute(:total, value)
end
Now you can assign anything like "1.000" or "2,343,111" and it will strip comma and dot.
If you want to be more permissive with the values (in french you could write "1 000"), you can use /\D/ for the regexp to remove anything that's not a digit.
When space ( ) or period (.) is used as a thousand separator and comma (,) as decimal separator, ie: "772 067,48". First it removes space, then period and replaces comma with period.
So not directly applicable to OPs question but I found the page through a search engine and others may do as well.
def foo_params
params.require(:foo).permit(:year, :quarter, :first).tap do |foo|
foo[:first] = foo[:first]&.gsub(' ', '').gsub('.', '').gsub(',', '.')
end

Arbitrary-length LIKE clause in Ruby on Rails ActiveRecord

I'm attempting to write a Ruby method which accepts an array of strings (for example, ["EG", "K", "C"], and returns all records from a database table where the icao_code field starts with any of those strings (for example, KORD, EGLL, and CYVR would all match). The length of the array will vary, and it will be input by a user, so it needs to be sanitized.
If I were only searching for a single string, I could do something like Airport.where("icao_code LIKE ?", "#{icao_start}%"). However, since I need to search against an arbitrary number of strings, I can't use that syntax.
Right now, I've got it working as follows:
def in_region(icao_starts)
where_clause = icao_starts.map{|i| "icao_code LIKE '#{i}%'"}.join(" OR ")
return Airport.where(where_clause)
end
However, I'm a bit worried using a setup like this with untrusted user input, since I suspect it would be vulnerable to SQL injection.
Is there a better way to get the same result in a more secure way?
You could consider something like this:
def in_region(icao_starts)
where_clause = "icao_code LIKE '#?%' OR " * icao_starts.length
return Airport.where(where_clause.sub(/\ OR\ $/, ''), *icao_starts)
end
This will build up a (potentially very long?) string with ? placeholders. The *icao_starts will expand that array into arguments to the where clause, so each ? will end up getting safely replaced. The sub(/\ OR\ $/, '') simply trims off the final OR (you could append 1=0 instead if you wanted).
If I were you I would also perform a .uniq on icao_starts before you do anything, truncate the array at some sensible upper length limit, and also have a whitelist of permitted values (oh, forget that, I thought users were searching by airport code). That should be pretty much infallible.
You are right about not interpolating user input into your SQL query. This is dangerous and makes your code vulnerable for SQLI attacks.
def in_region(icao_starts)
conditions = icao_starts.map { "icao_code LIKE ?"}
Airport.where(conditions.join(' OR '), *icao_starts.map { |name| "#{name}%"})
end
It is pretty similar than the solution of bogardpd but does not use a Regexp to get rid of the last " OR"

Rails 4 ts_range not persisting

I'm having an issue with Rails 4's support for Postgresql's ts_range data type. Here is the code that I am trying to persist:
before_validation :set_appointment
attr_accessor :starting_tsrange, :ending_tsrange
def set_appointment
self.appointment = convert_to_utc(starting_tsrange)...convert_to_utc(ending_tsrange)
end
def convert_to_utc
ActiveSupport::TimeZone.new("America/New_York").parse(time_string).utc
end
Basically I set an instance variable for the beginning and end of the appointment ts_range with two strings representing date_times. Before validation it converts them to utc and saves those values to the appointment attribute which should then be persisted. It sets things correctly but when I try to retrieve the record, the appointment attribute is now nil. Why is this code not working as expected?
Figured out the subtle bug in the code. The issue here is with the triple dot range operator. If we get two values that are the exact same time. The triple dot will say include everything from time a up until time b if they are the same exact time, then nothing will be included and the result will be nil. This can be visualized with the code below
(1...1).to_a # []
(1..1).to_a # [1]
So the way to fix this is to not use the triple dot notation when using ranges that can have the same value for a time. Use the double dot notation instead.

Rails validates_format_of

I want to use validates_format_of to validate a comma separated string with only letters (small and caps), and numbers.
So.
example1, example2, 22example44, ex24
not:
^&*, <> , asfasfsdafas<#%$#
Basically I want to have users enter comma separated words(incl numbers) without special characters.
I'll use it to validate tags from acts_as_taggable_on. (i don't want to be a valid tag for example.
Thanks in advance.
You can always test out regular expressions at rubular, you would find that both tiftiks and Tims regular expressions work albeit with some strange edge cases with whitespace.
Tim's solution can be extended to include leading and trailing whitespace and that should then do what you want as follows :-
^\s*[A-Za-z0-9]+(\s*,\s*[A-Za-z0-9]+)*\s*$
Presumably when you have validated the input string you will want to turn it into an array of tags to iterate over. You can do this as follows :-
array_var = string_var.delete(' ').split(',')
^([a-zA-Z0-9]+,\s*)*[a-zA-Z0-9]+$
Note that this regex doesn't match values with whitespace, so it won't match multiple words like "abc xyz, fgh qwe". It matches any amount of whitespace after commas. You might not need ^ or $ if validates_format_of tries to match the whole string, I've never used Rails so I don't know about that.
^[A-Za-z0-9]+([ \t]*,[ \t]*[A-Za-z0-9]+)*$
should match a CSV line that only contains those characters, whether it's just one value or many.

Resources