to_json中的会话感知模型方法导致n + 1个查询

时间:2017-12-27 17:06:38

标签: ruby-on-rails ruby ruby-on-rails-4 activerecord rails-activerecord

这是现有应用程序的优化问题,我已经使代码通用,既使其变得更加容易理解,也更容易理解,而不是我描述论坛讨论类型情况的专有模型。我已修改此示例中的所有代码而未对其进行测试,因此如果有任何错别字我道歉,如果有人向我指出,我会尝试修复它们。

让我们说我有一个有四个模型的rails应用程序:Event,User,Forum和Post。

重要的关系如下:

  • 用户有很多活动。
  • 论坛有很多帖子。
  • Post有很多活动。

前端是单页javascript应用程序,因此所有数据库数据都需要以json格式返回。

背景:

  • 当用户点击帖子时,会使用名称创建一个事件 '查看'这标志着这个帖子不再是新的。
  • 用户需要 登录以查看哪些帖子是新点击论坛的电话 以下终点:
  • 有多个用户,因此可以在帖子和用户之间建立多对多的关系。

example.com/forum/15/all_posts

见下相关代码:

论坛管制员:

#forums_controller.rb
def all_posts
    current_user = User.find(session[:user_id])
    forum = Forum.includes(:posts).where(id: params[:id]).take

    forum.posts.each do |post|
        post.current_user = current_user
    end

     render json: forum.to_json(
        include: [
            { posts: {
                methods: [:is_new]
            }}
        ]
     )
end

帖子模特:

#post.rb (posts model)

has_many :events
attr_accessor :current_user

def is_new
    if current_user #user may not be logged in
        !!self.events.where(user_id: current_user.id, name: 'Show').take
    else
        false
    end
end

模型是动作所在的位置,因此我们尝试将逻辑排除在控制器之外,但由于会话在模型中不可用,我们最终将这个疯狂的工作添加为将current_user添加为attr_accessor,以便方法可以为相关用户返回适当的数据....我不喜欢这样,但我从来没有想出更好的方法来做到这一点。我们在其他地方重复了这种模式,我很乐意听到替代方案。

这是我的问题:

在前端使用is_new调用来确定哪些帖子可以点亮,但它也会触发n + 1场景如果有10个帖子,这个端点会给我带来总共12个查询如果我的活动表很大,那就不好了。如果我将所有逻辑移动到控制器,我可以在2个查询中执行此操作。

总之,我有两个问题:

  1. 最重要:如何修复此n + 1情况?
  2. 一般来说有更好的方法吗?在给to_json打电话之前,我不想要每个循环。我发现这个模式不优雅或易于理解。同时我不想将所有代码都移到控制器中。执行此操作的轨道方式是什么?

2 个答案:

答案 0 :(得分:1)

如果使用scope是一种选择,我会尝试类似:

class Post < ApplicationRecord
  scope :is_new, -> { where(user_id: current_user.id, name: 'Show') } if current_user.id?
end

如果在您的情况下发送current_user是更好的选择,您也可以这样做:

class Post < ApplicationRecord
  scope :is_new, ->(current_user) {...}
end

答案 1 :(得分:0)

这只是一个伪代码来举例:

第一个答案

当我发布这个时,我忘了你正在从ForumsController渲染json。

发布

scope :for_user, -> (user = nil) do 
  includes(events: :users).where(users: {id: user.id}) if user
end

def is_new_for_user?(user = nil)
  return true if user.nil?
  self.events.empty?{ |e| e.name == 'Show' }
end

PostController中

def index
  @posts = Post.for_user(current_user)
end

文章/ index.html.erb

...
<% if post.is_new_for_user?(current_user) %>
  ...
<% end
...

第二个答案

这仍然是伪代码。我没有测试任何东西。

论坛

scope :for_user, -> (user = nil) do
  if user
    includes(posts: [events: :users]).where(users: {id: user.id})
  else
    includes(:posts)
  end
end

ForumsController

def all_posts
    current_user = User.find(session[:user_id])
    forum = Forum.for_user(current_user).where(id: params[:id]).take

     render json: forum.to_json(
        include: [
            { posts: {
                methods: [:is_new_for_user?(current_user)]
            }}
        ]
     )
end