Looking on SO, I see that the preferred way to currency using RoR is using decimal(8,2) and to output them using number_to_currency();
I can get my numbers out of the DB, but I'm having issues on getting them in.
Inside my update action I have the following line:
if #non_labor_expense.update_attributes(params[:non_labor_expense])
puts YAML::dump(params)
The dump of params shows the correct value. xx,yyy.zz , but what gets stored in the DB is only xx.00
What do I need to do in order to take into account that there may be commas and a user may not enter .zz (the cents). Some regex and for comma? how would you handle the decimal if it were .2 versus .20 .
There has to be a builtin or at least a better way.
My Migration (I don't know if this helps):
class ChangeExpenseToDec < ActiveRecord::Migration
def self.up
change_column :non_labor_expenses, :amount, :decimal, :precision => 8, :scale => 2
end
def self.down
change_column :non_labor_expenses, :amount, :integer
end
end
I tried Daniel's before_validation idea and I just couldn't get it to work. It seemed that the by the time I get to the before_validation the input has already been converted. The solution I went with was to override the method for the column, and strip the commas there:
def profit=(num)
num.gsub!(',','') if num.is_a?(String)
self[:profit] = num
end
It might depend on what DBMS you're using, but as far as I know, decimal fields won't accept commas (at least not as separators; there might be a way to have the database accept a comma as a decimal point rather than a period). What you will have to do is remove the commas from your numbers (in a before_save or before_validation filter, perhaps), and then when you display the number, add the commas back in.
before_validation :strip_commas_from_non_labor_expense
def strip_commas_from_non_labor_expense
self.non_labor_expense = self.non_labor_expense.to_s.gsub(/,/, '').to_f
end
Then use number_to_currency when you want to display the expense amount formatted with comma separated groups and two decimal places, as you mentioned:
<%
non_labor_expense = ... # get value from your model
puts number_to_currency(non_labor_expense, :precision => 2, :separator => ',')
%>
Checkout the delocalize gem:
http://github.com/clemens/delocalize
Here you can find a code snippet that will make any decimal column accept values with the comma as decimal separator:
http://gem-session.com/2010/03/how-to-use-the-comma-as-decimal-separator-in-rails-activerecord-columns-and-text-fields
Related
So I have a variable called #gases that holds gas element such as Carbon Monoxide [CO], Carbon Dioxide [CO2]. Right now if someone searches CO in the search function Carbon Dioxide [CO2] shows up first. So I wanted to know if I could do something like this
where("(lower(gas_analytes.gas)\[.*?\]) LIKE lower(?)", "#{gas_analyte}")
so the code above, if someone only searches for the element and not the whole content, I used \[.*?\], so if the search input equals the element in the bracket, but this does not work.
Is there anything similar I can do?
I would probably just add the square brackets to the search pattern like this:
where("lower(gas_analytes.gas) LIKE ?", "%[#{gas_analyte.downcase}]%")
Note that if it is possible that gas_analyze might include % characters then you should sanitize that string before using it:
where("lower(gas_analytes.gas) LIKE ?", "%[#{sanitize_sql_like(gas_analyte.downcase)}]%")
Because like is very slow you might consider splitting the string and extracting the abbreviation into a database column on it own. That would improve the query time a lot.
This code worked for my (I'm using SQLite database):
# seeds.rb
# Type `rake db:seed` in terminal to run this file
# GasAnalyte.destroy_all
10.times do |t|
GasAnalyte.create!(
gas: "#{SecureRandom.uuid} [CO2]"
)
end
p ['All', GasAnalyte.all].inspect
p ['Searching for "CO"', GasAnalyte.search_gas("CO")].inspect
# gas_analyte.rb
class GasAnalyte < ApplicationRecord
validates :gas, presence: true
scope :search_gas,
->(gas_analyte) { where("lower(gas_analytes.gas) LIKE lower(?)", "%[#{gas_analyte}%]") }
end
Here you can read about LIKE operator: https://www.w3schools.com/sql/sql_like.asp
I'm importing a csv file into pg database, and am getting this error that I never got before upgrading to Rails 5
def self.assign_from_row(row)
member = Member.where(membership_id: row[:membership_id]).first_or_initialize
member.assign_attributes row.to_hash.slice(
:last_name, :first_name, :membership_id, :email
).merge(
:birthday => row[4].nil? ? nil : DateTime.strptime(row[4], "%m/%d/%Y").strftime("%Y/%m/%d")
)
member
end
The exact error is with the birthday line. When I remove from .merge to ), things work. The csv headers look like this:
"Last Name","First Name","Membership Id","E-mail","Birthday"
and the rows look like this:
"Aber","Barbara","00591 2","bab#example.com","07/05/2015"
I have also tried this line
:birthday => DateTime.strptime("%m/%d/%Y").strftime("%Y/%m/%d")
I believe the problem lies in the fact that many of the birthdays are populated with "", or are nil and I don't know how to tell the database to insert nil when it is expecting a date format. Any help most appreciated
You can rescue nil if the date conversion fails.
:birthday => (DateTime.strptime(row[4], "%m/%d/%Y").strftime("%Y/%m/%d") rescue nil)
In line rescue is generally not recommended as it can mask other raised exceptions (for example, if you accidentally typed DateTim instead of DateTime you wouldn't spot the problem) but used with care, in line rescue is a useful tool.
I had to go back to the csv original, open it in Office Calc and change the cells containing the date as a string. I first changed the column to a number, then a date with a format to match the sql format '2015-02-23' and it imported just fine. Thanks everybody!
So it wasn't a Rails issue to begin with, but rather the misidentification of the column. I actually had to go through some hoops in Calc to do it. I had to change the column to a number first, then to a date.
I'm working on a small project where the user should type the phone numbers, so I would like to validate that information using the "ClientSideValidations" gem.
validates_format_of :telcasa, :celular, :tel_flia, :tel_trab, :tel_ref_2, :tel_ref_1,
length: { in: 10 },
:with => /\A(\d{10}|\(?\d{3}\)?[-. ]\d{3}[-.]\d{4})\z/,
:message => "Formato invalido"
But, for the region where this project is going to be used I have to validate the three first numbers of the phone that correspond to the area code ("809"/"829"/"849"). How can I validate that the user correctly typed the phone number with one of the three area codes?
Change /\A(\d{10}|\(?\d{3}\)?[-. ]\d{3}[-.]\d{4})\z/ to:
/\A(\(?(809|829|849)\)?[-. ]\d{3}[-.]\d{4})\z/
I took the liberty of dropping the part where you are matching any ten digit number - not sure why it was there or how it should be used in your context.
You can write some custom validation
validate do
valid_phone_codes = [ "007", "042", ...]
valid_phone_codes.each do |valid_code|
# Also handle optional parenthesis
return true if self.phone_number.starts_with?(valid_code, "(#{valid_code})")
end
errors.add(:phone_numbers, "Must start with a valid country code (one of #{valid_phone_codes.join(', ')}")
false
end
Or if you prefer, you can declare this code in a function def valid_country_codes, and then add a line
validate :valid_country_codes
I'm trying to remove the commas from a field in a model. I want the user to type a number, i.e. 10,000 and that number should be stored in the database as 10000. I was hoping that I could do some model-side normalization to remove the comma. I don't want to depend on the view or controller to properly format my data.
I tried:
before_validation :normalize
def normalize
self['thenumber'] = self['thenumber'].to_s.gsub(',','')
end
no worky.
http://github.com/mdeering/attribute_normalizer looks like a promising solution to this common problem. Here are a few examples from the home page:
# By default it will strip leading and trailing whitespace
# and set to nil if blank.
normalize_attributes :author, :publisher
# Using one of our predefined normalizers.
normalize_attribute :price, :with => :currency
# You can also define your normalization block inline.
normalize_attribute :title do |value|
value.is_a?(String) ? value.titleize.strip : value
end
So in your case you might do something like this:
normalize_attribute :title do |value|
value.to_s.gsub(',', '')
end
I think you're doing it right. This test passes:
test "should remove commas from thenumber" do
f = Foo.new(:thenumber => "10,000")
f.save
f = Foo.find(f.id)
assert f.thenumber == "10000"
end
And I used your code.
class Foo < ActiveRecord::Base
before_validation :normalize
def normalize
self['thenumber'] = self['thenumber'].to_s.gsub(',','')
end
end
Now, my schema is set up for thenumber to be a string though, not an integer.
Started
.
Finished in 0.049666 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
If you wanted to store this in the db as an integer, then you definitely need to override the setter:
def thenumber=(value)
self['thenumber'] = value.to_s.gsub(',','').to_i
end
If you do it your way, with an integer column, it gets truncated by AR....
>> f.thenumber = "10,000"
=> "10,000"
>> f.thenumber
=> 10
That's a little-known thing with Ruby and integers... it auto-casts by truncating anything that's no longer an integer.
irb(main):004:0> i = "155-brian-hogan".to_i
=> 155
Can be cool for things like
/users/155-brian-hogan
#user = User.find_by_id(params[:id])
But not so cool for what you're doing.
So either change the col to a string and use the filter, or change the setter :)
Good luck!
The problem with doing it that way is that for a while, the non-normalized stuff will exist in the object; if you have code that works on the attributes before stuff gets normalised, then that will be a problem.
You could define a setter:
def thenumber=(value)
# normalise stuff here, call write_attribute
end
Unfortunately I think a lot of the Rails form stuff writes the attributes directly, which is one of the reasons I don't tend to use it.
Or you could normalise the params in the controller before you pass them through.
Does ruby let you interchange between a . and [''] ?
I don't know, I'll try later, but I think you are supposed to use .
self.thenumber = self.thenumber.to_s.gsub(',','')
You should return true from your before_validation method, otherwise if the expression being assigned to self['thenumber'] ends up being nil or false, the data will not be saved, per the Rails documention:
If a before_* callback returns false,
all the later callbacks and the
associated action are cancelled.
Ostensibly, you are trying to normalize here then check the result of the normalization with your Rails validations, which will decide if nil/false/blank are okay or not.
before_validation :normalize
def normalize
self['thenumber'] = self['thenumber'].to_s.gsub(',','')
return true
end
I have an application that handles currency inputs. However, if you're in the US, you might enter a number as 12,345.67; in France, it might be 12.345,67.
Is there an easy way, in Rails, to adapt the currency entry to a locale?
Note that I'm not looking for display of the currency (ala number_to_currency), I'm looking to deal with someone typing in a currency string, and converting it into a decimal.
You could give this a shot:
def string_to_float(string)
string.gsub!(/[^\d.,]/,'') # Replace all Currency Symbols, Letters and -- from the string
if string =~ /^.*[\.,]\d{1}$/ # If string ends in a single digit (e.g. ,2)
string = string + "0" # make it ,20 in order for the result to be in "cents"
end
unless string =~ /^.*[\.,]\d{2}$/ # If does not end in ,00 / .00 then
string = string + "00" # add trailing 00 to turn it into cents
end
string.gsub!(/[\.,]/,'') # Replace all (.) and (,) so the string result becomes in "cents"
string.to_f / 100 # Let to_float do the rest
end
And the test Cases:
describe Currency do
it "should mix and match" do
Currency.string_to_float("$ 1,000.50").should eql(1000.50)
Currency.string_to_float("€ 1.000,50").should eql(1000.50)
Currency.string_to_float("€ 1.000,--").should eql(1000.to_f)
Currency.string_to_float("$ 1,000.--").should eql(1000.to_f)
end
it "should strip the € sign" do
Currency.string_to_float("€1").should eql(1.to_f)
end
it "should strip the $ sign" do
Currency.string_to_float("$1").should eql(1.to_f)
end
it "should strip letter characters" do
Currency.string_to_float("a123bc2").should eql(1232.to_f)
end
it "should strip - and --" do
Currency.string_to_float("100,-").should eql(100.to_f)
Currency.string_to_float("100,--").should eql(100.to_f)
end
it "should convert the , as delimitor to a ." do
Currency.string_to_float("100,10").should eql(100.10)
end
it "should convert ignore , and . as separators" do
Currency.string_to_float("1.000,10").should eql(1000.10)
Currency.string_to_float("1,000.10").should eql(1000.10)
end
it "should be generous if you make a type in the last '0' digit" do
Currency.string_to_float("123,2").should eql(123.2)
end
We wrote this:
class String
def safe_parse
self.gsub(I18n.t("number.currency.format.unit"), '').gsub(I18n.t("number.currency.format.delimiter"), '').gsub(I18n.t("number.currency.format.separator"), '.').to_f
end
end
Of course, you will have to set the I18n.locale before using this. And it currently only converts the string to a float for the locale that was set. (In our case, if the user is on the french site, we expect the currency amount text to only have symbols and formatting pertaining to the french locale).
You need to clean the input so that users can type pretty much whatever they want to, and you'll get something consistent to store in your database. Assuming your model is called "DoughEntry" and your attribute is "amount," and it is stored as an integer.
Here's a method that converts a string input to cents (if the string ends in two digits following a delimeter, it's assumed to be cents). You may wish to make this smarter, but here's the concept:
def convert_to_cents(input)
if input =~ /^.*[\.,]\d{2}$/
input.gsub(/[^\d-]/,'').to_i
else
"#{input.gsub(/[^\d-]/,'')}00".to_i
end
end
>> convert_to_cents "12,345"
=> 1234500
>> convert_to_cents "12.345,67"
=> 1234567
>> convert_to_cents "$12.345,67"
=> 1234567
Then overwrite the default "amount" accessor, passing it through that method:
class DoughEntry << ActiveRecord::Base
def amount=(input)
write_attribute(:amount, convert_to_cents(input))
end
protected
def convert_to_cents(input)
if input =~ /^.*[\.,]\d{2}$/
input.gsub(/[^\d-]/,'').to_i
else
"#{input.gsub(/[^\d-]/,'')}00".to_i
end
end
end
Now you're storing cents in the database. Radar has the right idea for pulling it back out.
Tim,
You can try to use the 'aggregation' feature, combined with a delegation class. I would do something like:
class Product
composed_of :balance,
:class_name => "Money",
:mapping => %w(amount)
end
class Money < SimpleDelegator.new
include Comparable
attr_reader :amount
def initialize(amount)
#amount = Money.special_transform(amount)
super(#amount)
end
def self.special_transform(amount)
# your special convesion function here
end
def to_s
nummber_to_currency #amount
end
end
In this way, you will be able to directly assign:
Product.update_attributes(:price => '12.244,6')
or
Product.update_attributes(:price => '12,244.6')
The advantage is that you do not have to modify anything on controllers/views.
Using the translations for numbers in the built-in I18n should allow you to enter your prices in one format (1234.56) and then using I18n bringing them back out with number_to_currency to have them automatically printed out in the correct locale.
Of course you'll have to set I18n.locale using a before_filter, check out the I18n guide, section 2.3.