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

时间:2018-11-25 01:12:39

标签: ruby-on-rails ruby-on-rails-4 scope class-method named-scope

在系统中,有在User模型中具有登录信息的员工, 个人档案模型中有关它们的其他信息。

我们希望能够显示有周年纪念的员工列表 这个月(聘用月份与当前月份相同) 他们在工作的第一,第二或五年的倍数。

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

最大的问题是,与其仅获得具有 范围内的周年纪念日,我得到了所有员工的名单 为零或用户信息(如果是周年纪念日)。

一个例子:

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
#

自“个人资料”中的数据开始雇用

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

2 个答案:

答案 0 :(得分:2)

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

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。

答案 1 :(得分:0)

使用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 )" 

这实际上是一个作用域,不需要我最初认为的类方法。