Rails - Upcoming birthdays for a selected month

If you have an application that stores people and their birth dates, it can be suprisingly difficult to find out whose birthdays are this month or next month.

There are two common approaches used for this:

  1. Use a SQL query that compares the month (and possibly day) only out of the date of birth field.
  2. Add additional fields (birth day and birth month) into your data to help you achieve this goal.

This post will go with option 2, as it is very difficult to find a SQL query solution that works on all databases (eg. MySQL uses Month(xx) whereas SQLite requires a string format function).

So we have a model "User" and it has a field "date_of_birth". We want to add two new fields to this model for the day and month of the date of birth. The following migration generator will achieve this:

rails generate migration add_birth_date_fields_to_users birth_day:integer birth_month:integer

You can then update your database.

rake db:migrate

The next step is to make sure you populate these fields for all of your existing records. You could have done this in the migration, but I like to do this in a separate rake task.

Create a rake file in your lib folder called "update_birth_date_fields.rake" This rake task will iterate through all users that have a date of birth, and will update the fields. This can take a while if you have a lot of records, it is why I like to separate it into a rake task.

desc "Updating birth dates from date of birth"
task :update_birth_date_fields => :environment do
  puts "Updating birth dates from date of birth"
  
  User.where('date_of_birth is not null').each do |u|
    u.birth_month = u.date_of_birth.month
    u.birth_day = u.date_of_birth.day
    u.save
  end

  puts "Finished updating birth dates from date of birth"
end

You can run this now with

rake update_birth_date_fields

Now we need to make sure that all new date of births that are added or edited will also populate these fields. We do this with a before_save callback. In your user.rb model file, add this code:

before_save :update_birth_date_fields

private
def update_birth_date_fields
  if date_of_birth_changed?
    self.birth_month = date_of_birth ? date_of_birth.month : nil
    self.birth_day = date_of_birth ? date_of_birth.day : nil
  end
end

Once this is all done, it is easy to access all users with birthdays this month:

User.where(birth_month: Time.zone.now.month).order(:birth_day)

This works out to be a very efficient query compared to other alternatives mentioned earlier.

By Joel Friedlaender