【问题标题】:Using calculations in Ruby and Rails在 Ruby 和 Rails 中使用计算
【发布时间】:2012-10-25 05:36:49
【问题描述】:

首先让我为一个看似简单的问题道歉,但是对于 Rails、Ruby 和编程的新手,我觉得我已经用尽了“Rails 新手”教程。

这就是我所反对的。

我有一个用户模型和机构模型,它们具有“has_many :through => :company_reps”关系。

用户具有基本字段(姓名、电子邮件、密码)(我使用的是devise

机构有许多字段,但相关的字段是(客户 = 布尔值,领导 = 布尔值,演示日期 = 日期/时间) 更复杂的是,每个机构都可以有一个或两个用户,但大多数只有一个。

我们正在为用户举办比赛,我需要根据 demo_date 字段和 client 字段为每个用户奖励积分。

所以首先我需要给每个用户 10 分,这与作为客户的机构相关,除非该机构有 2 个用户,在这种情况下我需要给这两个用户每个 5 分。

其次,我需要给与 2012 年 2 月之后有演示日期的机构相关的所有用户 1 分。

我正在使用 Ruby 1.9.2、Rails 3.2.8 和 MySQL

  • 那么,我该如何实现呢?
  • 是否应该创建一个新表和模型来存储点,如果是,我该如何保存计算?
  • 我应该将所有计算放入用户模型还是机构模型中?

一如既往地感谢您的帮助。

MySQL 机构信息

CREATE TABLE `institutions` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `state_id` int(11) DEFAULT NULL,
  `company` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `clientdate` datetime DEFAULT NULL,
  `street` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `city` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `zip` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `source` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `source2` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `demodate1` datetime DEFAULT NULL,
  `demodate2` datetime DEFAULT NULL,
  `demodate3` datetime DEFAULT NULL,
  `client` tinyint(1) DEFAULT NULL,
  `prospect` tinyint(1) DEFAULT NULL,
  `alead` tinyint(1) DEFAULT NULL,
  `notcontacted` tinyint(1) DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7805 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 

机构模式

class Institution < ActiveRecord::Base
  attr_accessible :company, :phone, :assets, :clientdate, :street, :city, :state_id, :zip, :source, :source2, :demodate1, :demodate2, :demodate3, :client, :prospect, :alead, :notcontacted
  belongs_to :state
  has_many :users, :through => :company_reps
  has_many :company_reps

end

用户模型

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :token_authenticatable, :confirmable,
  # :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  # Setup accessible (or protected) attributes for your model
  attr_accessible :email, :password, :password_confirmation, :remember_me, :first_name, :last_name
  # attr_accessible :title, :body

  has_many :states, :through => :rep_areas
  has_many :institutions, :through => :company_reps
  has_many :rep_areas
  has_many :company_reps

  def name 
    first_name + " " + last_name
  end


end

公司代表模式

class CompanyRep < ActiveRecord::Base
  belongs_to :user
  belongs_to :institution
end

【问题讨论】:

    标签: mysql ruby-on-rails ruby


    【解决方案1】:

    更新(因为我第一次尝试错误地假设User has_one :institution

    最简单的选择是在Institution 模型上进行基本计算,以确定机构“值”多少分,然后将该值相加以计算用户的分数。

    # Institution
    def points
      points_for_client + points_for_demo_date
    end
    
    private
    
    def points_for_client
      if client?
        10 / users.count
      else
        0
      end
    end
    
    def points_for_demo_date
      if demo_date.present? && demo_date >= Date.new(2012, 3, 1)
        1
      else
        0
      end
    end
    

    请注意,如果您愿意,可以使用三元运算符 ? : 将这些 if 语句压缩为单行语句。另请注意,我假设“2 月之后”是指“从 3 月 1 日起”。

    检查为零demo_date 也是一个品味问题。任君挑选

    # Verbose, but IMO intention-revealing
    demo_date.present? && demo_date >= Date.new(...)
    
    # Perhaps more idiomatic, since nil is falsy
    demo_date && demo_date >= Date.new(...)
    
    # Take advantage of the fact that >= is just another method
    # Concise, but I think it's a bit yuk!
    demo_date.try :>=, Date.new(...)
    

    现在每个机构都值得一定数量的积分,总结起来相当简单:

    # User
    def points
      institutions.inject(0) {|sum, institution| sum + institution.points }
    end
    

    如果您不熟悉它,请查看the docs for inject,这是一个非常棒的小方法。

    就性能而言,这是次优的。一个基本的改进是记忆结果:

    # Institution
    def points
      @points ||= points_for_client + points_for_demo_date
    end
    
    # User
    def points
      @points ||= institutions.inject ...
    end
    

    以便在同一请求中进一步调用points 不会重新计算该值。只要clientdemo_dateUser 对象还活着的时候不要改变就可以了:

    some_user.points   #=> 0
    some_user.institution.client = true
    some_user.points   #=> 0 ... oops
    

    User 对象将在下一个请求中重新创建,因此这可能不是问题(这取决于这些字段如何变化)。

    您还可以将points 字段添加到User,从而将值保存在数据库中,而是使用原始版本作为update_points 方法

    def update_points
      self.points = institutions.inject ...
    end
    

    但是,确定何时重新计算该值将是一个问题。

    我的建议是使其尽可能简单并避免过早优化。这是一个相对简单的计算,所以它不会是一个大的性能问题,只要你没有大量的用户和机构或大量的请求。

    【讨论】:

    • 哇...完美。非常感谢。
    • 另一个问题。当我做一个 Rails 控制台来尝试代码时。我会做类似 u = User.find(1) 的事情。然后 u.points 我得到未定义的方法“客户端?” .这是因为用户拥有多个机构吗?
    • 啊,对不起,我误读了这个问题,我的假设是User has_one :institution。我会更新我的答案。
    • 感谢您的更新。我现在已经半工作了。我说半因为如果我删除 point_for_demo_date 方法我可以做 User.find(1).points 并返回总和。但是,如果我将 point_for_demo_date 方法留在其中,则会收到以下错误:NoMethodError: undefined method `>=' for nil:NilClass.
    • 啊,如果demo_date 可以为零,那么您需要检查一下。更新:)
    【解决方案2】:

    积分累积到Users,因此在User 类上添加一个方法调用来返回他们累积的积分数似乎是有意义的。

    我首先编写一个方法来计算每次调用时的总分,并进行一些单元测试以确保计算正确。我一开始不会保存结果 - 取决于你有多少对象,你需要多久计算一次点等,你可能根本不需要保存它。

    【讨论】:

      猜你喜欢
      • 2011-12-05
      • 1970-01-01
      • 2011-02-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多