ERROR: undefined method `year' for nil:NilClass - ruby-on-rails

I have a code to calculate the age of a person, which worked perfectly, and it just stopped working.
Model:
def age
now = Time.now.utc.to_date
now.year - fechanacimiento.year - ((now.month > fechanacimiento.month || (now.month == fechanacimiento.month && now.day >= fechanacimiento.day)) ? 0 : 1)
end
My view:
<%= patient.age %>

def age
now = Time.now.utc.to_date
fechanacimiento && now.year - fechanacimiento.year - ((now.month > fechanacimiento.month || (now.month == fechanacimiento.month && now.day >= fechanacimiento.day)) ? 0 : 1)
end

An alternative is to use the database to calculate the age.
Postgres:
patients = Patient.select(
'patients.*',
'AGE(patients.date_of_birth) AS age'
)
MySQL:
patients = Patient.select(
'patients.*',
'TIMESTAMPDIFF(YEAR, date_of_birth, CURDATE()) AS age'
)
The advantage is that you can order and group the records by age if needed.

Related

Creating a scope with a query from a method on Ruby on Rails

Im trying to create a scope on a Model at my project, but I want that the query filter the elements basead on a method at my Model.
This is my model:
class Talent < ApplicationRecord
scope :public_profile, -> { Talent.all.select{ |t| t.age > 13 } }
def age
now = Time.now.utc.to_date
now.year - self.birth_date.year - ((now.month > self.birth_date.month ||
(now.month == self.birth_date.month && now.day >= self.birth_date.day)) ? 0 : 1)
end
When I try to run this scope I get the error:
NoMethodError: undefined method `year' for nil:NilClass
from app/models/talent.rb:195:in `age'
from (irb):6:in `block in irb_binding'
from (irb):6
The line 195 its on my method age. So, probably when doing the select, my element is coming nil.
What Im doing wrong here?
Given:
NoMethodError: undefined method `year' for nil:NilClass
And given that you're only calling the method year twice in the age method:
def age
now = Time.now.utc.to_date
now.year - self.birth_date.year - ((now.month > self.birth_date.month ||
(now.month == self.birth_date.month && now.day >= self.birth_date.day)) ? 0 : 1)
end
And given that now is certain not to be nil:
now = Time.now.utc.to_date
It would seem that self.birth_date is returning nil. And, as the error states, nil doesn't have the year method.

Ruby on Rails, Select Users with age range from Active Record

User.rb
# Attributes
# (..)
# birthdate (string)
# format "mm/yyyy"
def age
dob = self.birthdate.to_date
now = Time.now.utc.to_date
now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
end
In the console:
irb(main):002:0> current_user.age
=> 7
I'd be able to do the following:
age_range = "25-65"
User.where(:age => between age_range)
I'm stuck at the point how to get the value from age (class method) into the where call
First of all: use a date as type for birthdate in the database.
Then you can just use:
User.where(birthdate: 65.years.ago..25.years.ago)
If you can't change the birthdate type convert it using SQL (example with PostrgeSQL):
User.where('to_date(birthdate, 'MM/YYYY') between ? and ?', 65.years.ago, 25.years.ago)
But you may still have to correct it since you don't have the exact day and only the month.
With PostgreSQL you can use that Rails scope
scope :for_age_range, -> min, max {
where("date_part('year', age(birthdate)) >= ? AND date_part('year', age(birthdate)) <= ?", min, max)
}
User.for_age_range(18, 24)

How to prevent validation of self object

Hi I have a method that check overlapping for new and updated objects.
Method:
def timeline
if start_at_changed? || end_at_changed? || person_id_changed?
if (person.vacations.where('start_at <= ?', start_at).count > 0 &&
person.vacations.where('end_at >= ?', end_at).count > 0) ||
(person.vacations.where('start_at <= ?', start_at).count > 0 &&
person.vacations.where('end_at <= ?', end_at).count > 0 &&
person.vacations.where('end_at >= ?', start_at).count > 0) ||
(person.vacations.where('start_at >= ?', start_at).count > 0 &&
person.vacations.where('start_at <= ?', end_at).count > 0 &&
person.vacations.where('end_at >= ?', end_at).count > 0)
errors.add(:base, 'You have a vacation during this period.')
end
end
end
My issue is while I try edit some vacation. If I change e.g. start_at validation checks also this object and return error. Example: start_at 2016-06-27, end_at: 2016-06-30. I try change start_at to 2016-06-26. Validation returns 'You have a vacation during this period.', because it check period 2016-06-27 - 2016-06-30. How to exclude checking object that I try update, only other?
You just need to exclude the current vacation id on you queries. Something like:
def timeline
if start_at_changed? || end_at_changed? || person_id_changed?
if (person.vacations.where('start_at <= ? and vacations.id != ?', start_at, id).count > 0 &&
person.vacations.where('end_at >= ? and vacations.id != ?', end_at, id).count > 0) ||
...
end
end
end
Also you can use the not condition. More info here and here.
Just to point, you're executing a lot of queries here. This can be very expensive in production. My suggestion is to merge all this queries in just one query, so besides the speed improvements, the readability of your code will be really better.
You code will look like this:
def timeline
if start_at_changed? || end_at_changed? || person_id_changed?
overlapping_vacations = person.vacations.where('vacations.id != ?', id)
overlapping_vacations = overlapping_vacations.where('((start_at <= :start_at AND end_at >= :end_at) OR
(start_at <= :start_at AND end_at <= :end_at AND end_at >= :start_at) OR
(start_at >= :start_at AND start_at <= :end_at AND end_at >= :end_at))',
start_at: start_at, end_at: end_at)
if overlapping_vacations.exists?
errors.add(:base, 'You have a vacation during this period.')
end
end
end

Is it possible to make a background thread run faster?

I am using RubyMotion and have a calendar implemented using CKCalendarView and I have the following code highlighting days on which events occur.
The following method is called from layoutSubviews
def calendarDidLayoutSubviews(calendar)
_events_array = []
if self.events
self.events.reverse.each do |ev|
mdy = ev.date.month_date_year
_events_array << mdy unless _events_array.include?(mdy)
end
end
Dispatch::Queue.main.async do
today = NSDate.date.month_date_year
if calendar.dateButtons && calendar.dateButtons.length > 0
calendar.dateButtons.each do |db|
db.backgroundColor = "f4f2ee".to_color
if db.date && db.date >= calendar.minimumDate && db.date <= calendar.maximumDate
if _events_array && _events_array.length > 0
db.setTitleColor("c7c3bb".to_color, forState:UIControlStateNormal)
db.backgroundColor = "f4f2ee".to_color
if _events_array.include?(db.date.month_date_year)
db.setTitleColor(UIColor.blackColor, forState:UIControlStateNormal)
db.backgroundColor = "9ebf6c".to_color
_events_array.delete(db.date.month_date_year)
end
end
end
if db.date && db.date.month_date_year == today
if calendar.minimumDate <= db.date && calendar.maximumDate >= db.date
db.setTitleColor(UIColor.blackColor, forState:UIControlStateNormal)
else
db.setTitleColor("f4f2ee".to_color, forState:UIControlStateNormal)
end
end
end
end
end
end
As it is, this freezes the UI for a .5 - 1.5s when drawing. If I move it in to a background thread, it takes 4 or 5 times as long to draw, but doesn't freeze the UI.
Question: Is there a way to give the background thread a higher priority, or a way to incrementally draw and highlight the dateButtons so that it doesn't look like nothing is happening in the half dozen seconds it takes to draw(when not in the main thread)?
The method I posted was getting called in the main thread, so I switched it to be called in a background thread, and then called the main thread for all drawing operations inside my loop.
def calendarDidLayoutSubviews(calendar)
_events_array = []
if self.events
self.events.reverse.each do |ev|
mdy = ev.date.month_date_year
_events_array << mdy unless _events_array.include?(mdy)
end
end
today = NSDate.date.month_date_year
if calendar.dateButtons && calendar.dateButtons.length > 0
calendar.dateButtons.each do |db|
Dispatch::Queue.main.async do
db.backgroundColor = "f4f2ee".to_color
end
if db.date && db.date >= calendar.minimumDate && db.date <= calendar.maximumDate
if _events_array && _events_array.length > 0
Dispatch::Queue.main.async do
db.setTitleColor("c7c3bb".to_color, forState:UIControlStateNormal)
db.backgroundColor = "f4f2ee".to_color
end
if _events_array.include?(db.date.month_date_year)
Dispatch::Queue.main.async do
db.setTitleColor(UIColor.blackColor, forState:UIControlStateNormal)
db.backgroundColor = "9ebf6c".to_color
end
_events_array.delete(db.date.month_date_year)
end
end
end
if db.date && db.date.month_date_year == today
if calendar.minimumDate <= db.date && calendar.maximumDate >= db.date
Dispatch::Queue.main.async do
db.setTitleColor(UIColor.blackColor, forState:UIControlStateNormal)
end
else
Dispatch::Queue.main.async do
db.setTitleColor("f4f2ee".to_color, forState:UIControlStateNormal)
end
end
end
end
end
end

Assigning strings to ranges of numbers

This is a really simple problem. I have the following code:
def age_color
age = Time.now() - created_at
age_color = 'green' if age < 2.days
age_color = 'yellow' if age >= 2.days && age <= 5.days
age_color = 'red' if age > 5.days
end
which is not working properly anyway. I think it's ugly and reminds me of my PHP days. How can I write this more elegantly? It must never be nil.
Your construction doesn't work because you put it in wrong order. Try this
def age_color
if created_at < 5.days.ago then 'red'
elsif created_at > 2.days.ago then 'green'
else 'yellow'
end
You could use case:
age_in_days = (Time.now() - created_at).days
age_color = case age_in_days
when 0..1: 'green'
when 2..5: 'yellow'
else 'red'
end
The days method: http://as.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Numeric/Time.html#M000322

Resources