背景:Rails 7.1,仅使用 DB 交互层,没有 Web 框架,没有服务器等…

我确信这会引起一些不满,但我一直在车辆信息数据库中遇到ActiveRecord::DangerousAttributeError关于的问题.model_name。汽车有型号,这些型号有名称。“只需更改数据库”根本不是一个选项。源数据库是外部拥有和管理的。我实际上无法修改它。而且我将该数据库与来自其他公司的外部数据一起使用,所有这些公司都使用、期望和理解“model_name”的概念。我可以在读取数据时物理地修改数据,但还没有找到可以使其工作的解决方案。

所以我想知道我是否可以.model_name在应用程序加载时从 Rails 中删除该方法。我的应用程序的功能在我的计算机上是本地的,不会在其他地方使用,而且我不会在任何地方使用 Rails 方法。因此,这种方法的存在会给我带来麻烦,而从模型中删除它则不会。

.model_name 的 ActiveModel::Naming 实例方法。对我来说绝对不重要。

这可行吗?

8

  • 1
    虽然这是可行的,但是很多地方的 rails 都使用这种方法,如果它返回一个字符串而不是 ModelName 对象,就会中断。也许你应该使用sequel或者rom来代替。


    – 

  • 我很想使用 monkeypatch 来修补 danger_attribute_method? 以忽略您的专栏。


    – 

  • 亚历克斯,这是一个核选项,看起来有点愚蠢。


    – 

  • 2
    @Alex – 为什么不直接跳过模型呢?如果你破坏了模型,那么model_name你几乎破坏了模型提供的所有功能,例如关联、多态路由、i18n 查找、查询接口、表单绑定等。如果只使用 PORO,通过直接与数据库连接交互来读取/写入数据,那么错误会更少。至少这样你就知道什么是真正有效的。我认为你使用另一种不那么固执己见的 ORM 的想法要好得多。


    – 


  • 1
    @max 我个人倾向于采用 C/C++ 格言:“足够的绳子可以射到自己的脚”


    – 


最佳答案
2

这是一个极其糟糕的想法。

仅仅因为您没有在代码中直接引用该方法或意识到它的重要性,并不意味着它不是框架的核心部分。

ActiveModel 可能很简单,但是 ActiveModel 使用它来与表单、I18N、多态路由等所有内容进行交互。简而言之,几乎所有使 Rails 约定优于配置方法发挥作用的东西。

在 ActiveRecord 中,该方法用于根据类名查找表、派生外键等。

虽然您可以明确地配置所有内容并避免由此产生的一些错误,但您建议的并不是删除该#model_name方法 – 它本质上是使用从架构创建的方法对实例方法进行 monkeypatching,这要糟糕得多。

这会打开一个潘多拉盒子,里面有很多错误,因为模型实例仍然会响应#model_name,并且在隐式地将其用作字符串的地方,您将向 Rails 提供垃圾,而不是真正对应于路由或数据库表的内容,从而导致错误和不可预测的行为。这比直接中断的地方要糟糕得多,因为代码需要一个实例ActiveModel::Name

ActiveRecord 不提供任何机制来消除危险方法名称冲突,并且似乎不是您的应用程序的最佳选择。它是一种高度固执己见的 ORM,它假设您完全控制数据库架构并遵循其约定。如果不是,那么您只会让自己的生活变得困难。

使用另一个 ORM(例如 Sequel)或在应用程序和数据库之间添加一个适配器层,例如使用问题较少的列名复制数据库。

这是可行的:

# app/models/car.rb

# prevent model_name from raising a DangerousAttributeError
ActiveRecord::AttributeMethods.instance_variable_set(
  :@dangerous_attribute_methods,
  ActiveRecord::AttributeMethods.dangerous_attribute_methods - ["model_name"]
)

class Car < ApplicationRecord
  validate do
    errors.add(:model_name, :blank) if self[:model_name].blank?
  end
end

Car.new # force generation of attribute methods
Car.const_get(:GeneratedAttributeMethods).remove_method(:model_name)

但需要注意的是,你必须使用以下命令来获取属性值。以下是我所有的测试:

>> Car.first[:model_name]
=> "asdf"

>> Car.first.model_name
=>
#<ActiveModel::Name:0x00007f3cb51e0de0
 @collection="cars",
 @element="car",
...

>> Car.new.save!
`<main>': Validation failed: Model name can't be blank (ActiveRecord::RecordInvalid)`

>> Car.new(model_name: "a").save!
=> true
>> Car.last
=> #<Car:0x00007f3cb5487300 id: 8, model_name: "a", make: nil, created_at: "2024-10-25 14:35:39.206560000 +0000", updated_at: "2024-10-25 14:35:39.206560000 +0000">

2

  • 1
    似乎您可以通过覆盖以self.dangerous_attribute_method?排除model_name此类,以较小的影响方式执行相同的操作,然后使用alias_attribute :model, :model_name后跟,def model_name; super; end尽管我还没有测试过。


    – 


  • 1
    @engineersmnky 哦,是的,这要简单得多,除非它应该是def model_name; self.class.model_name; end


    –