Rails 使用 fetch 进行缓存

Posted

技术标签:

【中文标题】Rails 使用 fetch 进行缓存【英文标题】:Rails caching using fetch 【发布时间】:2021-11-27 06:20:43 【问题描述】:
Rails.cache.fetch(plan_list_cache_key(project), expires_in: EXPIRES_TIME) do
      plans = Plan.all.where(is_active: true)
      project_plan = project.project_plan
      hash_array = []
      plans.each do |plan|
        plan = plan.attributes
        expired = if (plan["id"] == project.plan.id)
                    (project_plan.end_date.to_date - Date.current).to_i.positive? ? false : true
                  else
                    false
                  end
        subscribed = plan["id"] == project.plan.id
        hash_array << plan.merge!(is_expired: expired , subscribed: subscribed)
      end
      hash_array
    end

型号:

class Plan < mysqlBase
  has_many :project_plans
end

class ProjectPlan < MysqlBase
  belongs_to :plan
  belongs_to :project
end

class Project < MysqlBase
  has_one :project_plan
  has_one :plan, :through => :project_plan
end

架构:

create_table "plans", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
    t.string "name"
    t.text "description"
    t.integer "email_limit"
    t.integer "validity"
    t.float "unit_price", limit: 24
    t.string "currency"
    t.integer "base_user_count"
    t.boolean "is_renewable"
    t.boolean "is_active"
    t.boolean "is_free"
    t.string "terms"
    t.integer "trial_days"
    t.boolean "default"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

create_table "project_plans", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
    t.integer "plan_id"
    t.integer "project_id"
    t.datetime "start_date"
    t.datetime "end_date"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

回应


    "status": "OK",
    "status_code": 200,
    "message": "ok",
    "data": 
        "plans": [
            
                "id": 1,
                "name": "Free",
                "description": "30 days free trial",
                "email_limit": 10000,
                "validity": 30,
                "unit_price": 0.0,
                "currency": "USD",
                "base_user_count": 0,
                "trial_days": 30,
                "is_free": true,
                "subscribed": false,
                "is_expired": false
            ,
            
                "id": 2,
                "name": "Standard",
                "description": "$3 for 100 users",
                "email_limit": 0,
                "validity": 30,
                "unit_price": 0.03,
                "currency": "USD",
                "base_user_count": 100,
                "trial_days": 0,
                "is_free": false,
                "subscribed": true,
                "is_expired": false
            
        ]
    

每个项目都有一个单独的 project_plan。所有用户的计划都是相同的。但我要做的是在 API 响应中添加两个附加字段 is_expiredsubscribed

我在这里使用 fetch 缓存我的响应。但是由于有一些数据库和逻辑操作,这是进行缓存的正确方法吗

如果这不是正确的方法,这里会发生什么类型的错误?

进行这种缓存的最佳方法是什么?

【问题讨论】:

这里最好的方法是使用关联和预先加载。你在 Ruby 中做了一堆效率极低的工作,这些工作应该在数据库中处理。我怀疑你真的需要缓存。 guides.rubyonrails.org/…guides.rubyonrails.org/… 为了给你一个真正解决问题的答案,我们需要一个模型、模式、数据和预期输出的例子。 我已经用架构和模型更新了描述。请检查@max 谢谢,但您仍然缺少预期结果的示例。虽然我们可以从您的代码中对其进行逆向工程,但这只会导致误解。 抱歉,已添加回复 【参考方案1】:

这里的一个解决方案是加入项目计划表并选择聚合:

SELECT 
  plans.*,
  -- you will get integers since MySQL is a peasant database and does not have real boolean types
  COUNT(pp.id) > 0 AS subscribed,
  COALESCE(MAX(pp.ends_at) < NOW(), FALSE) AS is_expired
FROM plans
LEFT OUTER JOIN project_plans pp 
  ON pp.plan_id = plans.id
  AND pp.project_id = ?
GROUP BY plans.id
class Plan
  def with_statuses_for_project(project)
    pp = ProjectPlan.arel_table
    j = pp.join(arel_table).on(
      pp[:plan_id].eq(arel_table[:id])
                  .and(
                    pp[:project_id].eq(project.id)
                  )
    )
    .select(
      arel_table[Arel.star],
      "COUNT(pp.id) > 0 AS subscribed",
      "COALESCE(MAX(pp.ends_at) < NOW(), FALSE) AS is_expired"
    )
    .joins(j.join_sources)
    .group(:id)
  end
end

【讨论】:

以上是关于Rails 使用 fetch 进行缓存的主要内容,如果未能解决你的问题,请参考以下文章

找到在 Rails 上使用 redis 缓存的最佳方法

由于缓存目录将所有权更改为 root,Rails 6 应用程序失败

Rails:ENV.fetch() 和 ENV[] 之间的区别

Rails 5可操作的未定义方法'fetch'用于nil:NilClass

JavaScript 的 Fetch 可以用来更新 Rails 控制器中的参数值吗?

Rails 低级缓存不适用于活动记录