Rails 5 API - 控制器更新操作有时不反映数据库更改(缓存问题)?

时间:2018-02-20 20:32:10

标签: ruby-on-rails ruby caching controller

我有一个has_many关系的2个简单模型。一个模板has_many TemplateItems。模板有一个template_type,可以是两个值中的一个('模板'或'核对清单')。

为简洁起见,我删除了不相关的代码。

template.rb

class Template < ApplicationRecord

  # Relationships
  belongs_to :account
  has_many :template_items, -> { order('sort ASC') }, dependent: :destroy
  accepts_nested_attributes_for :template_items, allow_destroy: true

  # Enums
  enum template_type: {template: 0, checklist: 1}
  enum status: {not_started: 0, started: 1, completed: 2}

  # Callbacks
  before_save :set_status, unless: :is_template? # only care about status for checklists

  def is_template?
    return self.template_type == 'template'
  end

  def set_status
    completed = 0
    self.template_items.each do |item|
      completed += 1 if item.is_completed
    end
    case completed
      when 0
        self.status = Template.statuses[:not_started]
      when 1..(self.template_items.length - 1)
        self.status = Template.statuses[:started]
      when self.template_items.length
        self.status = Template.statuses[:completed]
    end
  end
end

template_item.rb

class TemplateItem < ApplicationRecord  

  # Relationships
  belongs_to :template

  # Validations
  validates_presence_of :template
end

当客户端向Template Controller发送更新时,它包含嵌套的template_items:

templates_controller.rb

def template_params 
  params.require(:template).
    permit(:id, :account_id, :list_type, :name, :title, :info, :status, 
      template_items_attributes: 
      [:id, :template_id, :is_completed, :content, :item_type, :sort, :_destroy])
end

请注意,项目的某个属性称为sort。另请注意,排序顺序在Template模型中用于对template_items进行排序(请参阅has_many行)。

如果客户端使用template_items,则会调用以下更新操作:

templates_controller.rb

  def update
    if @template.update(template_params)
      render json: @template, serializer: TemplateSerializer, status: :ok 
    else
      render json: ErrorSerializer.serialize(@template.errors), status: :unprocessable_entity
    end
  end

奇怪的行为是数据库始终更新(在日志和数据库中验证),但有时渲染不会呈现新的排序顺序,而是呈现先前的排序顺序。 / p>

以下是操作错误地返回先前数据时的日志:

