Rails class 方法用作具有复杂逻辑的范围

Rails class method used as scope with complex logic

系统中有员工在用户模型中有登录信息,并且 Profile 模型中有关它们的其他信息。

我们希望能够显示周年纪念日的员工列表 这个月(入职月份与当前月份相同)并且是 他们工作的第一年、第二年或 5 年的倍数。

我们想像作用域一样使用它,但是由于逻辑复杂,我们正在制作 Class 方法。试图将逻辑分成小块变得混乱。 我确信可以简化代码。

最大的问题是,而不是只获取具有 作为范围的周年纪念日,我得到了所有员工的名单 如果是他们的周年纪念月,则为 nil 或他们的用户信息。

一个例子:

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 >>

达到预期结果并清理此代码的最佳方法是什么?

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
#

自 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
]

这可以通过使用数据库中的日期函数并在那里进行比较,以更简单、更有效的方式完成。

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

此示例是为 Postgres 编写的,您可能需要对其进行调整以适应您的 RDBMS。

使用 SQLITE,where 子句如下所示:

where "strftime('%m',employed_since) = strftime('%m', date('now'))
       AND employed_since < date('now','-1 year','+1 day')
       AND ( (strftime('%Y','now') - strftime('%Y', employed_since)) BETWEEN 1 AND 2
          OR (strftime('%Y','now') - strftime('%Y', employed_since)) % 5 = 0 )" 

这实际上是一个作用域,不需要像我最初想的那样使用 class 方法。