Issue converting currency from string in before_validation callback - ruby-on-rails

I'm having an issue stripping dollar signs and commas out of a currency before validations are run. For some reason the value is being set to 0 when saved.
class Property < ActiveRecord::Base
before_validation :format_currency
validates :street_name_1, presence: true
validates :city, presence: true
validates :state, presence: true
validates :postal_code, presence: true
validates :rate, presence: true, numericality: true
private
def format_currency
self.rate = rate.to_s.gsub(/[^0-9\.]/, '').to_i # Strip out non-numeric characters
end
end
When I do '$3,500'.to_s.gsub(/[^0-9\.]/, '').to_i in the rails console it correctly returns "3500". rate is being passed in to the model properly from the form as well.
For the life of me I can't figure out why it's not properly setting the value on save. Any help would be appreciated.
EDIT: I needed to override the default setter and do my gsub when the rate is set due to the database column being integer.
def rate=(rate)
write_attribute(:rate, rate.to_s.gsub(/[^0-9\.]/, '').to_i)
end

Since you are storing the rate column as an integer, and you are using Rails, I propose that your remove the format_currency method entirely. Rails has a method to deal with currencies already: number_to_currency.
When you want to output the price, simple use the helper:
number_to_currency #property.rate
This gives you greater flexibility too, since you can localise it and pass it a number of other options. Have a look at the API.
Edit, as per OP editing the question:
If you want to save something in that format, why don't you apply the dollar sine in the model?
before_save :set_rate
def set_rate
self.rate = "$#{rate}"
end

Related

Clarification on Ruby syntax in Rails

I'm learning Rails and in going through the official guides, I came across some code which I could not really understand the meaning of.
Case 1 -
class Person < ApplicationRecord
validates :name, presence: true
end
It looks to me that validates is a method that takes a symbol called :name as an argument. But then, what is presence? Is it also a method? But if it is, what is the significance of the : right after presence. I understand that the value true is being set for presence, which serves as kind of a validation, requiring the presence of (in other words). But I'm not quite clear on the syntax.
It might also be possible that presence: true is just a hash, where :presence (the symbol) is the key, and true is the value.
Case 2 -
class Person < ApplicationRecord
validates :terms_of_service, acceptance: true, message: 'must be abided'
end
Again, validates is the method that takes a symbol :terms_of_service as an argument. But what about the rest? Is it a hash with 2 key-value pairs, somewhat like {acceptance: true, message: 'must be abided'}?
And if it is indeed a hash, why is it tacked on to the validates method in each case? Why can't it be
validates :terms_of_service
acceptance: true, message: 'must be abided'
Thanks for the help!
That is the syntax for passing a hash to the method. What that is doing is the same thing as validates(:terms_of_service, {acceptance: true, message: 'must be abided'}). It's a common way of passing extra options to a method.
In Ruby there's a strong tradition for passing in options as a Hash as the last argument, strong enough that this tradition became new feature borrowed from Python: Keyword arguments.
In classic Ruby the method would be defined as this:
def validates(*args)
options = args.last.is_a?(Hash) ? args.pop : { }
# args is a list of fields
end
In Ruby 2.3 you can do this:
def validates(*args, **options)
# options is automatically any hash-style arguments if present
end
In Ruby 2.0+ you can also do this:
def validates(*args, acceptance: false, message: nil)
end
Where that defines options as first-class variables.
It's a common Ruby pattern, so it's good to understand what's going on here. Try writing your own methods that take options and you'll see how it plays out.

Changing data format with before_validation doesn't work

I'm saving some attributes of my model instances as floats, but it's necessary for my app to handle an input from a form with commas instead of points (ex. 10,99 should be saved as 10.99).
I'm trying to do that with before_validation callbacks, but I can't make it work properly - input with commas can't get through validations, I keep getting price/size2 is not a number error.
class Advert < ActiveRecord::Base
before_validation do
normalize(self.price) if is_number?(self.price)
normalize(self.size2) if is_number?(self.size2)
end
validates :price, presence: true, numericality: true
validates :size2, numericality: true
def is_number?(string)
true if Float(string) rescue false
end
def normalize(number)
number.to_s.gsub!(',', '.').to_f
end
Any help would be appreciated.
I havent tested it but i think you are not actually modifying price and size2 as you are gsub!ing number and not returning the value to the 2 attributes.
what about something like this:
class Advert < ActiveRecord::Base
before_validation :string_to_float
validates :price, presence: true, numericality: true
validates :size2, numericality: true
def is_number?(string)
true if Float(string) rescue false
end
def normalize(number)
number.to_s.gsub(',', '.').to_f
end
def string_to_float
self.price = normalize(self.price) if is_number?(self.price)
self.size2 = normalize(self.size2) if is_number?(self.size2)
end
end
Use a custom setter method instead:
class Advert
def price=(val)
#price = val.is_a?(Float) ? val : val.to_f
end
end
As I couldn't handle the decimal delimiter change via rails callbacks, I just wrote a simple jQuery workaround, maybe it'll help someone:
normalize_input = ->
$('form').submit ->
normalized_price = $('#advert_price').val().replace(',', '.') # advert_price is a form input
normalized_size2 = $('#size2input').val().replace(',', '.') # just like the size2input
$('#advert_price').val(normalized_price)
$('#size2input').val(normalized_size2)

