我有一个模型User
:
def User
has_many :cars
def cars_count
cars.count
end
def as_json options = {}
super options.merge(methods: [:cars_count])
end
end
当我需要向json呈现一组用户时,我最终会遇到N + 1查询问题。我的理解是,包括汽车并不能解决我的问题。
我想要做的是向User
添加方法:
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_json
方法,因此不会选择定义的类。如何在定义时使ActiveRecord::Relation
使用类中的as_json
方法?有更好的方法吗?
我可以缓存我的cars_count
方法:
def cars_count
Rails.cache.fetch("#{cache_key}/cars_count") do
cars.count
end
end
一旦缓存温暖,这很好,但是如果很多用户同时更新,则可能导致请求超时,因为必须在单个请求中更新许多查询。
我可以将其称为as_json
而不是调用我的方法my_dedicated_as_json_method
,而每次我需要渲染一组用户,而不是
render json: users
写
render json: users.my_dedicated_as_json_method
但是,我不喜欢这种做法。我可能忘记在某个地方调用此方法,其他人可能会忘记调用它,并且我正在失去代码的清晰度。由于这些原因,猴子补丁似乎是更好的途径。
答案 0 :(得分:0)
您是否考虑过将counter_cache
用于cars_count
?它非常适合您想要做的事情。
此blog article还提供了其他一些替代方案,例如如果你想手动建立一个哈希。
如果您真的想继续沿着猴子修补路线前进,请确保您正在修补ActiveRecord::Relation
而不是User
,并覆盖实例方法,而不是创建类方法。请注意,这会影响每个 ActiveRecord::Relation
,但您可以使用@klass
添加仅运行User
的逻辑的条件
# 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 :(得分:0)
在您的用户模型中:
def get_cars_count
self.cars.count
end
在你的控制器中:
User.all.as_json(method: :get_cars_count)
您可以创建一个方法来获取所有用户及其车辆数量。然后你可以在那上面调用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
在您的控制器中,您可以拨打as_json
:
User.users_with_cars.as_json
答案 2 :(得分:0)
以下是我的解决方案,以防其他人感兴趣。
# 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
。