9

Rails 4.2.1 へのアップグレードで deprecation エラーが発生します。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

それがきしむ動作を壊すかどうかはわかりませんが、私にとってはうまくいきます。Rails 4.2.x と squeel の組み合わせで問題が発生するようです。また、これを squeel イシュー トラッカーhttps://github.com/activerecord-hackery/squeel/issues/374にプッシュしました。

于 2015-04-20T19:23:07.030 に答える
1

編集済み

何よりもまず、コードが機能します。ImmutableRelationRails 4.2.1 で実行されている非推奨メッセージが発生したり、表示されたりすることはありません。

ページ アクションのコードでは、関係を複製する必要はありません。これは、各月のステップで新しい関係を作成するためです ( with: User...)。はい、12 のクエリを実行しますが、それは問題ではありません。

あまり重要ではありませんが、質問に 2 つのタイプミスがあります。モデルは で変更def number_first_logged_in(month)する必要がdef self.number_first_logged_in(month)あり、モデル名は である必要がありUser、 ではない必要がありModelます。

Railsコンソールでテストしました(Productsの代わりに、フィールドの代わりにUser使用)が、同じで正常に動作します。また、新しい Rails 4.2.1 アプリを起動し、質問のコードを (タイプミスを修正して) 使用すると、動作することを確信しています。:created_at:first_logged_in_at

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パブリック メソッドはパブリック API の一部ではないと述べています。ほとんどのクエリ メソッド (すべてではないにしても) には、ペア bang メソッド ( code ) があり、(クローンを返す代わりに) リレーションを変更し、InmutableRelationエラー (または以前のバージョンでは非推奨メッセージ) を発生させます。これらのパブリックメソッドは#nodoc、パブリック Rails API の一部ではありません。

何をすべきか?簡単ではない:

  1. コードでこれらの bang メソッドを検索してください。おそらく、AR で行われたモンキー パッチです。
  2. 使用している宝石を確認してください。おそらく、コードが機能する新しいアプリを開始し、ターゲットアプリにあるすべての宝石を追加し、失敗した場合は、再び機能するまで試行錯誤して宝石を削除します.

bang メソッドについて言及しましたが、リレーションを変更するメソッドにも当てはまります (これはパブリック API では実行できません)。モンキー パッチを探すか、リレーションを拡張する必要があります。

問題を解決するにはそれで十分なはずです。

私はあなたのコメントを読みました、そして私は要点を得ました、私はこれらのオプションだと思います:

  • Rails データベースの独立性は、are を通じて機能します。また、arel には、db 関数 (日付フィールドの月) を直接操作するメソッドがありません。arel を拡張して書くこともできますが、PostgreSql 用に 1 つ、MySql 用に 1 つ、Sqlite 用に 1 つ作成する必要があります。(この時点で高すぎる)

  • dev/test/prod で同じ db マネージャーを使用する場合は、私が提案したように部分的なテキスト クエリを使用できます。(これはあなたが好きではありません)

  • 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 で機能します。関数を使用するためdate_trunc(...)です。MySql で使用する必要がある場合は、month(users.created_at)代わりに使用します。k[0]MySql でマッピングする場合は、の代わりに使用する必要がありますk[0].month

注 2:group返されたハッシュのキーに 2 つの値が必要なため、呼び出しではフィールドのパラメーターが分離されています。

于 2015-04-16T22:00:47.253 に答える