问题
好的,所以我有一个奇怪的结构问题,而且我知道的rails约定没有帮助。
在我的系统中,用户拥有服务器的成员资格。这通常是一个HABTM关联,但我通过Memberships模型使用has_many来简化一些事情(根据http://blog.flatironschool.com/why-you-dont-need-has-and-belongs-to-many/的建议)。成员资格包含有关用户与服务器关系的元数据。对于我正在做的事情,这非常有效。
现在,我遇到的麻烦是,每个成员资格都可以包含许多用户在服务器上使用的配置文件。由于配置文件与一个用户和一个服务器相关联,因此我只有配置文件belongs_to成员资格。我想要做的是在服务器模型上使用counter_cache作为配置文件,因为Rails似乎不断运行此查询:
SELECT COUNT(*) FROM "profiles" INNER JOIN "memberships" ON "profiles"."membership_id" = "memberships"."id" WHERE "memberships"."server_id" = $1
我的尝试
首先,Rails没有belongs_to through,因此配置文件不能通过成员资格模型“owner_to”服务器,因此我无法在那里提供counter_cache。
我的第二个想法是使用服务器模型上的委托来委托:profiles_count到成员资格,只是通过成员资格来执行counter_cache,但我无法让它工作。
我的第三次尝试是将server_id委托给:Profile中的成员身份,看看是否允许我与Server模型建立belongs_to关联。不好。这里有趣的是,没有抛出任何错误,它只是在没有警告的情况下回滚新配置文件中的保存。
我能想到的另一件事是将server_id和user_id列添加到“个人档案”模型,从而有效地从成员资格中复制HABTM关联。我不确定这种互联性水平会有多稳定。这实际上是在两个方向上创建has_many关联,循环引用链:membership - > server-> profile-> membership ....
问题
我是否错过了以这种方式处理counter_caching的特定约定,或者我选择了:
支持代码
以下是修剪到相关位的相关类:
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