I have a Price model with 4 different fields:
t.decimal "amount"
t.decimal "amount_per_unit"
t.decimal "unit_quantity"
t.string "unit"
I'm trying to make a custom validation that allows either the amount or amount_per_unit fields (this includes unit quantity and unit) to be filled but not both of them. So to make a word diagram of what I mean.
amount = YES
amount_per_unit + unit + unit_quantity = YES
amount_per_unit (alone or amount.present) = NO
unit_quantity (alone or amount.present) = NO
unit (alone or amount.present) = NO
amount and amount_per_unit + unit + unit_quantity = NO
if you still are confused, just know that its either the amount itself thats filled in or the amount per unit fields that are (1 or 3).
So far I tried this validation in my Price model:
validates :amount, :numericality => true
validates :amount_per_unit, :numericality => true
validates :unit_quantity, :numericality => true
validates :unit, :inclusion => UNITS
validate :must_be_base_cost_or_cost_per_unit
private
def must_be_base_cost_or_cost_per_unit
if self.amount.blank? and self.amount_per_unit.blank? and self.unit.blank? and self.unit_quantity
# one at least must be filled in, add a custom error message
errors.add(:amount, "The product must have a base price or a cost per unit.")
return false
elsif !self.amount.blank? and !self.amount_per_unit.blank? and !self.unit.blank? and !self.unit_quantity
# both can't be filled in, add custom error message
errors.add(:amount, "Cannot have both a base price and a cost per unit.")
return false
else
return true
end
end
This validation doesn't work though as all fields are blank it results to a numericality error and if I fill all of them, it creates the price with all fields filled. What needs to be fixed?
I think your values are coming in as nil, not blank.
Try changing the second condition to:
elsif !self.amount.to_s.blank? and !self.amount_per_unit.to_s.blank? and !self.unit.to_s.blank? and !self.unit_quantity.to_s.blank?
Also, it seems you have a typo on the last condition on both statements (e.g. !self.unit_quantity instead of !self.unit_quantity.to_s.blank?
I hope that helps.
Related
I am using PhoneLib (https://github.com/daddyz/phonelib) to validate my phone numbers.
I have a model Subscription with a column phone_number.
I'm testing this phone number: 3052612151.
I can see that Phonelib considers it to be a valid number:
> Phonelib.valid_for_country? "3052612151", :us
#=> true
However, when I use Phonelib's validator, my record is not considered to be valid.
class Subscription < ApplicationRecord
validates :email, presence: true
validates :phone_number, phone: { countries: :us }
> s = Subscription.create(phone_number: '3052612151', email: 'example#example.com')
> s.valid?
#=> false
> s.errors.full_messages
#=> ["Phone number is invalid"]
I've been scratching my head for over an hour. Can anyone see what I've done wrong? Why does Phonelib consider the number to be valid when calling #valid_for_country? but not when used as a validator?
The only thing I can think of is maybe you should set the default country in an initializer
# config/initializers/phonelib.rb
Phonelib.default_country = "US"
Then just use validates :phone_number, phone: true in your model.
Try using countries: [:us], the validator actually does a & operation and checks if the size is greater than 0, maybe it's expecting an array. https://github.com/daddyz/phonelib/blob/master/lib/validators/phone_validator.rb#L85
The validator actually checks 4 validations, it's a really bad design that all validations return the same error message, weird :S. https://github.com/daddyz/phonelib/blob/master/lib/validators/phone_validator.rb#L63
I havel a model with validators on some fields. Example:
class Notation < ActiveRecord::Base
validates :name, presence: true, uniqueness: true
validates_length_of :name, minimum: 5, maximum: 128
end
is there a way to get those values from minimum and maximum for the :name field in the template (because I want to show there the value of min and max length to the user, and want to do this dynamically, with the template reflecting the values from the model)?
In the rails console, I can do something like
Notation.validators_on(:name)
which outputs
#<ActiveRecord::Validations::PresenceValidator:0x00000005428420 #attributes=[:name], #options={}>, #<ActiveRecord::Validations::UniquenessValidator:0x0000000541b8d8 #attributes=[:name], #options={:case_sensitive=>true}, #klass=Notation (call 'Notation.connection' to establish a connection)>, #<ActiveModel::Validations::LengthValidator:0x0000000540baf0 #attributes=[:name], #options={:minimum=>5, :maximum=>128}>
If I get using the array's index
Notation.validators_on(:name)[2]
I have:
#<ActiveModel::Validations::LengthValidator:0x0000000540baf0 #attributes=[:name], #options={:minimum=>5, :maximum=>128}>
but I was wondering if there is another way, maybe passing the type of validation I'm 'queryng', because I can't rely on the order of the array.
To get the validator, you can filter:
options = Notation.validators_on(:name)
.select { |v| v.is_a? LengthValidator }
.first.options
Then you can get options[:maximum] etc.
In case there is no such validator, you might have to rescue from the code, as otherwise the first will fail.
I'm fairly new to rails and having a problem that I'm sure is not unique, but can't seem to find an elegant solution to on my own:
I have a model with a :tax_rate decimal attribute that is used to create a total later on using some multiplication in the model.
This works just fine if the user enters the tax rate as '0.09' in the tax rate field, but obviously creates an erroniously large amount when the user enters '9'.
I'm looking to create some code that will handle a whole number being entered into my form.
My form:
<td><%= f.input :tax_rate, label: false, input_html: { class: 'taxtarget', precision: 2 }%></td>
The calcualtions in the model:
before_save :persist_calculations
validates_presence_of :price, :units
def calculated_total
price * units + calculated_tax
end
def calculated_tax
if self.tax_rate.present?
self.price * self.tax_rate
else
self.price
end
end
def persist_calculations
self.total = calculated_total
self.tax_amount = calculated_tax
end
I would like to either handle the whole number by preventing a whole number (requiring a decimal point) or by converting a whole number into a decimal by adding two decimal places to it.
Anyone have any good resources or ideas for how to accomplish this?
Either add a validation to your model for tax_rate so that it cannot be a larger than 1.0:
validates :tax_rate, less_than: 1.0
and/or add a before_validation to your model that converts large numbers to small ones:
before_validation ->(obj) {
obj.tax_rate = obj.tax_rate / 100 if obj.tax_rate > 1
}
and/or handle it your controller, which is generally preferable to using callbacks.
I am trying to validate the entry a user makes in an amount field.
The field is amount_money
This field is a string which is validated on form submission
monetize :amount, :as => :amount_money
validates :amount, numericality: {only_integer: true}
validates :amount_money, numericality: {greater_than_or_equal_to: 0}
validate :amount_money_within_limit
validate :is_a_valid_number
I want to ensure there are no letters or symbols and that the amount is in an acceptable range.
the code to do this is
def amount_money_within_limit
if amount_money && amount_money.cents > 10_000_00
errors.add(:amount_money, 'cannot exceed $10,000.')
end
if amount_money && amount_money.cents < 1_00
errors.add(:amount_money, 'Problem with Amount')
end
end
this works great for input numbers, of numbers and letters, of letters, of special characters but
If I try Bob - the validation kicks in
but if I try BBob - the validation is bypassed.
If the input contains 2 Capital letters next to each other - it fails.
I've tried a downcase - but that doesn't suit as the field is monetized (money gem) - and the downcase screws up if there is valid input.
If the input to the field contains two uppercase letters - all the validations are bypassed So something like AA is not caught by any on the above validations
Why don't you use regular expressions? Something like this:
def is_a_valid_number? amount_money
amount_money =~ /\d+/
end
It seems you have put 1 validation on the wrong field, you should've put validations only on amount field (your real db field), and not on the amount_money which is automagical field from rails-money gem. I'll apply their documentation on numerical validations to your case:
monetize :amount,
:numericality => {
:only_integer => true,
:greater_than_or_equal_to => 1_00,
:less_than_or_equal_to => 10_000_00
}
You won't need any other custom validations with this setup.
How can I validate if the input in a text field is a number? not_integer is not what I am looking for. It can be a decimal number.
You can check for numericality
validates :points, numericality: true
If you want a more general approach, you can use is_a?. The parent number class in Ruby is Numeric.
a = 4
a.is_a? Numeric
=> true
b = 5.4
b.is_a? Numeric?
=> true
c = "apple"
c.is_a? Numeric
=> false
d = "4"
d.is_a? Numeric
=> false
To restrict the user from entering non-numeric values at the form-level and avoid expensive server call just to check numericality.
Use this in the form:
<%= f.number_field :attribute_name, :step => 'any' %>
This will create an html element as below:
<input id="post_attribute_name" name="post[attribute_name]" step="any" type="number">
Upon form submission, the input value is checked at the form level. step = "any" will allow decimals.
I would also recommend adding validation at the Model level using,
validates :attribute_name, numericality: true ## As suggested by Justin Wood
This way you have double protection, i.e., one at the form-level itself and the other at Model level.