【问题标题】:Active Model Serializer and Pundit deleting records during a Show CRUD actionActive Model Serializer 和 Pundit 在 Show CRUD 操作期间删除记录
【发布时间】:2018-04-17 14:44:56
【问题描述】:

好的,这里有些东西严重损坏了......

我将 Active Model Serializer 和 Pundit 用于我的 Rails 5 JSONAPI 服务器,将 Ember 用于我的前端应用程序。

我有User 模型和针对用户模型的 Pundit 政策,可防止非作者查看未发布的故事和章节。

目前,我看到一个奇怪的问题是这样的:

1. UserA creates StoryA, and two published chapters Chapter1 and Chapter2
2. UserA then creates two unpublished chapters Chapter3 and Chapter4
3. UserA logouts
4. UserB logins
5. UserB views the same story created by UserA
6. Server policy kicks in and scope the results to only published chapters since UserB isn't the author.
7. * An SQL DELETE query is sent to delete the two unpublished stories for some odd reason.

这是一些截图:

UserA 创建了 2 个已发布的故事和 2 个未发布的故事

数据库记录显示4个故事属于巨神峰故事

UserA 注销,UserB 登录,查看 Mount Targon 故事

(如您所见,UserB 只看到两个已发布的章节,这是正确的,但是...)

出于某种奇怪的原因,未发布的章节从数据库中删除

查看 Rails 控制台,我在 ChaptersController#show CRUD 操作期间看到 DELETE 查询:

Started GET "/stories/16" for 127.0.0.1 at 2017-11-05 17:02:53 +0800
Processing by StoriesController#show as JSON
  Parameters: {"id"=>"16"}
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  Story Load (0.1ms)  SELECT  "stories".* FROM "stories" WHERE "stories"."id" = ? LIMIT ?  [["id", 16], ["LIMIT", 1]]
  Chapter Load (0.3ms)  SELECT "chapters".* FROM "chapters" INNER JOIN "stories" ON "stories"."id" = "chapters"."story_id" INNER JOIN "users" ON "users"."id" = "stories"."user_id" WHERE "chapters"."story_id" = ? AND ((stories.published = 't' AND chapters.published = 't') OR stories.user_id = 2)  [["story_id", 16]]
  Chapter Load (0.1ms)  SELECT "chapters".* FROM "chapters" WHERE "chapters"."story_id" = ?  [["story_id", 16]]
   (0.1ms)  begin transaction
Started GET "/chapters/26" for 127.0.0.1 at 2017-11-05 17:02:53 +0800
  SQL (0.3ms)  DELETE FROM "chapters" WHERE "chapters"."id" = ?  [["id", 32]]
Started GET "/chapters/27" for 127.0.0.1 at 2017-11-05 17:02:53 +0800
Processing by ChaptersController#show as JSON
  SQL (0.1ms)  DELETE FROM "chapters" WHERE "chapters"."id" = ?  [["id", 33]]
Processing by ChaptersController#show as JSON
  Parameters: {"id"=>"26"}
   (2.1ms)  commit transaction
  Parameters: {"id"=>"27"}
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
[active_model_serializers]   User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  Chapter Load (0.1ms)  SELECT  "chapters".* FROM "chapters" WHERE "chapters"."id" = ? LIMIT ?  [["id", 26], ["LIMIT", 1]]
[active_model_serializers]   Story Load (0.4ms)  SELECT "stories".* FROM "stories" WHERE "stories"."user_id" = ?  [["user_id", 1]]
  Chapter Load (0.2ms)  SELECT  "chapters".* FROM "chapters" WHERE "chapters"."id" = ? LIMIT ?  [["id", 27], ["LIMIT", 1]]
Started GET "/chapters/32" for 127.0.0.1 at 2017-11-05 17:02:53 +0800
Started GET "/chapters/33" for 127.0.0.1 at 2017-11-05 17:02:53 +0800
  Story Load (0.2ms)  SELECT  "stories".* FROM "stories" WHERE "stories"."id" = ? LIMIT ?  [["id", 16], ["LIMIT", 1]]
[active_model_serializers] Rendered StorySerializer with ActiveModelSerializers::Adapter::JsonApi (22.64ms)
  Story Load (0.1ms)  SELECT  "stories".* FROM "stories" WHERE "stories"."id" = ? LIMIT ?  [["id", 16], ["LIMIT", 1]]
