How do I recognize invalid dates in rails? - ruby-on-rails

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

Related

Detecting if value of attribute changed during last update doesnt work with Active Model Dirty

I am trying to send a notification email in my rails app only if the value of my column status was modified by the current update. I tried using Active Model Dirty as was suggested in some post and the status_changed? method. Unfortunately my email is never sent because #partnership.status_changed? constantly returns false even though the value of status was indeed changed during the last update. Here's my controller code :
def update
authorize #partnership
if #partnership.update(partnership_params)
send_notification_email
render json: {success: "partnership successfully updated"}, status: 200
else
render_error(nil, #partnership)
end
end
private
def send_notification_email
PartnershipMailer.partnership_status_change(#partnership).deliver_now if #partnership.status_changed?
end
I have also included Active Model Dirty in my model :
class Partnership < ActiveRecord::Base
include ActiveModel::Dirty
What am I doing wrong ?
.update also saves the model after updating it's data, therefore resetting the dirty-values. Try using .assign_attributes. It will just assign the attributes, then you can check for changes, and finally remember to save the model.
As #Thounder pointed out, the ActiveModel::Dirty method <attribute>_changed? is reset whenever you save a record. Thus, it only tracks changes between saves.
For your use case, what you want to use is the previous_changes method, which returns a hash with the key being the attribute changed and the value being an array of 2 values: old and new.
person = Person.new(name: "Bob")
person.name_changed? # => true
person.save
person.name_changed? # => false (reset when save called)
person.previous_changes # => { name: [nil, "Bob"] }
person.previous_changes[:name] # => returns a "truthy" statement if :name attribute changed
My pseudo-code may be wrong, but the principle works. I've been bitten by this "gotcha" before, and I wish the Rails core team would change it.
I understand their reasoning, but it makes more sense to me to track <attribute>_changed? after a save as well, because that seems the common use case to me.
You can try this method to check the changed attributes for the active record.
#partnership.changed.include?("status")
If it returns true then we have status attribute which was changed in this record.
Use #partnership.saved_change_to_status? or #partnership.saved_change_to_attribute(:status) as per docs.
Here is a one line method you can into the model which is the best for your case :
after_commit :send_notification_email, if: Proc.new { |model| model.previous_changes[:status]}

Validation Confirmation - Change Confirmation From String to Integer

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

ActiveRecord: abort datetime parsing if value is invalid

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.

How to validate (and handle) if value passed to .to_date is valid or returns error?

If user inserts a value that is not accepted to the to_date method, e.g. "some gibberish", it returns:
"ArgumentError: invalid date"
How could I construct a custom validation that returns an validation message to user and doesn't halts the application?
I was thinking that reusing the code from another answer would be a good idea, but I don't know how to apply it to to_date.
validate do
self.errors[:start] << "must be a valid date" unless DateTime.parse(self.start) rescue false
end
It could be achieved with rescue, and then addes to errors
class Post < ActiveRecord::Base
validate do |post|
begin
Date.parse(post.date)
rescue ArgumentError
errors.add(:date, "must be a valid date")
end
end
## Another way
validate do |post|
# make sure that `date` field in your database is type of `date`
errors.add(:date, "must be a valid date") unless post.date.try(to_date)
end
end
Rather than writing your own validation (and having to write tests for it!) I suggest the validates_timeliness gem
Using it is as simple as
validates_date :attribute_name

Convert data when putting them into a database using active record

What's the cleanest way to convert data (field values) when storing/loading into/from a database.
I usually convert the values the user has entered in the model class:
def date_str
format_date(self.date)
end
def date_str=(str)
self.date = DateParser.parse(str)
end
(Note: The internal date parser is not sufficient, it doesn't cover many of our locale date notation styles, anyway).
This works already pretty fine, but I doesn't let me check for validity the usual RoR way because we don't store the string the user has entered. I usually let the parser function return nil if anything was wrong with the date, but this isnt very user-friendly – I cant really tell if the user hasn't entered anything or just a invalid value.
I'm looking for some way to integrate the conversion better into the A::R world where I can produce some useful error message if the conversion fails. Best would be if I could use the same facilities as the A::R::Adapter uses internally.
Has anybody some good ideas? What are other approaches?
The way you've suggested is fine, another alternative would be to do something like
attr_accessor :when
before_validation :update_date
def update_date
self.date = parse_date(self.when) if self.when
end
def parse_date(date_as_string)
# do something with the date
end
However, you're going to have write the validation yourself. It's not ideal, but it shouldn't be that much code.
If you're planning on using this all over your app, you should maybe look at writing a custom validator too, you can get more info about that here:
http://marklunds.com/articles/one/312
You can do this in a before validation callback that way you can use the normal AR validations on date.
before_validation :convert_date
def convert_date
self.date = DateParser.parse(self[:date])
end
The attribute_normalizer provides a nice dsl for doing this kind of thing like this:
class Klass < ActiveRecord::Base
normalize_attributes :date do |value|
value.is_a?(String) ? DateParser.parse(value) : nil
end
end

Resources