I'm using Rail's ActiveRecord Validation Confirmation to validate an id. I have two form fields, cbiz_id and cbiz_id_confirmation, so that the fields receive exactly the same content.
cbiz_id is an integer and ActiveRecord makes cbiz_id_confirmation a string.
Is there a way to validate cbiz_id_confirmation as an integer?
validates :cbiz_id, confirmation: true, presence: { if: :has_any_qapps_role?, notice: I18n.t('messages.updated') }
I've attempted a number of things such as:
before_validation :change_cbiz_id_confirmation_to_integer
def change_cbiz_id_confirmation_to_integer
:cbiz_id_confirmation.to_i
end
I'm new to Ruby and Rails, so foundational explanations are appreciated!
Rails converts cbiz_id because it looks at the DB can sees that the column is an integer. Since there's no column for cbiz_id_confirmation, Rails doesn't have any type data, and the default type for any form submission (since form encoding and query params have no types) is a string.
Your change_cbiz_id_confirmation_to_string should work, but you're calling to_i on a symbol. You want just cbiz_id_confirmation.to_i (no leading :). Also, perhaps that method should end in to_integer.
If you want to ensure cbiz_id_confirmation is an integer even outside of validations, you can write a setter method that does the conversion for you.
attr_reader :cbiz_id_confirmation
def cbiz_id_confirmation=(value)
#cbiz_id_confirmation = Integer(value)
rescue TypeError
#cbiz_id_confirmation = nil
end
Integer(value) will raise an exception if the value cannot be converted (eg. it is nil or the word "ham"), so the rescue block makes this act like Rails and treat invalid data as nil. You can also use value.to_i, which that will coerce nil and "ham" to 0, or just let the exception happen and handle the problem elsewhere.
I solved the issue with the following code:
attr_accessor :current_user_qapps_role, :cbiz_id_confirmation
validates :cbiz_id, confirmation: true, presence:true, if: :has_any_qapps_role?
def cbiz_id_confirmation=(val)
#cbiz_id_confirmation = val.to_i
end
You can easily create a custom validation rule like this, but there's probably some more elegant solution to your problem if I'd understand the context:
validate :cbiz_matches
def cbiz_matches
errors.add(:cbiz_id, "ID's don't match") unless self.cbiz_id.to_i == self.cbiz_id_confirmaton.to_i
end
Related
When modifying Active Record object's attributes (like Task.update date: '2015-01-01'), I'd like to know when the value is invalid ('2015-01-32' or 'whatever'). Right now, activerecord just writes nil in case of those, not very friendly I must say. Previous value is lost. I'd like to leave it there, if user inputted nothing like date.
Is there any more sensible way to figure it out than the following?
if ActiveRecord::Attribute.from_user('date', '2015-01-32', ActiveRecord::Type::Date.new).value
puts 'valid date'
end
P.S. Just in case someone wants to know how type casting happens.
If there's no way to tell activerecord to not discard values, and no public interface for checking if a date is valid, than the best option must be to use validation:
class Task < ActiveRecord::Base
validates_date :date
end
you can try to parse it and when it raise error then its nit valid:
valid = Date.parse('2015-01-30').present? rescue false # true
valid = Date.parse('2015-01-32').present? rescue false # false
I have a datetime field in my ActiveRecord model (in Rails) and I need to protect my model from crashes when date fieid is out of ranges.
I found this question and and tried to make similarly:
class Something < ActiveRecord::Base
validate :important_date_is_valid_datetime, on: :create
validates :important_date, presence: true
private
def important_date_is_valid_datetime
if (DateTime.parse(important_date.to_s()) rescue ArgumentError) == ArgumentError then
errors.add(:important_date, 'must be a valid datetime')
end
end
end
This code can 'validate' datetime field but it don't abort field parsing if valie is out of range:
irb(main):007:0> Something.new(important_date: "2015-20-40").valid?
ArgumentError: argument out of range
from /home/shau-kote/.gem/ruby/2.1.0/gems/activesupport-4.2.0/lib/active_support/values/time_zone.rb:350:in `initialize'
from /home/shau-kote/.gem/ruby/2.1.0/gems/activesupport-4.2.0/lib/active_support/values/time_zone.rb:350:in `new'
from /home/shau-kote/.gem/ruby/2.1.0/gems/activesupport-4.2.0/lib/active_support/values/time_zone.rb:350:in `parse'
...
Can I prevent similar crashes?
This is an issue with Rails 4 where the exception gets thrown before your validators are even called. There is a pull request that brings back the behavior of Rails 3 where a invalid date is just nil. This pull request seems to be merged, so maybe updating your Rails version can already fix the problem.
For the meantime I can think of two workarounds: First, you could use client side validations that of course can be tampered with if wanted but would protect users using your app in "good faith" and second, instead of using a validation you could be checking for a valid date (or the ArgumentError respectively) in the controller before the parameter is used to set the datetime attribute. Both options are not optimal but should prevent more crashes.
Lets say I have the following model:
class Car < ActiveRecord::Base
attr_accessible :wheels,
:engine_cylinders
validates :wheels, :engine_cylinders, presence: true, numericality: true
end
Lets say I then have the following controller action:
#car = Car.find(params[:id])
#car.wheels = "foo"
#car.engine_cylinders = 4
#car.save
This save will fail as wheels will fail the numericality condition.
Is there any way to persist the succesful attributes (in this case engine_cylinders), while adding the invalid attributes to the errors array? E.g. is there a "soft" validation in Rails?
You want to write a Custom Validator.
class Car < ActiveRecord::Base
validate :wheel_range,
:engine_cylinder_range
def engine_cylinder_range
flash[:notice] = "engine_cylinder was not saved because it wasn't a number" unless engine_cylinder.is_a? Fixnum
# set engine_cylinder back to old value
end
def wheel_range
flash[:notice] = "wheels was not saved because it wasn't a number" unless wheels.is_a? Fixnum
# set wheels back to old value
end
end
You don't have to use flash here, you could use any variable for internal processing or re-display.
You may also want to put this custom validation check on the :before_save hook. Use the _was magic method to get the old value.
If you're looking to bypass validations you can always do so with:
if #car.save
# ...
else
#car.save(validate: false)
end
You may want to have a different conditional on that or whatever... but this is how you bypass validations on a one-off basis.
This may, however, destroy the errors array so you could rebuild it after the save(validate: false) with:
#car.valid?
You can also bypass validations one-at-a-time using #car.update_attribute(:attribute, <value>).
If you just want to know if the model is valid or not without saving it #car.valid? does exactly that. It also adds invalid attributes to errors array. And #pdobb already pointed out how to bypass validation when saving.
Is there a way to validate that one text_field does not equal another before saving record? I have two text_fields with integers in them and they cannot be identical for record to be valid.
You can add a custom validation:
class Something
validate :fields_a_and_b_are_different
def fields_a_and_b_are_different
if self.a == self.b
errors.add(:a, 'must be different to b')
errors.add(:b, 'must be different to a')
end
end
That will be called every time your object is validated (either explicitly or when you save with validation) and will add an error to both of the fields. You might want an error on both fields to render them differently in the form.
Otherwise you could just add a base error:
errors.add(:base, 'a must be different to b')
In your model:
validate :text_fields_are_not_equal
def text_fields_are_not_equal
self.errors.add(:base, 'Text_field1 and text_field2 cannot be equal.') if self.text_field1 == self.text_field2
end
more fun:
validates :a, exclusion: { in: ->(thing) { [thing.b] } }
Though of course this isn't terribly readable, but it's elegant - we're leveraging the exclusion validation with a proc to prevent the values from being equal. A more verbose approach might be preferred by some, but I'm a fan of brevity - code that doesn't exist can't have bugs. Plus, this will get its error message the default rails location, which may be convenient for i18n purposes.
(better?)
I'm new to RoR/Gems, this is a basic question.
I created a gem, MyNameGem, in order to learn the process. It contains these methods:
def returnValidationString1
puts 'Validation String'
end
def returnValidationString2
puts 'ANother Validation String'
end
I included the gem in a simple rails app, everything seems to be working as expected.
I this to my model:
validates :name => MyNameGem.returnValidationString1
What I'm trying to create is a gem that I can use inside a validation routine. So, for example, I want to do this: validates :name => (call my gem method, return a string, and use that string as the validation requirement)
puts only prints to console.
if you want to return 'MyNameGem' write return 'MyNameGem' or simply 'MyNameGem because the last line gets returned automatically.
The function of puts is to put things to the console, so that's exactly what it will do. What your validates call is doing is kind of unusual though, and doesn't seem to make any sense. Your code evaluates to:
validates :name => (puts "MyNameGem")
That really doesn't mean anything. puts usually returns nil.
If you want to write a custom validation routine that's stored in a gem, that's a different question.