Processing by ChaptersController#show as JSON
Processing by ChaptersController#show as JSON
[active_model_serializers] Rendered ChapterSerializer with ActiveModelSerializers::Adapter::JsonApi (0.82ms)
Completed 200 OK in 43ms (Views: 27.1ms | ActiveRecord: 3.9ms)

我的 ChaptersController 显示动作中甚至没有“删除”或“销毁”两个词……那么记录是如何被删除的呢?

# CHAPTERS CONTROLLER
def show
  chapter = Chapter.find_by(id: params[:id])

  if chapter.present?
    authorize chapter
    render json: chapter, status: :ok
  else
    skip_authorization
    render json: { error: "Chapter not found" }, status: :not_found
  end
end

我的章节策略显示方法:

# CHAPTER PUNDIT POLICY
def show?
  (@record.published? && @record.story.published?) || (@record.story.user == @user)
end

我的 StoriesController Show 动作如下所示:

# STORIES CONTROLLER
def show
  story = Story.find_by(id: params[:id])

  if story.present?
    authorize story
    story.chapters = policy_scope(story.chapters)
    render json: story, include: [:user, :chapters], status: :ok
  else
    skip_authorization
    render json: { errors: "Story not found" }, status: :not_found
  end
end

我认为可能是 Ember 在幕后做了一些有趣的额外查询,但我使用 Postman Mac 应用程序来测试查看故事,果然,未发布的章节被删除而根本不经过 Ember。出于某种奇怪的原因,它发生在服务器端 =/

有什么想法吗?

【问题讨论】:

    标签: ruby-on-rails ember.js active-model-serializers json-api pundit


    【解决方案1】:

    在我看来,这实际上是在 StoriesController#show 中完成的。您应该再次尝试仅测试此方法(在 RSpec 或其他方式中),并且您还可以逐步执行代码以准确定位删除发生的位置。我的猜测是:

    story.chapters = policy_scope(story.chapters)
    

    您正在更改此处的故事章节。大概你想做类似的事情

    @displayed_chapters = policy_scope(story.chapters)
    

    【讨论】:

    • 将行注释为# story.chapters = policy_scope(story.chapters) 确实可以防止该记录在数据库中被删除。我需要根据 Pundit 策略过滤章节,否则我的服务器会返回所有章节,包括未发布的章节,即使对于没有创建故事的用户也是如此。是否有适当的方法来过滤掉数据?更奇怪的是,我没有在story.chapters = policy_scope(story.chapters)之后打电话给story.save,那为什么故事会自救呢?
    • 好吧,没关系,我得到了为什么它自动保存在这里的答案:gist.github.com/demisx/9896113 文档说:When you assign an object to a has_many association, that object is automatically saved (in order to update its foreign key). If you assign multiple objects in one statement, then they are all saved.
    • 现在我需要弄清楚如何在这一行中过滤我的包含字段 render json: story, include: [:user, :chapters], status: :ok 这样 story.chapters 就不会如果用户不是作者,则返回未发布的章节 =/
    • @Zhang 我假设您可以将授权的 cmets 作为参数传递给序列化程序,或者覆盖序列化程序中的 cmets 字段并在那里授权(不确定哪个更收费我的头-但显然其他人以前也遇到过这个问题:)
    • 不确定以前是否有人遇到过这个问题,我的搜索结果没有返回任何类似的东西。在 Pundit 文档中,他们确实有一个关于在 Rails erb html 视图中使用 policy_scope(@post.comments) 的部分,但我的前端是用 Ember 构建的,而不是 Rails,所以我不能用 Ember 做到这一点。我什至尝试用 jsonrails-rb gem 替换 Active Model Serializer 以渲染到 JSONAPI,结果仍然相同。我已经在 Pundit github 问题跟踪器上发布了这个问题。这个用例看起来很简单,如果 Pundit 开发人员说没有办法做到这一点,我会感到困惑。
    【解决方案2】:

    没有任何东西可以作为 DELETE 的可能来源。我认为解决方案是一些好的、老式的调试。

    • 尝试注释掉动作中的所有行。然后一一取消注释。
    • 查找日志中提到的事务
    • 订阅 sql 查询并在查询被删除时引发并查看堆栈跟踪。或者完全覆盖删除。

    如果删除发生在序列化程序或策略中,是用户态代码中的某些内容导致它发生。两个图书馆甚至都不知道活动记录。

    【讨论】:

      猜你喜欢
      • 2014-09-20
      • 2016-01-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-20
      • 1970-01-01
      相关资源
      最近更新 更多