【发布时间】:2022-01-02 04:25:20
【问题描述】:
我想结合以下两件事:
我的 STI 结构:
假设 Rails 模型 User。只有两种类型的用户,即Admin 和Customer。 STI 列是type,表称为users,但实际上,我们永远不会有和User 对象,只有Admins 和Customers。
-
User(用于存储所有数据和实现通用功能,但从未实例化)AdminCustomer < User
动态扩展模型:
在某些情况下,动态扩展模型类很方便,可以暂时拥有一个具有更多能力的对象来执行特定操作。假设我的create 控制器操作检查附加字段“发送欢迎邮件”(由new 表单提供)。该属性是虚拟的,因此我们可以在表单中使用它,就好像它是一个普通的列一样。这可以通过以下方式实现:
extended_user_class = Class.new(User)
extended_user_class.send(:include, MyAwesomeMixins)
extended_user_class.class_eval do
my_virtual_attribute :send_welcome_mail, default: true
end
model = extended_user_class.new
# send model to the view and it "just works"
两者结合
不幸的是,将这两种技术结合起来并不能以上述方式工作,因为 Rails STI 似乎被匿名类破坏了:
[1] pry(main)> User.where(id: 1).to_sql
=> "SELECT `users`.* FROM `users` WHERE `users`.`id` = 1"
[2] pry(main)> Class.new(User).where(id: 1).to_sql
=> "SELECT `users`.* FROM `users` WHERE `users`.`type` IS NULL AND `users`.`id` = 1"
编辑:要重现上述内容,必须在生产环境中。
第二个语句是错误的,因为它按类型过滤。第一个语句是预期的行为。
因此,显然匿名类对 STI 不利。如https://dev.to/factorial/a-trick-with-ruby-anonymous-classes-11pp 中所述重新定义常量User 可能会破坏应用程序 - 我希望扩展类仅在显式调用时使用 (extended_user_class) - User 必须不受影响。
这可以在 Ruby 3 / Rails 6 中实现吗?
【问题讨论】:
-
感觉您描述的模式与大约 10 年前在 Rails 社区中流行的 DCI 模式非常相似。当时我真的不喜欢这种模式——主要是因为它使代码更难理解和使用,因为你不能再确定一个类的实例在你的应用程序的两个不同地方以相同的方式表现。您总是必须仔细检查是否需要以某种方式更改实例才能在特定上下文中工作。
-
但更重要的是这个模式有a huge impact on your application's performance。
-
我在“rails 控制台”中没有遇到同样的问题。
Class.new(Client).where(id: 1).to_sql->"SELECT \"clients\".* FROM \"clients\" WHERE \"clients\".\"id\" = 1" -
这真是一个阅读@spickermann的地狱!
-
这看起来更像是寻找问题的解决方案。如果您想从模型本身中删除处理表单的逻辑,您可以使用表单对象,但我不明白您为什么要动态生成子类,因为它们只有两个。匿名课程不仅对 STI 不利。 ActiveModel 和 ActiveRecord 基于类名的大量假设。雅格尼。
标签: ruby-on-rails ruby anonymous-class sti