gpt4 book ai didi

ruby-on-rails - 将方法委托(delegate)给 has_many 关联会忽略预加载

转载 作者:数据小太阳 更新时间:2023-10-29 06:46:49 28 4
gpt4 key购买 nike

是否可以将方法委托(delegate)给 Rails 中的 has_many 关联,并且仍然将预加载的数据保存在该关联上,同时遵循得墨忒耳法则?目前在我看来,你被迫选择一个或另一个。也就是说:通过不委托(delegate)来保留预加载的数据,或者丢失预加载的数据和委托(delegate)。

示例:我有以下两个模型:

class User < ApplicationRecord
has_many :blogs

delegate :all_have_title?, to: :blogs, prefix: false, allow_nil: false

def all_blogs_have_title?
blogs.all? {|blog| blog.title.present?}
end
end


class Blog < ApplicationRecord
belongs_to :user

def self.all_have_title?
all.all? {|blog| blog.title.present?}
end
end

注意:User#all_blogs_have_title? 做的事情与 all_have_title? 的委托(delegate)方法完全相同。

据我了解,以下内容违反了得墨忒耳定律。但是:它会保留您预加载的数据:

user = User.includes(:blogs).first
User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
Blog Load (0.1ms) SELECT "blogs".* FROM "blogs" WHERE "blogs"."user_id" = 1
=> #<User id: 1, name: "all yes", created_at: "2017-12-05 20:28:00", updated_at: "2017-12-05 20:28:00">

user.all_blogs_have_title?
=> true

注意:当我调用 user.all_blogs_have_title? 时,它没有执行额外的查询。但是,请注意方法 all_blogs_have_title? 正在询问 Blog 属性,这违反了 demeter 法则。

其他应用得墨忒耳定律但会丢失预加载数据的方式:

user = User.includes(:blogs).first
User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
Blog Load (0.1ms) SELECT "blogs".* FROM "blogs" WHERE "blogs"."user_id" = 1
=> #<User id: 1, name: "all yes", created_at: "2017-12-05 20:28:00", updated_at: "2017-12-05 20:28:00">

user.all_have_title?
Blog Load (0.2ms) SELECT "blogs".* FROM "blogs" WHERE "blogs"."user_id" = ? [["user_id", 1]]
=> true

希望这两种实现的缺点是显而易见的。理想情况下:我想用委托(delegate)实现的第二种方式来做,但要维护预加载的数据。这可能吗?

最佳答案

解释

all_have_title? 委托(delegate)在您的示例中无法正常工作的原因是您将方法委托(delegate)给 blogs 关联,但尚未将其定义为 Blog 类方法,它们是不同的实体,因此是接收者。

到这里大家肯定会问为什么在OP提供的第二个例子中调用user.all_have_title?时没有出现NoMethodError异常。 ActiveRecord::Associations::CollectionProxy 中详细阐述了这背后的原因。文档(这是 user.blogs 调用的结果对象类),由于我们的示例命名状态而改写:

that the association proxy in user.blogs has the object in user as @owner, the collection of his blogs as @target, and the @reflection object represents a :has_many macro.
This class delegates unknown methods to @target via method_missing.

所以事情发生的顺序如下:

  1. delegateUser 初始化模型的 has_many 范围内定义 all_have_title? 实例方法;
  2. 当调用 user all_have_title? 方法时,委托(delegate)给 has_many 关联;
  3. 因为没有定义这样的方法,所以它被委托(delegate)给 Blogall_have_title? 方法,通过 method_missing;
  4. all方法在 Blog 上调用,current_scope 包含 user_id 条件(此时 scoped_attributes 包含 { "user_id"=>1} 值),所以没有关于预加载的信息,因为基本上发生的事情是:

    Blog.where(user_id: 1)

    分别为每个user,这是与之前执行的预加载相比的关键区别,后者使用in查询多个值的关联记录,但一个此处执行的查询使用 = 查询单个记录(这就是查询本身甚至没有在这两个调用之间缓存的原因)。

解决方案

要显式封装方法并将其标记为基于关系(在 UserBlog 之间),您应该在 has_many 中定义和描述它的逻辑 关联范围:

class User
delegate :all_have_title?, to: :blogs, prefix: false, allow_nil: false

has_many :blogs do
def all_have_title?
all? { |blog| blog.title.present? }
end
end
end

因此您所做的调用应该只会导致以下 2 个查询:

user = User.includes(:blogs).first
=> #<User:0x00007f9ace1067e0
User Load (0.8ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
Blog Load (1.4ms) SELECT `blogs`.* FROM `blogs` WHERE `blogs`.`user_id` IN (1)
user.all_have_title?
=> true

这样 User 就不会隐式操作 Blog 的属性,您也不会丢失预加载的数据。如果不想关联方法直接操作title属性(all方法中的block),可以在Blog中定义一个实例方法在那里建模并定义所有逻辑:

class Blog
def has_title?
title.present?
end
end

关于ruby-on-rails - 将方法委托(delegate)给 has_many 关联会忽略预加载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47662568/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com