问题
好的,所以我有一个奇怪的结构问题,而且我知道的 rails 约定没有帮助。
在我的系统中,用户拥有服务器的成员资格。这通常是一个 HABTM 协会,但我通过会员模型使用 has_many 来简化一些事情(根据http://blog.flatironschool.com/why-you-dont-need-has-and-belongs-to的建议-许多/)。成员资格具有有关用户与服务器关系的元数据。对于我正在做的事情,这很好用。
现在,我遇到的麻烦是每个成员都可以有许多配置文件供用户在服务器上使用。由于配置文件与一个用户和一个服务器相关联,因此我只有配置文件属于_to Membership。我想要做的是在服务器模型上为 Profiles 使用 counter_cache,因为 Rails 似乎一直在运行这个查询:
SELECT COUNT(*) FROM "profiles" INNER JOIN "memberships" ON "profiles"."membership_id" = "memberships"."id" WHERE "memberships"."server_id" = $1
我的尝试
首先,Rails 没有belong_to through,因此配置文件不能通过会员模型“belong_to”服务器,因此我无法在那里提供counter_cache。
我的第二个想法是使用 Server 模型上的委托将 :profiles_count 委托给 Membership 并通过成员资格执行 counter_cache,但我无法让它工作。
我的第三次尝试是将 :server_id 委托给 Profile 中的 :membership ,看看这是否允许我建立与 Server 模型的 belongs_to 关联。不好。有趣的是,没有为此引发错误,它只是在没有警告的情况下回滚新配置文件的保存。
我能想到的唯一另一件事是将 server_id 和 user_id 列添加到 Profiles 模型中,从而有效地从 Memberships 中复制 HABTM 关联。我不确定这种相互关联的水平会有多稳定。这将有效地在两个方向上创建 has_many 关联,使用循环引用链:membership->server->profile->membership....
问题
是否有一个我错过了以这种方式处理 counter_caching 的特定约定,或者我是否坚持以下选择:
- 通过inner join不断从数据库中查询count的现状
- 冒着无限循环的风险
支持代码
以下是精简到相关位的相关类:
class User < ApplicationRecord
has_many :memberships, dependent: :destroy
has_many :servers, through: :memberships
has_many :profiles, through: :memberships
end
class Server < ApplicationRecord
has_many :memberships
has_many :users, through: :memberships
has_many :profiles, through: :memberships
# This did not work with either singular or plural membership:
# delegate :profiles_count, to: :membership
end
class Profile < ApplicationRecord
belongs_to :membership, counter_cache: true
# The following also does not work:
# belongs_to :server, counter_cache: true
# delegate :server_id, to: :membership
scope :for_user, -> (id) { joins(:membership).where(memberships: { user_id: id }) }
scope :for_server, -> (id) { joins(:membership).where(memberships: { server_id: id}) }
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :server, counter_cache: true
has_many :profiles, dependent: :destroy
end
以下是 db/schema.rb 中的 create_tables 为简洁起见被截断为相关字段:
create_table "users", force: :cascade do |t|
# ...
end
create_table "servers", force: :cascade do |t|
# ...
t.integer "memberships_count", default: 0
t.integer "profiles_count", default: 0
end
create_table "profiles", force: :cascade do |t|
t.bigint "membership_id"
# ...
end
create_table "memberships", force: :cascade do |t|
t.bigint "user_id"
t.bigint "server_id"
# ...
t.integer "profiles_count", default: 0
end