I, [2018-02-20T20:22:55.997835 #1852]  INFO -- : Processing by Api::TemplatesController#update as JSON
...parameters here...
D, [2018-02-20T20:22:56.002965 #1852] DEBUG -- :   User Load (1.7ms)  SELECT  "users".* FROM "users" WHERE "users"."uid" = $1 LIMIT $2  [["uid", "rmcsharry+owner@gmail.com"], ["LIMIT", 1]]
D, [2018-02-20T20:22:56.115190 #1852] DEBUG -- :   Template Load (2.6ms)  SELECT  "templates".* FROM "templates" WHERE "templates"."id" = $1 ORDER BY LOWER(templates.name) ASC LIMIT $2  [["id", "f9f6bca2-cb84-4349-8546-ca38026db407"], ["LIMIT", 1]]
D, [2018-02-20T20:22:56.121995 #1852] DEBUG -- :    (0.4ms)  BEGIN
D, [2018-02-20T20:22:56.129177 #1852] DEBUG -- :   TemplateItem Load (2.5ms)  SELECT "template_items".* FROM "template_items" WHERE "template_items"."template_id" = $1 AND "template_items"."id" IN ('419cb7ec-ca3f-4911-8a00-bec20f5ca89c', 'a7ac1687-8cb5-4199-a03b-d7cc975a0387', 'd7d885b6-2a75-487a-918c-6f3abaae7df1', 'b1b0277c-632f-4fe1-82e5-d020ee313d5b') ORDER BY sort ASC  [["template_id", "f9f6bca2-cb84-4349-8546-ca38026db407"]]
D, [2018-02-20T20:22:56.137975 #1852] DEBUG -- :   Account Load (1.4ms)  SELECT  "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT $2  [["id", "c379e356-4cce-4de2-b1b4-984b773dd43e"], ["LIMIT", 1]]
D, [2018-02-20T20:22:56.144421 #1852] DEBUG -- :   CACHE Template Load (0.0ms)  SELECT  "templates".* FROM "templates" WHERE "templates"."id" = $1 ORDER BY LOWER(templates.name) ASC LIMIT $2  [["id", "f9f6bca2-cb84-4349-8546-ca38026db407"], ["LIMIT", 1]]
D, [2018-02-20T20:22:56.148992 #1852] DEBUG -- :   CACHE Template Load (0.0ms)  SELECT  "templates".* FROM "templates" WHERE "templates"."id" = $1 ORDER BY LOWER(templates.name) ASC LIMIT $2  [["id", "f9f6bca2-cb84-4349-8546-ca38026db407"], ["LIMIT", 1]]
D, [2018-02-20T20:22:56.156300 #1852] DEBUG -- :   TemplateItem Load (2.4ms)  SELECT "template_items".* FROM "template_items" WHERE "template_items"."template_id" = $1 ORDER BY sort ASC  [["template_id", "f9f6bca2-cb84-4349-8546-ca38026db407"]]
D, [2018-02-20T20:22:56.171567 #1852] DEBUG -- :   SQL (1.9ms)  UPDATE "template_items" SET "sort" = $1, "updated_at" = $2 WHERE "template_items"."id" = $3  [["sort", 2], ["updated_at", "2018-02-20 19:22:56.167142"], ["id", "d7d885b6-2a75-487a-918c-6f3abaae7df1"]]
D, [2018-02-20T20:22:56.175072 #1852] DEBUG -- :   SQL (0.7ms)  UPDATE "template_items" SET "sort" = $1, "updated_at" = $2 WHERE "template_items"."id" = $3  [["sort", 1], ["updated_at", "2018-02-20 19:22:56.172797"], ["id", "a7ac1687-8cb5-4199-a03b-d7cc975a0387"]]
D, [2018-02-20T20:22:56.176305 #1852] DEBUG -- :    (0.6ms)  COMMIT
I, [2018-02-20T20:22:56.183481 #1852]  INFO -- : Rendered TemplateSerializer with ActiveModelSerializers::Adapter::Attributes (2.97ms)

当动作正确返回新数据时,这是日志 - 我已经标记了差异(1)和(2):

I, [2018-02-20T20:52:47.490513 #3087]  INFO -- : Processing by Api::TemplatesController#update as JSON
 ...parameters...
D, [2018-02-20T20:52:47.499201 #3087] DEBUG -- :   User Load (2.0ms)  SELECT  "users".* FROM "users" WHERE "users"."uid" = $1 LIMIT $2  [["uid", "rmcsharry+owner@gmail.com"], ["LIMIT", 1]]
D, [2018-02-20T20:52:47.706520 #3087] DEBUG -- :   Template Load (2.3ms)  SELECT  "templates".* FROM "templates" WHERE "templates"."id" = $1 ORDER BY LOWER(templates.name) ASC LIMIT $2  [["id", "c965c3ed-ace2-43af-9abd-f85392bdb948"], ["LIMIT", 1]]
D, [2018-02-20T20:52:47.727668 #3087] DEBUG -- :    (0.3ms)  BEGIN
D, [2018-02-20T20:52:47.777126 #3087] DEBUG -- :   TemplateItem Load (2.2ms)  SELECT "template_items".* FROM "template_items" WHERE "template_items"."template_id" = $1 AND "template_items"."id" IN ('ff034c14-252f-4366-9b31-526b5211e92b', '4e6ec7ef-ba53-4ec2-ab2e-97dd3b2c41bc', '3628b6ca-cddb-4d65-a6c3-86dfdcaa92f4', '35e61d68-143c-4bac-ab15-fbbb2b3f13d1') ORDER BY sort ASC  [["template_id", "c965c3ed-ace2-43af-9abd-f85392bdb948"]]
D, [2018-02-20T20:52:47.820226 #3087] DEBUG -- :   Account Load (1.4ms)  SELECT  "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT $2  [["id", "c379e356-4cce-4de2-b1b4-984b773dd43e"], ["LIMIT", 1]]
D, [2018-02-20T20:52:47.847928 #3087] DEBUG -- :   CACHE Template Load (0.0ms)  SELECT  "templates".* FROM "templates" WHERE "templates"."id" = $1 ORDER BY LOWER(templates.name) ASC LIMIT $2  [["id", "c965c3ed-ace2-43af-9abd-f85392bdb948"], ["LIMIT", 1]]
D, [2018-02-20T20:52:47.850995 #3087] DEBUG -- :   CACHE Template Load (0.0ms)  SELECT  "templates".* FROM "templates" WHERE "templates"."id" = $1 ORDER BY LOWER(templates.name) ASC LIMIT $2  [["id", "c965c3ed-ace2-43af-9abd-f85392bdb948"], ["LIMIT", 1]]
(1) D, [2018-02-20T20:52:47.856858 #3087] DEBUG -- :   Template Exists (0.9ms)  SELECT  1 AS one FROM "templates" WHERE "templates"."name" = $1 AND ("templates"."id" != $2) AND "templates"."account_id" = 'c379e356-4cce-4de2-b1b4-984b773dd43e' AND "templates"."template_type" = $3 LIMIT $4  [["name", "Daffy"], ["id", "c965c3ed-ace2-43af-9abd-f85392bdb948"], ["template_type", 0], ["LIMIT", 1]]
D, [2018-02-20T20:52:47.863415 #3087] DEBUG -- :   SQL (1.1ms)  UPDATE "template_items" SET "sort" = $1, "updated_at" = $2 WHERE "template_items"."id" = $3  [["sort", 2], ["updated_at", "2018-02-20 19:52:47.859495"], ["id", "3628b6ca-cddb-4d65-a6c3-86dfdcaa92f4"]]
D, [2018-02-20T20:52:47.865969 #3087] DEBUG -- :   SQL (0.6ms)  UPDATE "template_items" SET "sort" = $1, "updated_at" = $2 WHERE "template_items"."id" = $3  [["sort", 3], ["updated_at", "2018-02-20 19:52:47.864044"], ["id", "35e61d68-143c-4bac-ab15-fbbb2b3f13d1"]]
D, [2018-02-20T20:52:47.868568 #3087] DEBUG -- :    (2.0ms)  COMMIT
(2) D, [2018-02-20T20:52:47.918381 #3087] DEBUG -- :   TemplateItem Load (1.5ms)  SELECT "template_items".* FROM "template_items" WHERE "template_items"."template_id" = $1 ORDER BY sort ASC  [["template_id", "c965c3ed-ace2-43af-9abd-f85392bdb948"]]
I, [2018-02-20T20:52:47.930257 #3087]  INFO -- : Rendered TemplateSerializer with ActiveModelSerializers::Adapter::Attributes (17.22ms)

注意差异:

(1)日志显示&#39;模板存在&#39;消息

(2)在提交之后Rails重新加载template_items以从数据库中获取更新的数据。

我知道我可以修复此问题并强制更新操作始终执行(2)并重新加载template_items子对象:

templates_controller.rb

  def update
    if @template.update(template_params)
      @template.template_items.reload
      render json: @template, serializer: TemplateSerializer, status: :ok 
    else
      render json: ErrorSerializer.serialize(@template.errors), status: :unprocessable_entity
    end
  end

但是如果Rails有能力(有时)自己解决这个问题,为什么我需要这样做呢?尽管在两个调用中都使用了缓存,但在正确的第二个示例中,Rails已经发现需要在更新数据库后重新加载子对象,但不是在第一种情况下。

所以我想要了解的是控制这种行为的原因。在我看来,它必须与before_save模型中的Template操作相关,因为该操作仅针对第二种情况触发(template_type是&#39;模板&#39;)而不是第1个(template_type是&#39;核对清单&#39;)。换句话说,当这个动作触发它时,似乎就会发生变化。更新操作的行为。

所以我的问题是:

  1. 为什么同一动作的行为不同?如果是的话 before_save,那么为什么?

  2. 为什么在正确的情况下日志显示模板存在(因为它 两种情况都存在)?

  3. Rails如何知道在正确的情况下重新加载更新的子项 但不是在不正确的情况下?

  4. **更新**

    以下是 template_serializer.rb

    class TemplateSerializer < ActiveModel::Serializer
      attributes :id, 
        :account_id,
        :name,
        :info,
        :title,
        :template_type,
        :status
    
      has_many :template_items, include_nested_associations: true
    end
    

1 个答案:

答案 0 :(得分:1)

这里的问题是您在更改排序之前请求项目。这意味着您已经更改了它们所排序的属性,因此将不再对您拥有的项目数组进行排序。换句话说,在修改它们之后,没有另一个查询返回正确的顺序。

所以,我说可能的解决方案是:

  1. 在改变排序后重新加载项目。
  2. 在你改变排序之前,不要拉动物品。
  3. 根据更改的排序值突变template_items的顺序。
  4. 权衡:

    1. 您有2个选择查询以及更新。
    2. 在选择记录之前,您必须使用TemplateItem.update(id, sort: sort)更新记录以及事务中的所有更新。
    3. 如果您没有呈现所有结果,或者未来决定不这样做,您可能会修改不再在页面上的项目。并且,可能还有其他问题。
    4.   

      为什么同一动作的行为不同?如果是before_save,那么为什么?

      before_save在保存前请求template_items。否则,template_items在串行器呈现它们之前不会被调用。请注意,这意味着您的before_save回调没有按照您希望的方式执行,因为它正在根据以前的值修改状态。

        

      为什么在正确的情况下日志显示模板存在(因为它在两种情况下都存在)?

      SELECT  1 AS one FROM "templates" WHERE
      "templates"."name" = 'Daffy' AND 
      ("templates"."id" != 'c965c3ed-ace2-43af-9abd-f85392bdb948') AND 
      "templates"."account_id" = 'c379e356-4cce-4de2-b1b4-984b773dd43e' AND
      "templates"."template_type" = 0
       LIMIT 1
      

      查看SQL,这看起来像一个验证,以确保名称在模板和类型中是唯一的。

        

      Rails如何知道在正确的情况下重新加载更新的子项但不是在不正确的情况下?

      Rails不知道。它只在两种情况下加载它们一次。只是,使用before_save它会在记录更新之前运行。

      要点:

      解决此时间问题的最简单方法是使用不同的回调,在更新子项后触发,例如after_update