我有一个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;)。换句话说,当这个动作触发它时,似乎就会发生变化。更新操作的行为。
所以我的问题是:
为什么同一动作的行为不同?如果是的话 before_save,那么为什么?
为什么在正确的情况下日志显示模板存在(因为它 两种情况都存在)?
Rails如何知道在正确的情况下重新加载更新的子项 但不是在不正确的情况下?
**更新**
以下是 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
答案 0 :(得分:1)
这里的问题是您在更改排序之前请求项目。这意味着您已经更改了它们所排序的属性,因此将不再对您拥有的项目数组进行排序。换句话说,在修改它们之后,没有另一个查询返回正确的顺序。
所以,我说可能的解决方案是:
template_items
的顺序。权衡:
TemplateItem.update(id, sort: sort)
更新记录以及事务中的所有更新。为什么同一动作的行为不同?如果是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
。