9

我在升级到 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 次,但参数不同。当用户未分组时,此方法在其他地方使用。如何按照弃用警告中的建议“克隆”关系?

我不想简单地忽略这一点,因为它在运行测试时会填满我的屏幕,这不是很有帮助

4

4 回答 4

5

就我而言,是 squeel gem 产生了弃用警告。一个简单的猴子补丁修复了警告。

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中。

于 2015-04-20T19:23:07.030 回答
1

已编辑

首先,您的代码有效。它不会引发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 的一部分。

该怎么办?这是不容易的:

  1. 在您的代码中搜索这些爆炸方法,也许是在 AR 上完成的猴子补丁。
  2. 检查您正在使用的宝石。也许,开始一个新的应用程序,代码工作,并添加你在目标应用程序中拥有的所有 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调用为其字段提供了单独的参数,因为您希望返回哈希的键上有两个值。

于 2015-04-16T22:00:47.253 回答
0

我看到两种可能的解决方案:

您可以清除查询之间的缓存: ActiveRecord::Base.connection.query_cache.clear 清除活动记录缓存

您可以.dup在操作之前使用它来克隆关系,这样您就不会使缓存无效。

于 2015-04-18T00:18:09.900 回答
-2

简短的回答:

忽略它。弃用消息已从 rails 的 master 分支中删除。Rails 5 不会抱怨它。

长答案:

以下问题/提交似乎给人的印象是弃用消息是错误添加的,并且已被删除:

可能值得尝试使用 rails 的 master 分支的代码。

于 2015-03-20T16:39:11.430 回答