Reputation: 99
Based on 'PESEL" number I have to group user by their age. I created something like this and it is working, but... To be honest, it look bad for me.
HELPER:
def years(pesel)
years = (0..99).to_a
birth_year = []
case pesel[2..3].to_i
when 0..19
20.times do |index|
first_number = index % 2 == 0 ? (5 * index) : ((5 * index))
second_number = index % 2 == 0 ? (5 * index + 4) : ((5 * index) + 4)
first_year = Date.today.year - second_number.to_s.rjust(4,'1900').to_i
second_year = Date.today.year - first_number.to_s.rjust(4,'1900').to_i
birth_year += ["#{first_year}-#{second_year}"]
end
multiplied_birth_years = ([birth_year] * 5).inject(&:zip).flatten
hash = Hash[years.zip multiplied_birth_years]
hash.fetch(pesel[0..1].to_i)
when 20..39
20.times do |index|
first_number = index % 2 == 0 ? (5 * index) : ((5 * index))
second_number = index % 2 == 0 ? (5 * index + 4) : ((5 * index) + 4)
first_year = Date.today.year - second_number.to_s.rjust(4,'2000').to_i
second_year = Date.today.year - first_number.to_s.rjust(4,'2000').to_i
birth_year += ["#{first_year}-#{second_year}"]
end
multiplied_birth_years = ([birth_year] * 5).inject(&:zip).flatten
hash = Hash[years.zip multiplied_birth_years]
hash.fetch(pesel[0..1].to_i)
when 40..59
20.times do |index|
first_number = index % 2 == 0 ? (5 * index) : ((5 * index))
second_number = index % 2 == 0 ? (5 * index + 4) : ((5 * index) + 4)
first_year = Date.today.year - second_number.to_s.rjust(4,'2100').to_i
second_year = Date.today.year - first_number.to_s.rjust(4,'2100').to_i
birth_year += ["#{first_year}-#{second_year}"]
end
multiplied_birth_years = ([birth_year] * 5).inject(&:zip).flatten
hash = Hash[years.zip multiplied_birth_years]
hash.fetch(pesel[0..1].to_i)
end
end
CONTROLLER:
def grouped_by_age
@yearsbook = @study_participations.includes(user: :profile).group_by do |study_participation|
years(study_participation.user.profile.pesel)
end
end
A small explanation and example. I am interested in first 6 numbers that correspond sequentially: Year of birth, month, day So if my PESEL == '980129(...)', then I was born twenty-ninth of January 1998 If someone was born in year 2000, then we add 20 to pesel-month number(for example '002129(...)' it is twenty-ninth of January 2000. If someone was born 2100, then we add 40 to pesel-month number. I have explained what the pesel number is all about, now what I want to do with it. I need to group users by their age range. Function from above returns has like this:
{0=>"118-122",
1=>"118-122",
2=>"118-122",
3=>"118-122",
4=>"118-122",
5=>"113-117",
6=>"113-117",
7=>"113-117",
8=>"113-117",
9=>"113-117",
10=>"108-112",
11=>"108-112",
12=>"108-112",
13=>"108-112",
14=>"108-112",
15=>"103-107",
16=>"103-107",
17=>"103-107",
18=>"103-107",
19=>"103-107",(...)}
Unfortunately this is not very efficient, because for each user (4000 max) I have to execute the functions from scratch. Is there any way to increase efficiency of this? I thought about storing this hash as const and changing it once a year, but I don't really know how to do that or if it is possible.
EDIT: Forgot to mention: I need to compare user age with hash, so I can extract age range
EDIT2: Based on @yoones answer I created something like this:
HELPER:
def years_cache
years = []
201.times do |index|
years += [Date.today.year - (1900 + index)]
end
birth_year = []
60.times do |index|
year = if index < 20
'1900'
elsif index < 40
'2000'
else
'2100'
end
first_number = 5 * (index % 20)
second_number = (5 * (index % 20)) + 4
first_year = Date.today.year - second_number.to_s.rjust(4, year).to_i
second_year = Date.today.year - first_number.to_s.rjust(4, year).to_i
birth_year += ["#{first_year}-#{second_year}"]
end
multiplied_birth_years = ([birth_year] * 5).inject(&:zip).flatten
@hash = (years.zip multiplied_birth_years).to_h
end
def years(cache, pesel)
day = pesel[4..5]
case pesel[2..3].to_i
when 0..19
month = pesel[2..3]
year = pesel[0..1].prepend('19')
when 20..39
month = (pesel[2..3].to_i - 20).to_s
year = pesel[0..1].prepend('20')
when 40..59
month = (pesel[2..3].to_i - 40).to_s
year = pesel[0..1].prepend('21')
end
birth_date = Time.strptime("#{day}/#{month}/#{year}", '%d/%m/%Y')
age = ((Time.zone.now - birth_date) / 1.year.seconds).floor
cache.fetch(age)
end
CONTROLLER:
def grouped_by_age
cache = years_cache()
@yearsbook = @study_participations.includes(user: :profile).group_by do |study_participation|
years(cache, study_participation.user.profile.pesel)
end
end
Upvotes: 0
Views: 183
Reputation: 2474
I guess you could at least build the cache once then use it in your loop. The following code is not pretty, it's just to illustrate what I mean:
def build_year_cache(index, rjust_str)
first_number = 5 * index
second_number = index % 2 == 0 ? (5 * index + 4) : ((5 * index) + 4)
first_year = Date.today.year - second_number.to_s.rjust(4, rjust_str).to_i
second_year = Date.today.year - first_number.to_s.rjust(4, rjust_str).to_i
"#{first_year}-#{second_year}"
end
def build_years_cache
cache = {}
years = (0..99).to_a
[
[0..19, '1900'],
[20..39, '2000'],
[40..59, '2100']
].each do |range, rjust_str|
birth_year = []
20.times do |index|
birth_year.append(build_year_cache(index, rjust_str))
end
multiplied_birth_years = ([birth_year] * 5).inject(&:zip).flatten
cache[range] = Hash[years.zip multiplied_birth_years]
end
cache
end
def years(pesel, cache)
year = pesel[0..1].to_i
month = pesel[2..3].to_i
range = cache.keys.find { |k, v| k.include?(month) }
cache[range].fetch(year)
end
def grouped_by_age
cache = build_years_cache
@yearsbook = @study_participations.includes(user: :profile).group_by do |study_participation|
years(study_participation.user.profile.pesel, cache)
end
end
Upvotes: 0
Reputation: 4348
Instead of doing the complicated calculating of birth date from PESEL every time you want to view the page, do it once and store it in the database. Having a birth date column on the user makes a lot of sense.
Then when you want to group them, you can even do it via the database. If you still need to do it in ruby, then getting the birth year is as easy as user.birth_date.year
In order to then group users into ranges of 5 years according to age, add an age_range
method to the model and group by that.
@study_participations.includes(user: :profile).group_by do |study_participation|
study_participation.user.age_range
end
Where age_range
can be for example
def age_range
(Date.today.year - birth_date.year) / 5) * 5
end
Format that however you like
Upvotes: 2