In the system there are employees with login information in the User model and other information about them in the Profile model.
We want to be able to display a list of employees who have an anniversary this month (the month of hire is the same as the current one) and it is their 1st, 2nd, or a multiple of 5 years on the job.
We want to use it like a scope, but since the logic is complex, we are making a Class method. Trying to split the logic into small chunks is becoming messy. I am sure that the code can be simplified.
The biggest issue is that instead of getting a list of only the employees with an anniversary as a scope would do, I am getting a list of all the employees as nil or their user info if it is their anniversary month.
An example:
irb_001 >> Profile.anniversary?
[
[0] nil,
[1] nil,
[2] #<User:0x007fd17c883740> {
:id => 3,
:first_name => "Sally",
:last_name => "Brown",
:email => "sally@peanuts.com",
:password_digest => "[redacted]",
:created_at => Tue, 21 Feb 2018 11:12:42 EST -05:00,
:updated_at => Sat, 25 Feb 2018 12:28:45 EST -05:00,
},
[3] nil,
[4] nil,
[5] #<User:0x007fd17a2eaf38> {
:id => 6,
:first_name => "Lucy",
:last_name => "Van Pelt",
:email => "lucy@peanuts.com",
:password_digest => "[redacted]",
:created_at => Tue, 20 Nov 2018 21:01:04 EST -05:00,
:updated_at => Tue, 20 Nov 2018 21:02:36 EST -05:00,
},
[6] nil
]
irb_002 >>
What is the best way to achieve the desired result and clean up this code?
class User < ActiveRecord::Base
has_one :profile, dependent: :destroy
accepts_nested_attributes_for :profile, allow_destroy: true
after_create :create_matching_profile
delegate :active, to: :profile, prefix: true
private
def create_matching_profile
profile = build_profile
profile.save
end
end
class Profile < ActiveRecord::Base
belongs_to :user
def self.years_employed(profile)
# calculate how many years employed
@profile = profile
if @profile.employed_since?
(( Date.today.to_time - @profile.employed_since.to_time )/1.year.second).to_i
else
0
end
end
def self.anniversary_month(profile)
# get the month of hire
@profile = profile
@profile.employed_since? ? @profile.employed_since.month : 0
end
def self.anniversary?
# first, second, or multiple of five year anniversary month
@profiles = Profile.where("employed_since is not null")
@profiles.map do |profile|
if ( Date.today.month == anniversary_month(profile) )
@years_working = years_employed(profile)
if ( @years_working> 0 &&
( @years_working == 1 || @years_working == 2 || ( @years_working % 5 == 0 )))
result = true
else
result = false
end
else
result = false
end
profile.user if result
end
end
end
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# first_name :string
# last_name :string
# email :string
# password_digest :string
# created_at :datetime not null
# updated_at :datetime not null
#
# Table name: profiles
#
# id :integer not null, primary key
# user_id :integer
# active :boolean
# employed_since :date
# ...other attributes...
# created_at :datetime not null
# updated_at :datetime not null
#
employed since data from Profiles
[
[0] Sun, 01 Dec 1991,
[1] Thu, 01 May 2018,
[2] Wed, 01 Nov 2017,
[3] Wed, 01 Feb 2017,
[4] Thu, 01 Aug 2018,
[5] Fri, 01 Nov 2013,
[6] Fri, 01 Nov 1991
]
This can be done in a much simpler and more efficient way by using the date functions in the database and doing the comparison there.
class User < ApplicationRecord
has_one :profile
def self.anniversary
self.joins(:profile)
.where("EXTRACT(MONTH FROM profiles.employed_since) = EXTRACT(MONTH FROM now())")
.where("profiles.employed_since < ?", 1.year.ago)
.where(%q{
EXTRACT(year FROM now()) - EXTRACT(year FROM profiles.employed_since BETWEEN 1 AND 2
OR
CAST(EXTRACT(year FROM now()) - EXTRACT(year FROM profiles.employed_since) AS INTEGER) % 5 = 0
})
end
end
This example is written for Postgres and you might need to adapt it to your RDBMS.