【问题标题】:How to list the ages of users from their DOB如何从 DOB 中列出用户的年龄
【发布时间】:2014-10-27 23:12:25
【问题描述】:

我想创建统计信息来显示我的注册用户的年龄组。

例子:

18-24 - 150 registered users
25-34 - 3948 registered users
35-44 - 10028 registered users
45+ - 538 registered users

我在用户表中有一个生日 (dob) 属性。我根据用户模型计算他们的年龄:

def age
  now = Time.now.utc.to_date
  now.year - birthday.year - ((now.month > birthday.month || (now.month == birthday.month && now.day >= birthday.day)) ? 0 : 1)
end

我自己能做的最好的事情是:

User.group(:birthday).count

输出每个出生日期的用户总数(不是年龄)

我想在视图中输出年龄组。有人可以帮助我吗?或者,可以列出每个年龄的用户总数。哪个是最好的解决方案。

【问题讨论】:

  • 年龄组是固定的还是您需要某种方式才能在应用程序中更改它们?
  • 现在我只有使用birthday 属性的出生日期,并且我在视图中输出他们的年龄。年龄组应该是固定的。
  • birthday 字段是必需的吗?
  • birthday 字段是必需的,因为那是出生日期。拥有 DOB 比将年龄存储在数据库中要好。

标签: ruby-on-rails


【解决方案1】:
  1. 创建实例方法来计算用户的年龄app/models/user.rb

    def age
      now = Time.now.utc.to_date
      now.year - birthdate.year - ((now.month > birthdate.month || (now.month == birthdate.month && now.day >= birthdate.day)) ? 0 : 1)
    end
    
  2. 创建代表年龄组的对象(我将使用无表模型,但它可以是纯 ruby​​ 对象甚至结构)app/models/age_group.rb:

    class AgeGroup
      include ActiveModel::Model # not really necessary, but will add some AM functionality which could be nice later
      attr_accessor :from, :to, :count
    end
    
  3. 为计算年龄组创建服务对象(选择服务对象只是我个人的喜好。你可以创建一个助手或任何你认为最适合你需要的东西)。 app/services/age_groups_service.rb:

    class AgeGroupService
      # @params 
      #   ranges - an array of age group ranges. E.g.: [[0, 18], [19, 24], [25, 34], ...]
      #   users - an array of users from which the age groups will be computed. Defaults to all users  
      def initialize(ranges = [], users = User.all.to_a)
        @ranges = ranges
        @users = users
        @age_groups = []
      end
    
      # Count users in each age group range
      # @return
      #   an array of age groups. E.g.: [{ from: 0, to: 18, count: 12 }, ...]
      def call
        @ranges.each do |range|      
          users = @users.select { |user| user.age >= range[0] && user.age <= range[1] }      
          @age_groups << AgeGroup.new(from: range[0], to: range[1], count: users.length)                
       end
       @age_groups
      end
    end
    
  4. 将调用AgeGroupsService返回的值分配给控制器中的实例变量:

    age_group_service = AgeGroupsService.new([[18, 24], [25, 34], [35, 44], [45, 100]])
    @age_groups = age_group_service.call
    
  5. 在视图中打印结果:

    <ul>
      <% @age_groups.each do |age_group| %>
        <li><%= age_group.from %> - <%= age_group.to %>: <b><%= age_group.count %></b></li>
      <% end %>
    </ul>
    

一些进一步的想法/注释:

  • AgeGroup 模型和 AgeGroupsService 不是必需的。您可以使用 PORO 和 helpers 代替,或者您甚至可以将所有代码放入 User 模型和相关控制器中。无论如何,我强烈建议不要这样做,因为它会变得混乱并且感觉不对。年龄组似乎值得拥有自己的位置。
  • 这是一个非常简单的解决方案。如果您没有很多用户,它将正常工作。在拥有数千名用户的生产应用程序中,这种实现会出现问题。这里的问题是将用户分配到年龄组的算法的计算复杂性。该算法遍历每个年龄组范围的每个用户。你可以用不同的方式解决这个问题:1。通过降低算法的复杂性或必须迭代的用户数量来改进算法。我的第一个尝试是确保一旦确定当前范围内没有更多用户并且对所有后续范围的迭代不会迭代已经分配给某个范围的用户,就会立即停止对范围的迭代。 2。如果您不害怕使用原始 SQL 并且不介意失去数据库不可知性,请使用 SQL 进行计算。您可以创建一个组合查询,甚至是一个数据库视图,它将返回两个日期之间生日的所有用户的计数。 3。让它异步工作。将计算移到后台(这样它就不会阻塞控制器),然后使用 Javascript 中的异步调用加载它。

【讨论】:

  • 谢谢。这按预期工作!将进行调整,因为我确实计划拥有数千名注册会员。感谢您让我开始!
  • 哦,我差点忘了!您可以使用缓存来提高性能,尽管它可能有点:您可以缓存年龄组并仅在它们发生变化时重新计算(新用户、用户离开、用户变老等......)
【解决方案2】:

你可以在 MySQL 中做这样的事情来得到所有 8-10 岁的人的总和:

SELECT COUNT(*) as FROM users WHERE Year(dob) BETWEEN 2004 AND 2006;

或在 Rails 中

User.where('Year(dob) IN (?)',(2004..2006)).count

使用此代码,您可以执行以下操作来获取某个年龄段的人数:

groups = [[18,24],[25,35],[34,44],[45,99]]
current_year = DateTime.now.year
groups.each do |age_group|
  range = Range.new(*age_group.map{|y| current_year - y}.reverse)
  print age_group.join(" - ")+": "
  puts User.where('Year(dob) IN (?)',range).count
end

【讨论】:

  • 谢谢。我接受了 Marek 的回答,但我会评估你的代码,看看哪个表现更好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-01
  • 1970-01-01
  • 2023-02-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多