克隆 rails 中的关系
Cloning a relation in rails
我在升级到 rails 4.2.1 时遇到弃用错误,Modifying already cached Relation. The cache will be reset. Use a cloned Relation to prevent this warning.
我正在尝试的操作 运行 按月获取登录用户数。
我的测试很简单:
get :page
expect(response).to be_success
控制器动作:
def page
@months = {}
(0..11).each do |month|
@months[month] = User.group(:usergroup).number_first_logged_in(Date.new(Date.today.year,month+1, 1))
end
end
用户模型
class Model < ActiveRecord::Base
...
def number_first_logged_in(month)
where(first_logged_in_at: month.beginning_of_month..month.end_of_month).count
end
end
我意识到我 运行几乎相同的查询 12 次,但参数不同。当用户未分组时,此方法在其他地方使用。我怎样才能 'clone' 弃用警告中建议的关系?
我不想简单地忽略它,因为它在 运行 宁测试时填满了我的屏幕,这不是很有帮助
简答:
Ignore it. The deprecation message has been removed from the master branch of rails. Rails 5 will not be complaining about it.
长答案:
以下 issues/commits 似乎是弃用消息被错误添加并已被删除的印象:
- remove deprecation warning when modifying a Relation with cached arel.
- reset
@arel
when modifying a Relation in place.
- Calling #count on a relation makes it immutable, but ImmutableRelation is never thrown
可能值得在 rails 的主分支上尝试您的代码。
已编辑
首先也是最重要的您的代码有效。它不会在 rails 4.2.1 上引发 ImmutableRelation
或显示弃用消息 运行。
页面操作上的代码不需要克隆关系,因为它会在每个月的步骤中创建一个新关系(使用: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
但是,你有一个问题。此问题与 public rails API 无关,因为它没有可能引发这些错误或弃用的方法。 Rails 团队表示任何 #nodoc
public 方法都是 而不是 public API 的一部分。许多查询方法(如果不是全部的话)都有一个 pair bang 方法(code),它修改关系(而不是 return 克隆)并引发 InmutableRelation
错误(或弃用以前版本的消息)。这些 public 方法是 #nodoc
,不属于 public rails API。
怎么办?不容易:
- 在您的代码中搜索这些 bang 方法,也许是在 AR 上完成的猴子补丁。
- 检查您正在使用的宝石。也许,启动一个代码正常运行的新应用程序,并添加您在目标应用程序中拥有的所有 gem,如果失败,反复试验删除 gem,直到它再次运行。
我指的是 bang 方法,但是修改关系的方法也是如此(这不能由 public API 完成)。你必须寻找猴子补丁或扩展关系。
这一定足以解决问题。
我看了你的评论,我明白了,我认为这些选项:
Rails 数据库独立性通过 arel 实现。而arel没有
直接使用数据库函数(日期字段的月份)的方法。
您可以扩展 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
其中 return 具有此模式的散列:(更多计数 here)
{
[<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,因为它使用函数 date_trunc(...)
,如果您需要与 MySql 一起使用,则需要使用 month(users.created_at)
。使用 MySql 映射时,您需要使用 k[0]
而不是 k[0].month
.
注意 2:group
调用已为其字段分隔参数,因为您希望 returned 散列的键上有两个值。
我看到两个可能的解决方案:
您可以清除查询之间的缓存:
ActiveRecord::Base.connection.query_cache.clear
来自 Clearing active Record Cache
您可以在操作之前使用 .dup
克隆关系,这样您就不会使缓存失效。
在我的例子中,是 squeel gem 产生了弃用警告。一个简单的 monkeypatch 修复了警告。
module Squeel
module Adapters
module ActiveRecord
module RelationExtensions
def execute_grouped_calculation(operation, column_name, distinct)
super
end
end
end
end
end
我不确定它是否会破坏 squeel 行为,但它对我有用。 Rails 4.2.x 与 squeel 的结合似乎是个问题。我还将其推送到 squeel 问题跟踪器 https://github.com/activerecord-hackery/squeel/issues/374。
我在升级到 rails 4.2.1 时遇到弃用错误,Modifying already cached Relation. The cache will be reset. Use a cloned Relation to prevent this warning.
我正在尝试的操作 运行 按月获取登录用户数。
我的测试很简单:
get :page
expect(response).to be_success
控制器动作:
def page
@months = {}
(0..11).each do |month|
@months[month] = User.group(:usergroup).number_first_logged_in(Date.new(Date.today.year,month+1, 1))
end
end
用户模型
class Model < ActiveRecord::Base
...
def number_first_logged_in(month)
where(first_logged_in_at: month.beginning_of_month..month.end_of_month).count
end
end
我意识到我 运行几乎相同的查询 12 次,但参数不同。当用户未分组时,此方法在其他地方使用。我怎样才能 'clone' 弃用警告中建议的关系?
我不想简单地忽略它,因为它在 运行 宁测试时填满了我的屏幕,这不是很有帮助
简答:
Ignore it. The deprecation message has been removed from the master branch of rails. Rails 5 will not be complaining about it.
长答案:
以下 issues/commits 似乎是弃用消息被错误添加并已被删除的印象:
- remove deprecation warning when modifying a Relation with cached arel.
- reset
@arel
when modifying a Relation in place. - Calling #count on a relation makes it immutable, but ImmutableRelation is never thrown
可能值得在 rails 的主分支上尝试您的代码。
已编辑
首先也是最重要的您的代码有效。它不会在 rails 4.2.1 上引发 ImmutableRelation
或显示弃用消息 运行。
页面操作上的代码不需要克隆关系,因为它会在每个月的步骤中创建一个新关系(使用: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
但是,你有一个问题。此问题与 public rails API 无关,因为它没有可能引发这些错误或弃用的方法。 Rails 团队表示任何 #nodoc
public 方法都是 而不是 public API 的一部分。许多查询方法(如果不是全部的话)都有一个 pair bang 方法(code),它修改关系(而不是 return 克隆)并引发 InmutableRelation
错误(或弃用以前版本的消息)。这些 public 方法是 #nodoc
,不属于 public rails API。
怎么办?不容易:
- 在您的代码中搜索这些 bang 方法,也许是在 AR 上完成的猴子补丁。
- 检查您正在使用的宝石。也许,启动一个代码正常运行的新应用程序,并添加您在目标应用程序中拥有的所有 gem,如果失败,反复试验删除 gem,直到它再次运行。
我指的是 bang 方法,但是修改关系的方法也是如此(这不能由 public API 完成)。你必须寻找猴子补丁或扩展关系。
这一定足以解决问题。
我看了你的评论,我明白了,我认为这些选项:
Rails 数据库独立性通过 arel 实现。而arel没有 直接使用数据库函数(日期字段的月份)的方法。 您可以扩展 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
其中 return 具有此模式的散列:(更多计数 here)
{
[<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,因为它使用函数 date_trunc(...)
,如果您需要与 MySql 一起使用,则需要使用 month(users.created_at)
。使用 MySql 映射时,您需要使用 k[0]
而不是 k[0].month
.
注意 2:group
调用已为其字段分隔参数,因为您希望 returned 散列的键上有两个值。
我看到两个可能的解决方案:
您可以清除查询之间的缓存:
ActiveRecord::Base.connection.query_cache.clear
来自 Clearing active Record Cache
您可以在操作之前使用 .dup
克隆关系,这样您就不会使缓存失效。
在我的例子中,是 squeel gem 产生了弃用警告。一个简单的 monkeypatch 修复了警告。
module Squeel
module Adapters
module ActiveRecord
module RelationExtensions
def execute_grouped_calculation(operation, column_name, distinct)
super
end
end
end
end
end
我不确定它是否会破坏 squeel 行为,但它对我有用。 Rails 4.2.x 与 squeel 的结合似乎是个问题。我还将其推送到 squeel 问题跟踪器 https://github.com/activerecord-hackery/squeel/issues/374。