Group Users by Age Range in ruby
You want to build some SQL that looks like this:
select count(*),
case
when age between 10 and 14 then '10 - 14'
when age between 15 and 21 then '15 - 21'
-- ...
end as age_range
from users
where age between 10 and 120
group by age_range
In ActiveRecord terms, that would be:
# First build the big ugly CASE, we can also figure out the
# overall max and min ages along the way.
min = nil
max = nil
cases = AGE_RANGES.map do |r|
min = [r[:min], min || r[:min]].min
max = [r[:max], max || r[:max]].max
"when age between #{r[:min]} and #{r[:max]} then '#{r[:min]} - #{r[:max]}'"
end
# Then away we go...
age_ranges = Users.select("count(*) as n, case #{cases.join(' ')} end as age_range")
.where(:age => min .. max)
.group('age_range')
.all
That will leave you with an array of objects in age_ranges
and those objects will have n
and age_range
methods. If you want a Hash out of that, then:
age_ranges = Hash[age_ranges.map { |r| [r.age_range, r.n] }]
That won't include ranges that don't have any people in them of course; I'll leave that as an exercise for the reader.
Group users by age range in rails
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
How to list the ages of users from their DOB
create instance method to compute age of a user
app/models/user.rb
:def age
now = Time.now.utc.to_date
now.year - birthdate.year - ((now.month > birthdate.month || (now.month == birthdate.month && now.day >= birthdate.day)) ? 0 : 1)
endCreate object representing an age group (I'll go with tableless model, but it can be pure ruby object or even struct)
app/models/age_group.rb
:class AgeGroup
include ActiveModel::Model # not really necessary, but will add some AM functionality which could be nice later
attr_accessor :from, :to, :count
endCreate service object for computing age groups (Choosing service object is just my personal preference. You can create a helper or whatever you think will fit your needs the best).
app/services/age_groups_service.rb
:class AgeGroupService
# @params
# ranges - an array of age group ranges. E.g.: [[0, 18], [19, 24], [25, 34], ...]
# users - an array of users from which the age groups will be computed. Defaults to all users
def initialize(ranges = [], users = User.all.to_a)
@ranges = ranges
@users = users
@age_groups = []
end
# Count users in each age group range
# @return
# an array of age groups. E.g.: [{ from: 0, to: 18, count: 12 }, ...]
def call
@ranges.each do |range|
users = @users.select { |user| user.age >= range[0] && user.age <= range[1] }
@age_groups << AgeGroup.new(from: range[0], to: range[1], count: users.length)
end
@age_groups
end
endAssign value returned by call to the
AgeGroupsService
to an instance variable in your controller:age_group_service = AgeGroupsService.new([[18, 24], [25, 34], [35, 44], [45, 100]])
@age_groups = age_group_service.callPrint out the results in a view:
<ul>
<% @age_groups.each do |age_group| %>
<li><%= age_group.from %> - <%= age_group.to %>: <b><%= age_group.count %></b></li>
<% end %>
</ul>
Some further ideas / notes:
AgeGroup
model andAgeGroupsService
aren't necessary. You can use PORO and helpers instead or you can even put all the code intoUser
model and relevant controller. Anyway I would strongly recommend against this as it will go messy and it just feels wrong. Age groups seem like something deserving it's own place.- This is very simple solution. It will work fine if you don't have a lot of users. This implementation would be problematic in a production app with thousands of users. The problem here is the computational complexity of the algorithm assigning users into age groups. The algorithm iterates through every user for each age group range. You could solve this problem in different ways: 1. improve the algorithm by reducing its complexity or number of users it has to iterate over. My first try would be to make sure the iteration over the ranges will stop as soon as it's clear there aren't any more users for the current range and iterations over all following ranges won't iterate over users already assigned to a range. 2. use SQL for the computation if you aren't afraid of using raw SQL and don't mind loosing the DB agnosticity. You can create a combined query or even a DB view which will return the count of all users with birthday between two dates. 3. Make it work asynchronously. Move the computation into backround (so it won't block the controller) and then load it using async call in Javascript.
Ruby on Rails, Select Users with age range from Active Record
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.
How to group records in ranges of their created_at age in Rails
You don't need count
in the end, you query should like this
Deal.select(sql).group(:day_range)
see the example result in here
making a pie-chart of the user age in rails
So for thousands of users I'd definitely do this in the database. While the labels will probably need some massaging, you can start with a query like this:
User.group("date_trunc('year', age(dob))").count
This will result in a hash with entries that look something like this:
{
"00:00:00" => 3,
"1 year" => 5,
"2 years" => 8,
...
}
You can then do relabelling and group the year results into bins (e.g. 10-20) as needed. I wouldn't try to do this all in one line in your view - I'd make this a method either on the model or on a dedicated query object.
search for users age based on date of birth
This should do it:
# view
<%= select @object, :min_age, (18..75).to_a %>
to
<%= select @object, :max_age, (18..75).to_a %>
# controller
def show
@search = Search.find(params[:id])
@users = @search.users
end
# Search model
def find_users
users = User.order(:id)
users = users.where(gender: gender) if gender.present?
users = users.where(zip_code: zip_code) if zip_code.present?
users = users.where(children: children) if children.present?
users = users.where(religion: religion) if religion.present?
users = users.where(ethnicity: ethnicity) if ethnicity.present?
if min_age.present? && max_age.present?
min = [ min_age, max_age ].min
max = [ min_age, max_age ].max
min_date = Date.today - min.years
max_date = Date.today - max.years
users = users.where("birthday BETWEEN ? AND ?", max_date, min_date)
end
users
end
Related Topics
Ruby - Naming Convention - Letter Case for Acronyms in Class/Module Names
Do I Need to Indent My Code in Ruby
How to Call a Method That Is a Hash Value
Ruby Regex to Capture Everything Between Two Strings (Inclusive)
Ruby on Rails: Creating a Model Entry with a Belongs_To Association
Flash Message with HTML_Safe from the Controller in Rails 4 (Safe Version)
How to Convert 1 to "First", 2 to "Second", and So On, in Ruby
Bundle Exec Jekyll Serve: Cannot Load Such File
Ruby on Rails: Params Is Nil. Undefined Method '[]' for Nil:Nilclass
How to Include Ё in [А-Я] Regexp Char Interval
Building a Simple Search Form in Rails
Is Ruby Any Good for Gui Development
Show Markers on Google Maps Dynamically -Rails 3.2
Validate That String Contains Only Allowed Characters in Ruby
Ruby 'Require' Call Fails on Custom Code