是否可以将方法委托给rails中的has_many
关联,并且仍然保存该关联上的预加载数据,同时遵循demeter定律?目前在我看来,你被迫选择其中一个。即:通过NOT委托保留预加载的数据,或丢失预加载的数据和委托。
示例:我有以下两种模式:
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?
的委派方法完全相同。
以下,据我所知,违反了德米特定律。但是:它会维护您的预加载数据:
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定律。
应用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
希望两种实现的缺点都很明显。理想情况下:我希望以委托实现的第二种方式实现,但要维护预加载的数据。这可能吗?
答案 0 :(得分:4)
<强>解释强>
all_have_title?
委派在您的示例中无法正常工作的原因是您将方法委托给blogs
关联,但将其定义为Blog
类方法,它们是不同的实体,因而是接收者。
此时,所有关注者都会问一个问题,为什么在OP提供的第二个示例中调用NoMethodError
时没有引发user.all_have_title?
异常。这背后的原因在ActiveRecord::Associations::CollectionProxy
文档(这是user.blogs
调用的结果对象类)中详细阐述,由于我们的示例namings状态而改述:
user.blogs
中的关联代理将user
中的对象设为@owner
,将blogs
收集为@target
,{{1} object表示@reflection
宏 此类通过:has_many
将未知方法委派给@target
。
所以发生的事情的顺序如下:
method_missing
在初始化delegate
模型的all_have_title?
范围内定义has_many
实例方法; User
user
时,方法被委托给all_have_title?
协会; has_many
委托给Blog
class all_have_title?
方法; all
方法在method_missing
上调用Blog
,其中包含current_scope
条件user_id
此时持有scoped_attributes
值),所以没有关于预加载的信息,因为基本上发生的是:
{"user_id"=>1}
分别为每个Blog.where(user_id: 1)
,这是与之前执行的预加载相比的主要差异,它使用user
按多个值查询相关记录,但此处执行的一个查询单个记录使用in
(这就是为什么查询本身甚至没有在这两个调用之间缓存的原因)。
<强>解决方案强>
要明确封装方法并将其标记为基于关系(在=
和User
之间),您应该在Blog
关联中定义和描述它的逻辑范围:
has_many
因此,您所做的调用应仅产生以下2个查询:
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
这种方式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
模型中定义实例方法并定义其中的所有逻辑:
Blog
答案 1 :(得分:3)
这是一个符合demeter法则的解决方案,并且尊重预加载的数据(不会再次点击数据库)。这当然有点奇怪,但我找不到任何其他解决方案,我真的想知道其他人对此的看法:
<强>模型强>
class User < ApplicationRecord
has_many :blogs
def all_blogs_have_title?
blogs.all_have_title_present?(self)
end
end
class Blog < ApplicationRecord
belongs_to :user
def self.all_have_title_present?(user)
user.blogs.any? && user.blogs.all? {|blog| blog.title.present?}
end
end
<强>用法强>
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
到达它的邻居(Blog
)的属性,它将问题委托给它的邻居并允许邻居(再次:Blog
)回答关于它自己的属性的问题。
奇怪的是,在Blog
模型中,类方法会询问user.blogs
,因此Blog
知道User
上的关联。但也许这没关系,因为毕竟Blog
和User
彼此之间有着共同关系。
答案 2 :(得分:0)
此scope_delegation gem将完成您的工作。
如果你要像这样定义你的作品
WebElement element = driver.findElement(By.id("gbqfd"));
JavascriptExecutor executor = (JavascriptExecutor)driver;
executor.executeScript("arguments[0].click();", element);
这应该有效:)