已编辑
首先,您的代码有效。它不会引发ImmutableRelation
或显示在 rails 4.2.1 上运行的弃用消息。
您的页面操作上的代码不需要克隆关系,因为它会在每个月的步骤中创建一个新关系(使用:)User...
。是的,它会进行 12 次查询,但这对您来说不是问题。
这不是太重要,但是您在问题上有两个拼写错误。您的模型必须更改def number_first_logged_in(month)
为def self.number_first_logged_in(month)
并且模型名称必须是User
和不是Model
。
我在 rails 控制台测试它(Products
而不是User
,并使用:created_at
而不是:first_logged_in_at
字段),但它是相同的并且工作正常。而且我很确定,如果您启动一个新的 rails 4.2.1 应用程序并使用问题中的代码(已修复错别字),它将起作用。
alejandro@work-one [ruby-2.1.1@rails42]: ~/rails/r42example
[09:14:04] $ rails c
Loading development environment (Rails 4.2.1)
~/rails/r42example (development) > @m = {};(0..11).each {|m| @m[m] = Product.group(:name).number_first_logged_in(Date.new(Date.today.year,m+1, 1)) }
(0.1ms) SELECT COUNT(*) AS count_all, name AS name FROM "products" WHERE ("products"."created_at" BETWEEN '2015-01-01' AND '2015-01-31') GROUP BY "products"."name"
(0.1ms) SELECT COUNT(*) AS count_all, name AS name FROM "products" WHERE ("products"."created_at" BETWEEN '2015-02-01' AND '2015-02-28') GROUP BY "products"."name"
(0.1ms) SELECT COUNT(*) AS count_all, name AS name FROM "products" WHERE ("products"."created_at" BETWEEN '2015-03-01' AND '2015-03-31') GROUP BY "products"."name"
(0.1ms) SELECT COUNT(*) AS count_all, name AS name FROM "products" WHERE ("products"."created_at" BETWEEN '2015-04-01' AND '2015-04-30') GROUP BY "products"."name"
(0.1ms) SELECT COUNT(*) AS count_all, name AS name FROM "products" WHERE ("products"."created_at" BETWEEN '2015-05-01' AND '2015-05-31') GROUP BY "products"."name"
(0.1ms) SELECT COUNT(*) AS count_all, name AS name FROM "products" WHERE ("products"."created_at" BETWEEN '2015-06-01' AND '2015-06-30') GROUP BY "products"."name"
(0.1ms) SELECT COUNT(*) AS count_all, name AS name FROM "products" WHERE ("products"."created_at" BETWEEN '2015-07-01' AND '2015-07-31') GROUP BY "products"."name"
(0.1ms) SELECT COUNT(*) AS count_all, name AS name FROM "products" WHERE ("products"."created_at" BETWEEN '2015-08-01' AND '2015-08-31') GROUP BY "products"."name"
(0.1ms) SELECT COUNT(*) AS count_all, name AS name FROM "products" WHERE ("products"."created_at" BETWEEN '2015-09-01' AND '2015-09-30') GROUP BY "products"."name"
(0.1ms) SELECT COUNT(*) AS count_all, name AS name FROM "products" WHERE ("products"."created_at" BETWEEN '2015-10-01' AND '2015-10-31') GROUP BY "products"."name"
(0.1ms) SELECT COUNT(*) AS count_all, name AS name FROM "products" WHERE ("products"."created_at" BETWEEN '2015-11-01' AND '2015-11-30') GROUP BY "products"."name"
(0.1ms) SELECT COUNT(*) AS count_all, name AS name FROM "products" WHERE ("products"."created_at" BETWEEN '2015-12-01' AND '2015-12-31') GROUP BY "products"."name"
=> 0..11
但是,你有一个问题。此问题与公共 Rails API 无关,因为它没有可能引发这些错误或弃用的方法。Rails 团队表示,任何#nodoc
公共方法都不属于公共 API。许多查询方法(如果不是全部)都有一个 pair bang 方法(code),它修改关系(而不是返回一个克隆)并引发InmutableRelation
错误(或以前版本的弃用消息)。这些公共方法是#nodoc
并且不是公共 Rails API 的一部分。
该怎么办?这是不容易的:
- 在您的代码中搜索这些爆炸方法,也许是在 AR 上完成的猴子补丁。
- 检查您正在使用的宝石。也许,开始一个新的应用程序,代码工作,并添加你在目标应用程序中拥有的所有 gem,如果失败,尝试和错误删除 gem,直到它再次工作。
我指的是 bang 方法,但修改关系的方法也是如此(这不能由公共 API 完成)。您必须寻找猴子补丁或扩展关系。
这必须足以解决问题。
我读了你的评论,我明白了,我认为这些选项:
Rails 数据库独立性通过 arel 工作。并且 arel 没有直接使用 db 函数(日期字段的月份)的方法。你可以扩展 arel 并编写它,但是你需要为 PostgreSql 编写一个为 MySql 另一个为 Sqlite 编写。(太贵了,在这一点上)
如果您在 dev/test/prod 上使用相同的数据库管理器,则可以按照我的建议使用部分文本查询。(这不喜欢你)
保留 12 个查询(我认为您可以接受这个)
为 group_by 添加一个专用字段(year_month 可以)。(非常严格,很难改变)
我保留旧答案,因为:如果我是你,我会这样做:
class User
scope :for_current_year, -> { where(created_at: Date.today.beginning_of_year..Date.today.end_of_year }
end
在控制器页面操作上,您可以使用:(我建议使用它)
User.for_current_year
.group("date_trunc('month', users.created_at)", "usergroup").count
它返回具有这种模式的哈希:(更多计数在这里)
{
[<first date of the month of created_at>, <usergroup>] => count,
...
}
但是如果你想得到和@months
以前一样的结果,你必须用 ruby 映射结果。
def page
@months = User.for_current_year
.group("date_trunc('month', users.created_at)", "usergroup").count
.map { |k,v| {k[0].month => {k[1] => v}} }
end
注意 1:此代码适用于 PostgreSQL,因为它使用 function date_trunc(...)
,如果您需要与您想使用的 MySql 一起使用month(users.created_at)
。使用 MySql 进行映射时,您需要使用k[0]
而不是k[0].month
.
注意 2:该group
调用为其字段提供了单独的参数,因为您希望返回哈希的键上有两个值。