如何从DOB中列出用户的年龄

时间:2014-10-22 14:17:48

标签: ruby-on-rails

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

示例:

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

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

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

我希望在视图中输出多年的组。有人可以帮我吗?或者替代方案可以列出每个年龄段的总用户数。无论哪个是最佳解决方案。

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:2)

  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对象甚至是struct)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>
    
  6. 一些进一步的想法/说明:

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