NoMethodError: undefined method `<' for nil:NilClass - ruby-on-rails

I'm adding a custom validation in my application while creating a coupon, start date of the validity for the coupon should be before the end date.
validate :to_must_be_after_from
private
def to_must_be_after_from
if valid_to < valid_from
self.errors[:base] << "Coupon cannot expire before the start of its validity period."
end
end
valid_to and valid_from are the date fields.
When I'm running this, NoMethodError occurred. I have the following question regarding this,
'<' is an operator and not a function, then how such error can occurs.
How to fix this and make the code function properly.

Most operators are actually methods in Ruby. This code:
valid_to < valid_from
is merely syntactic sugar to
valid_to.<(valid_from)
The error message is pretty much self explanatory, you have to make sure valid_to and valid_from are not nil, using guard clause, for example, i.e. like this:
def to_must_be_after_from
return if valid_to.blank? || valid_from.blank?
# rest of the code
end

First you need to check whether valid from or valid to is blank or not. then you can check the value is less than or greater than .
def to_must_be_after_from
return if valid_from.blank? || valid_to.blank?
if valid_from < Date.today
errors.add(:base, "You can't select past dates in valid from")
elsif valid_to < valid_from
errors.add(:base, "valid to can't be before the valid from date")
end
end

Based on the error message-
You are trying to compare variables in which one variable is nil.
Can you check if both valid_to and valid_from are having proper values, before checking the validation - if valid_to < valid_from ?

Related

How to check range between two dates with rails 6

Hello i have this blog module where my posts can have 3 states "no_status" "in_draft" and "published", the user can set the publish_date and publish_end_date for his posts
while this range between the dates is fulfilled, the status of the post must be "published", and when it is finished return to "in_draft"
def post
if self.no_status? || self.in_draft?
if self.publish_date >=Date.today && self.publish_end <= Date.today
self.update_attribute :status, 'published'
end
elsif self.published?
if self.publish_date.past? && self.publish_end.past?
self.update_attribute :status, 'in_draft'
end
end
end
What is the proper way to manage this, i have a big problem with my conditions.
In the 1st branch your conditionals are mixed up now, they should be opposite (that's why it doesn't work as expected - you check than publish_date is greater that current date, but this is wrong - it must be in the past to have the post published today). So if you simply "mirror" your conditional operators it should work - but there are cleaner ways of writing the same:
Date.today.between?(publish_date, publish_end)
# or
(publish_date..publish_end).cover?(Date.today)
In the 2nd branch checking that pulish_end date is n the past should be enough. Checking if publish_date is in the past too is redundant - if it is not you have bigger problems what just a wrong status :) - this kind of basic data integrity is better to be addressed by model validations.
Also, the nested ifs are absolutely unnecessary here, they just make the code harder to reason about.
To summarize, something like the following should do the job (I'm not discussing here how this method is being used and whether it should be written this way or not - just addressing the initial question)
def post
new_status =
if published? && publish_end.past?
'in_draft'
elsif Date.today.between?(publish_date, publish_end)
'published'
end
update_attribute :status, new_status
end
You can use Object#in?
def post
if no_status? || in_draft?
update(status: 'published') if Date.today.in?(publish_date..publish_end)
elsif published?
update(status: 'in_draft') if publish_date.past? && publish_end.past?
end
end
(BTW you don't need self in Ruby every time)
You can use Range#cover?. It basically takes a range and checks if the date is withing the start/end;
(10.days.ago..1.day.ago).cover?(3.days.ago)
# true
So, in your case;
(publish_date..publish_end).cover?(Date.today)

How to manage dates in Custom Validations in Rails

Hello Devs I'm trying to compare dates in a custom validator, but it seems that i'm not doing it properly.
i need to make a condition for a document, if 90 days have passed since the date of expiration, if its true then return an error.
class CheckDocumentValidator < ActiveModel::Validator
def validate(record)
expiration_date = record.expiration_date
actual_date = Time.current
diff = ((actual_date - expiration_date.to_time)/3600).round
days_diff = diff/24
if days_diff > 90
record.errors.add(:expiration_date, "error")
end
end
end
expiration_date is a date attribute on my model AttachmentInstance
In the logs says that -- Error: undefined method `to_time' for nil:NilClass
i think the error
Error: undefined method `to_time' for nil:NilClass
is because no data found on record.expiration_date.
if record.expiration_date is Time class. it should be like this
if record.expiration_date + 90.day > Time.now
record.errors.add(:expiration_date, "error")
end

updating a model attribute that will only change once

I have a subject model with attributes including a start_date and end_date - as well as a completed boolean attribute.
In subject.rb, I have a method to find how many weeks are remaining for a given subject:
def weeks_left
time = (self.end_date.to_time - Date.today.to_time).round/1.week
if time < 0
"completed"
elsif time < 1
"less than 1 week"
elsif time == 1
"1 week"
else
"#{time} weeks"
end
end
I want to tick the completed attribute if self.weeks_left == "completed" and the best way to do that seems like a call back, but I'm a bit unsure about using after_find - in general it seems like it would be too many queries, and indeed too big of a pain (especially after reading this) - but in this case, once a subject is complete, it's not going to change, so it seems useless to check it's status more than once - what's the best way to handle this?
Why dont you make a scope for this?
scope :completed, ->{where("end_date <= ?", Time.now)}
and a method
def completed?
self.weeks_left == "completed"
end
Looks like you need ActiveRecord::Callbacks. You can see more information here or on rails guide
before_save :update_completed
def update_completed
if (end_date_changed?)
time = (self.end_date.to_time - Date.today.to_time).round/1.week
self.complete = time < 0
end
end
This way you update the complete flag whenever end_date changes and it would always be in sync.
However because this is a calculated value you could also not store it as an attribute and simply define a method to get it
def complete
time = (self.end_date.to_time - Date.today.to_time).round/1.week
return time < 0
end

NoMethodError when method is returning nil

I have two methods which are used to determine whether to apply a class to the page to show that something is overdue and needs attention.
I'm getting an error when a brand new user registers:
undefined method `last_contact_done_date=' for #<User:0x6183708>
The line that it's referencing the error to is this:
2: <div class="span3 <%= "overdue" if (signed_in? && contact_overdue?(current_user.id)) %>">
The contact_overdue? method is this (in a home page helper)
def contact_overdue?(user_id)
#user = User.find_by_id(user_id)
return true if (Date.today - (#user.last_contact_done_date ||= Date.tomorrow)) > 6
end
and the last_contact_done_date method is this in the User model
def last_contact_done_date
self.contacts.order('date_done DESC').first.try(:date_done)
end
I thought that if I was using the ||= operator in the contact_overdue? method, then I would return -1 if the last_contact_done_date is nil. But it appears that's not working. What operator should I be using in last_contact_done_date or how should I change the contact_overdue? method so that if there are no contacts, then false is returned from the contact_overdue? method?
To return the default value of -1 when there is no last contact done date, use
#user.last_contact_done_date || -1
(it is unreasonable to expect Date.tomorrow to return -1 ;) )
||= is an assignment operator; a ||= b is equivalent to a = a || b; and if a is an attribute (i.e. is prefixed with a dot and an instance, c.a), assignment to it will call the method a=. Thus, your code necessitates that you have a method named last_contact_done_date= to handle the assignment, which you don't.

Clarifying a custom Rails 3.0 Validation with methods

I've created a custom validator in Rails 3.0 which validates whether a combination of columns is unique within a table. The entire code of the validation is:
class UniqueInProjectValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
unless object.class.where("project_id = ? AND #{attribute} = ?", object.project_id, value).empty?
if object.new_record?
object.errors[attribute] << (options[:message] || "must be unique in each project")
else
orig_rec = object.class.find(object.id)
if value != orig_rec.method(attribute).call || object.project_id != orig_rec.project_id
object.errors[attribute] << (options[:message] || "must be unique in each project")
end
end
end
end
Note that it is not easy to recognize what the if statements do, so I was hoping to be able to replace the unless conditional with a def attribute_and_project_exist? method and the second if statement with a def attribute_or_project_changed? method. However when creating those methods, the arguments from validates_each do not pass because of encapsulation.
Now the question: Is there a way to somehow cleanly allow those variables to be accessed by my two newly created methods as one can do with column names in a model, or am I stuck with the options of either passing each argument again or leaving the hard to read conditional statements?
Thanks in advance!
I suppose you could clean it up a bit with one variable, one lambda, and one "return as soon as possible":
def validate_each(object, attribute, value)
# If there is no duplication then bail out right away as
# there is nothing to check. This reduces your nesting by
# one level. Using a variable here helps to make your
# intention clear.
attribute_and_project_exists = object.class.where("project_id = ? AND #{attribute} = ?", object.project_id, value).empty?
return unless attribute_and_project_exists
# This lambda wraps up your second chunk of ugly if-ness and saves
# you from computing the result unless you have to.
attribute_or_project_changed = lambda do
orig_rec = object.class.find(object.id)
value != orig_rec.method(attribute).call || object.project_id != orig_rec.project_id
end
# Note that || short-circuits so the lambda will only be
# called if you have an existing record.
if object.new_record? || attribute_or_project_changed.call
object.errors[attribute] << (options[:message] || "must be unique in each project")
end
end
I don't know how much better that is than your original but the logic and control flow is a lot clearer to me due to the nicer chunking.

Resources