in Ruby on Rails is it possible to call a method in the middle of several validations?

I'm trying to find out of it's possible to perform a method call that alters the information going into the database for some attributes, during validations. The desired workflow is: user submits a url, I validate it, if it matches the regex, then embedly is called. The embedly function gets the information for the title and image_url. I would like to perform validations on the title and image_url as well, but these don't exist until I've called the embedly method.
Is there a way to:
1. validate the link_url
2. call the embedly method
3. validate the resulting title and image_url attributes?
Any help is appreciated:
class ListLink < ActiveRecord::Base
belongs_to :list
default_scope -> {order('created_at DESC')}
#the REGEX urls are matched against
VALID_URL_REGEX = /\A(http:\/\/|https:\/\/|www|)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?\z/i
validates :link_url, presence: true,
format:{with: VALID_URL_REGEX, message: "Please enter a valid url."}
validates :list_id, presence: true
#if is a valid url, ping embedly for more information on it
before_save :embedly
#is it possible to call just these 2 validations after the :embedly method?
validates :title, presence: true, length:{minimum: 4, maximum: 200}
validates :image_url, presence: true
private
def embedly
embedly_api = Embedly::API.new :key => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
:user_agent => 'Mozilla/5.0 (compatible; mytestapp/1.0; my#email.com)'
#duplicate the url for use in the embedly API
url = link_url.dup
obj = embedly_api.extract :url => url
#extract and save a title and image element to the database
self.title = obj[0].title
self.image_url = obj[0]["images"][0]["url"]
end
end
You could write a before_validation callback which checks your link_url and if it's valid (i.e. if it matches the URL), performs your embedly stuff. Then, during the regular validation, you can still add the error message to an invalid link_url. This could look something like this:
class ListLink < ActiveRecord::Base
before_validation :embedly_if_valid
# the REGEX urls are matched against
VALID_URL_REGEX = /\A(http:\/\/|https:\/\/|www|)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?\z/i
validates :link_url, presence: true,
format:{with: VALID_URL_REGEX, message: "Please enter a valid url."}
validates :list_id, presence: true
validates :title, presence: true, length:{minimum: 4, maximum: 200}
validates :image_url, presence: true
private
def embedly_if_valid
embedly if self.link_url =~ VALID_URL_REGEX
end
def embedly
embedly_api = Embedly::API.new :key => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
:user_agent => 'Mozilla/5.0 (compatible; mytestapp/1.0; my#email.com)'
#duplicate the url for use in the embedly API
url = link_url.dup
obj = embedly_api.extract :url => url
#extract and save a title and image element to the database
self.title = obj[0].title
self.image_url = obj[0]["images"][0]["url"]
end
end
Note that you will probably get validation errors for the title and image_url fields if the link_url is not valid. You could probably code around this if it is not desired by setting the link_url to nil in a before_validation hook unless it is valid.
Instead of using the before_save callback, I suggest doing this in a custom setter for link_url, something along the lines of...
def link_url=(url)
if url =~ VALID_URL_REGEX
super
embedly
end
end
or, if you're not calling link_url = ... do this in a before_validation callback.
You won't be able to do it the way you mention here. You seem to have different validations depending on the state of the object.
Instead you could run the embedly before save and then validate everything. If you need some validation in order to use the embedly method, you could use a form object (example #3) to separate the different steps of your flow.
You could also use before_validation to run embedly, however it makes it that you need to duplicate the validation on the fields required by embedly (you need to test it in your method before validation, and then on the model in case you need to re-run the method later). You could get passed it with a custom validation method called explicitely, but I'm not a big fan of this.
I would re-think the design a little. Calling an external service like Embedly is best done as a background job as you never know how long it's going to take, and it could block other requests for your application.
You would have to allow the ListLink to be created without a title and image_url, though. Your background worker would then set these attributes when it runs.
You would still want to validate them at this point, of course, but this can be done using conditional validations. Something like this:
validates :title, presence: true, length:{minimum: 4, maximum: 200}, unless: :new_record?

How to validate virtual attribute in Rails?

I have this class:
class Project < ActiveRecord::Base
validates :hourly_rate, :numericality => { :greater_than_or_equal_to => 0 },
:allow_blank => true
def hourly_rate=(number)
self.hourly_rate_in_cents = number.present? ? number.to_d * 100 : nil
end
end
Essentially, any new hourly_rate that gets entered by the user will get saved to the database as an integer.
This works quite well for numbers.
But any string that is being entered, is automatically converted into 0.0 and gets saved without any validation message!
Isn't there a way to validate this using any of Rails' validation methods?
Thanks for any help.
You can create your own validate method and use that to check for the type of object.
For example (and forgive me if there's an error in this code, since it's just off the top of my head):
validate :hourly_rate_is_integer
def hourly_rate_is_integer
errors.add(:hourly_rate, "must be Integer") unless self.hourly_rate.is_a?(Integer)
end
If you have a reader method for this that converts the other way, it will work as you expect. You've only shown the assignment method here.
def hourly_rate
self.hourly_rate_in_cents and self.hourly_rate_in_cents.to_f / 100
end
All the validation routines do is call the given method and apply tests to the result.
You might want to ensure that presence is specifically tested:
validates :hourly_rate, :presence => true, ...

Controlling the order of rails validations

I have a rails model which has 7 numeric attributes filled in by the user via a form.
I need to validate the presence of each of these attributes which is obviously easy using
validates :attribute1, :presence => true
validates :attribute2, :presence => true
# and so on through the attributes
However I also need to run a custom validator which takes a number of the attributes and does some calculations with them. If the result of these calculations is not within a certain range then the model should be declared invalid.
On it's own, this too is easy
validate :calculations_ok?
def calculations_ok?
errors[:base] << "Not within required range" unless within_required_range?
end
def within_required_range?
# check the calculations and return true or false here
end
However the problem is that the method "validate" always gets run before the method "validates". This means that if the user leaves one of the required fields blank, rails throws an error when it tries to do a calculation with a blank attribute.
So how can I check the presence of all the required attributes first?
I'm not sure it's guaranteed what order these validations get run in, as it might depend on how the attributes hash itself ends up ordered. You may be better off making your validate method more resilient and simply not run if some of the required data is missing. For example:
def within_required_range?
return if ([ a, b, c, d ].any?(&:blank?))
# ...
end
This will bail out if any of the variables a through d are blank, which includes nil, empty arrays or strings, and so forth.
An alternative for slightly more complex situations would be to create a helper method which runs the validations for the dependent attributes first. Then you can make your :calculations_ok? validation run conditionally.
validates :attribute1, :presence => true
validates :attribute2, :presence => true
...
validates :attribute7, :presence => true
validate :calculations_ok?, :unless => Proc.new { |a| a.dependent_attributes_valid? }
def dependent_attributes_valid?
[:attribute1, ..., :attribute7].each do |field|
self.class.validators_on(field).each { |v| v.validate(self) }
return false if self.errors.messages[field].present?
end
return true
end
I had to create something like this for a project because the validations on the dependent attributes were quite complex. My equivalent of :calculations_ok? would throw an exception if the dependent attributes didn't validate properly.
Advantages:
relatively DRY, especially if your validations are complex
ensures that your errors array reports the right failed validation instead of the macro-validation
automatically includes any additional validations on the dependent attributes you add later
Caveats:
potentially runs all validations twice
you may not want all validations to run on the dependent attributes
Check out http://railscasts.com/episodes/211-validations-in-rails-3
After implementing a custom validator, you'll simply do
validates :attribute1, :calculations_ok => true
That should solve your problem.
The James H solution makes the most sense to me. One extra thing to consider however, is that if you have conditions on the dependent validations, they need to be checked also in order for the dependent_attributes_valid? call to work.
ie.
validates :attribute1, presence: true
validates :attribute1, uniqueness: true, if: :attribute1?
validates :attribute1, numericality: true, unless: Proc.new {|r| r.attribute1.index("#") }
validates :attribute2, presence: true
...
validates :attribute7, presence: true
validate :calculations_ok?, unless: Proc.new { |a| a.dependent_attributes_valid? }
def dependent_attributes_valid?
[:attribute1, ..., :attribute7].each do |field|
self.class.validators_on(field).each do |v|
# Surely there is a better way with rails?
existing_error = v.attributes.select{|a| self.errors[a].present? }.present?
if_condition = v.options[:if]
validation_if_condition_passes = if_condition.blank?
validation_if_condition_passes ||= if_condition.class == Proc ? if_condition.call(self) : !!self.send(if_condition)
unless_condition = v.options[:unless]
validation_unless_condition_passes = unless_condition.blank?
validation_unless_condition_passes ||= unless_condition.class == Proc ? unless_condition.call(self) : !!self.send(unless_condition)
if !existing_error and validation_if_condition_passes and validation_unless_condition_passes
v.validate(self)
end
end
return false if self.errors.messages[field].present?
end
return true
end
I recall running into this issue quite some time ago, still unclear if validations order can be set and execution chain halted if a validation returns error.
I don't think Rails offers this option. It makes sense; we want to show all of the errors on the record (including those that come after a failing, due to invalid input, validation).
One possible approach is to validate only if the input to validate is present:
def within_required_range?
return unless [attribute1, attribute2, ..].all?(&:present?)
# check the calculations and return true or false here
end
Make it pretty & better structured (single responsibility) with Rails idiomatic validation options:
validates :attribute1, :presence => true
validates :attribute2, :presence => true
# and so on through the attributes
validate :calculations_ok?, if: :attributes_present?
private
def attributes_present?
[attribute1, attribute2, ..].all?(&:present?)
end
def calculations_ok?
errors[:base] << "Not within required range" unless within_required_range?
end
def within_required_range?
# check the calculations and return true or false here
end

Resources