Ruby on rails 轨道N+;1查询:monkeypatching ActiveRecord::关系#as_json 处境
我有一个型号Ruby on rails 轨道N+;1查询:monkeypatching ActiveRecord::关系#as_json 处境,ruby-on-rails,ruby,activerecord,Ruby On Rails,Ruby,Activerecord,我有一个型号用户: def User has_many :cars def cars_count cars.count end def as_json options = {} super options.merge(methods: [:cars_count]) end end def User ... def self.as_json options = {} cars_counts = Car.group(:user_id).cou
用户
:
def User
has_many :cars
def cars_count
cars.count
end
def as_json options = {}
super options.merge(methods: [:cars_count])
end
end
def User
...
def self.as_json options = {}
cars_counts = Car.group(:user_id).count
self.map do |user|
user.define_singleton_method(:cars_count) do
cars_counts[user.id]
end
user.as_json options
end
end
end
问题
当我需要将一组用户呈现给json时,我最终会遇到N+1查询问题。我的理解是,包括汽车并不能解决我的问题
试图修复
我想做的是向User
添加一个方法:
def User
has_many :cars
def cars_count
cars.count
end
def as_json options = {}
super options.merge(methods: [:cars_count])
end
end
def User
...
def self.as_json options = {}
cars_counts = Car.group(:user_id).count
self.map do |user|
user.define_singleton_method(:cars_count) do
cars_counts[user.id]
end
user.as_json options
end
end
end
这样,所有的汽车计数都将在一个查询中查询
剩余问题
ActiveRecord::Relation
已经有一个as\u json
方法,因此不选择类定义的方法。定义类时,如何使ActiveRecord::Relation
使用as_json
方法?有更好的方法吗
编辑
1.缓存
我可以缓存我的cars\u count
方法:
def cars_count
Rails.cache.fetch("#{cache_key}/cars_count") do
cars.count
end
end
一旦缓存变热,这很好,但是如果同时更新了很多用户,则可能会导致请求超时,因为在单个请求中必须更新很多查询
2.专用方法
我不需要将我的方法称为_json
,我可以将它称为我的_专用的_as_json_方法
,每次我需要呈现一组用户时,我都可以将它称为
render json: users
写
然而,我不喜欢这样做。我可能会忘记在某个地方调用这个方法,其他人可能会忘记调用它,而我正在失去代码的清晰性。出于这些原因,猴子补丁似乎是一种更好的方法。您是否考虑过对
车辆计数使用计数器缓存
?它非常适合你想做的事情
这还提供了一些其他的替代方案,例如,如果您想手动构建散列
如果您确实想继续monkey修补程序,那么请确保您正在修补ActiveRecord::Relation
而不是User
,并重写实例方法而不是创建类方法。请注意,这将影响每个ActiveRecord::relationship
,但您可以使用@klass
添加一个只为用户运行逻辑的条件
# Just an illustrative example - don't actually monkey patch this way
# use `ActiveSupport::Concern` instead and include the extension
class ActiveRecord::Relation
def as_json(options = nil)
puts @klass
end
end
选择1
在您的用户模型中:
def get_cars_count
self.cars.count
end
在控制器中:
User.all.as_json(method: :get_cars_count)
选择2
您可以创建一个方法来获取所有用户及其汽车数量。然后你就可以在上面调用as_json方法了
它大致看起来像:
#In Users Model:
def self.users_with_cars
User.left_outer_joins(:cars).group(users: {:id, :name}).select('users.id, users.name, COUNT(cars.id) as cars_count')
# OR may be something like this
User.all(:joins => :cars, :select => "users.*, count(cars.id) as cars_count", :group => "users.id")
end
在控制器中,您可以调用作为\u json
:
User.users_with_cars.as_json
这是我的解决方案,以防其他人感兴趣
# config/application.rb
config.autoload_paths += %W(#{config.root}/lib)
# config/initializers/core_extensions.rb
require 'core_extensions/active_record/relation/serialization'
ActiveRecord::Relation.include CoreExtensions::ActiveRecord::Relation::Serialization
# lib/core_extensions/active_record/relation/serialization.rb
require 'active_support/concern'
module CoreExtensions
module ActiveRecord
module Relation
module Serialization
extend ActiveSupport::Concern
included do
old_as_json = instance_method(:as_json)
define_method(:as_json) do |options = {}|
if @klass.respond_to? :collection_as_json
scoping do
@klass.collection_as_json options
end
else
old_as_json.bind(self).(options)
end
end
end
end
end
end
end
# app/models/user.rb
def User
...
def self.collection_as_json options = {}
cars_counts = Car.group(:user_id).count
self.map do |user|
user.define_singleton_method(:cars_count) do
cars_counts[user.id]
end
user.as_json options
end
end
end
感谢@gwcodes为我指出ActiveSupport::Concern
在我的控制器中,我希望能够呈现json:car\u品牌。用户
和呈现json:User。在最后一个小时内更新
以及更多。选项1有N+1查询问题,选项2意味着我每次都要用车给用户打电话,这不是干巴巴的。