And*_*rew 4 ruby ruby-on-rails
在一个简单的Ruby on Rails应用程序中,我试图计算用户发布的连续天数。因此,举例来说,如果我过去4天中的每一天都发布了邮件,我希望在个人资料中显示“您当前的发布时间是4天,请继续努力!” 或类似的东西。
我应该跟踪其中一个模型中的“条纹”,还是应该在其他位置进行计算?不知道我应该在哪里做,或如何正确做,所以任何建议都会很棒。
很高兴包含您认为有用的任何代码,请告诉我。
我不确定这是否是最好的方法,但是这是在SQL中执行此操作的一种方法。首先,看看下面的查询。
SELECT
series_date,
COUNT(posts.id) AS num_posts_on_date
FROM generate_series(
'2014-12-01'::timestamp,
'2014-12-17'::timestamp,
'1 day'
) AS series_date
LEFT OUTER JOIN posts ON posts.created_at::date = series_date
GROUP BY series_date
ORDER BY series_date DESC;
Run Code Online (Sandbox Code Playgroud)
我们使用generate_series生成的日期范围从2014年12月1日开始,到2014年12月17日结束(今天)。然后我们在posts桌子上做一个左外连接。这样一来,我们每天就可以在该范围内获得一行,而该num_posts_on_date列中该天的帖子数。结果看起来像这样(SQL Fiddle在这里):
series_date | num_posts_on_date
---------------------------------+-------------------
December, 17 2014 00:00:00+0000 | 1
December, 16 2014 00:00:00+0000 | 1
December, 15 2014 00:00:00+0000 | 2
December, 14 2014 00:00:00+0000 | 1
December, 13 2014 00:00:00+0000 | 0
December, 12 2014 00:00:00+0000 | 0
... | ...
December, 01 2014 00:00:00+0000 | 0
Run Code Online (Sandbox Code Playgroud)
现在我们知道从12月14日至17日每天都有帖子,所以如果今天是12月17日,我们知道当前的“连胜”是4天。如本文所述,我们可以做一些更多的SQL以获取最长的连胜记录,但是由于我们只对“当前”连胜记录的长度感兴趣,因此只需要进行很小的更改即可。我们所要做的是改变我们的查询只得到了第一次约会的这num_posts_on_date是0(SQL小提琴):
SELECT series_date
FROM generate_series(
'2014-12-01'::timestamp,
'2014-12-17'::timestamp,
'1 day'
) AS series_date
LEFT OUTER JOIN posts ON posts.created_at::date = series_date
GROUP BY series_date
HAVING COUNT(posts.id) = 0
ORDER BY series_date DESC
LIMIT 1;
Run Code Online (Sandbox Code Playgroud)
结果:
series_date
---------------------------------
December, 13 2014 00:00:00+0000
Run Code Online (Sandbox Code Playgroud)
但是由于我们实际上想要的是自上一天以来没有帖子的天数,因此我们也可以在SQL中执行此操作(SQL Fiddle):
SELECT ('2014-12-17'::date - series_date::date) AS days
FROM generate_series(
'2014-12-01'::timestamp,
'2014-12-17'::timestamp,
'1 day'
) AS series_date
LEFT OUTER JOIN posts ON posts.created_at::date = series_date
GROUP BY series_date
HAVING COUNT(posts.id) = 0
ORDER BY series_date DESC
LIMIT 1;
Run Code Online (Sandbox Code Playgroud)
结果:
days
------
4
Run Code Online (Sandbox Code Playgroud)
你去!
现在,如何将其应用于我们的Rails代码?像这样:
qry = <<-SQL
SELECT (CURRENT_DATE - series_date::date) AS days
FROM generate_series(
( SELECT created_at::date FROM posts
WHERE posts.user_id = :user_id
ORDER BY created_at
ASC LIMIT 1
),
CURRENT_DATE,
'1 day'
) AS series_date
LEFT OUTER JOIN posts ON posts.user_id = :user_id AND
posts.created_at::date = series_date
GROUP BY series_date
HAVING COUNT(posts.id) = 0
ORDER BY series_date DESC
LIMIT 1
SQL
Post.find_by_sql([ qry, { user_id: some_user.id } ]).first.days # => 4
Run Code Online (Sandbox Code Playgroud)
如您所见,我们添加了一个条件来限制user_id的结果,并用一个查询替换了我们的硬编码日期,该查询获取了用户generate_series在该范围的开头的第一条帖子(功能内的子选择)的日期和CURRENT_DATE在范围的末尾。
最后一行有点有趣,因为find_by_sql它将返回一个Post实例数组,因此您必须days在数组中的第一个实例上调用以获取值。或者,您可以执行以下操作:
sql = Post.send(:sanitize_sql, [ qry, { user_id: some_user.id } ])
result_value = Post.connection.select_value(sql)
streak_days = Integer(result_value) rescue nil # => 4
Run Code Online (Sandbox Code Playgroud)
在ActiveRecord中,可以使其变得更干净:
class Post < ActiveRecord::Base
USER_STREAK_DAYS_SQL = <<-SQL
SELECT (CURRENT_DATE - series_date::date) AS days
FROM generate_series(
( SELECT created_at::date FROM posts
WHERE posts.user_id = :user_id
ORDER BY created_at ASC
LIMIT 1
),
CURRENT_DATE,
'1 day'
) AS series_date
LEFT OUTER JOIN posts ON posts.user_id = :user_id AND
posts.created_at::date = series_date
GROUP BY series_date
HAVING COUNT(posts.id) = 0
ORDER BY series_date DESC
LIMIT 1
SQL
def self.user_streak_days(user_id)
sql = sanitize_sql [ USER_STREAK_DAYS_SQL, { user_id: user_id } ]
result_value = connection.select_value(sql)
Integer(result_value) rescue nil
end
end
class User < ActiveRecord::Base
def post_streak_days
Post.user_streak_days(self)
end
end
# And then...
u = User.find(123)
u.post_streak_days # => 4
Run Code Online (Sandbox Code Playgroud)
上面的代码未经测试,因此可能需要花些时间才能使其生效,但我希望至少可以为您指明正确的方向。
| 归档时间: |
|
| 查看次数: |
1064 次 |
| 最近记录: |