如何处理列名('hash')与 Rails 冲突的旧数据库表?

Posted

技术标签:

【中文标题】如何处理列名(\'hash\')与 Rails 冲突的旧数据库表?【英文标题】:How do I handle a legacy database table with a column name ('hash') which ***es with Rails?如何处理列名('hash')与 Rails 冲突的旧数据库表? 【发布时间】:2021-08-06 07:46:42 【问题描述】:

我正在使用一个旧的 mysql 数据库,我尝试使用 Rails 与之交互,创建记录时出现错误:

2.7.2 :005 > HashRef.create!(hash: '00080e597c26a05197aaa4462e53cdf9847b56a4d5d47e550fdc01554434df8gg2fffsss')
  TRANSACTION (0.2ms)  BEGIN
  HashRef Create (0.3ms)  INSERT INTO `hash_ref` (`hash`) VALUES ('00080e597c26a05197aaa4462e53cdf9847b56a4d5d47e550fdc01554434df8gg2fffsss')
  TRANSACTION (4.7ms)  COMMIT
Traceback (most recent call last):
        2: from (irb):4
        1: from (irb):5:in `rescue in irb_binding'
TypeError (no implicit conversion of String into Integer)

这似乎是因为 DB 表有一个名为 hash(主键)的字符串列,它与某些 Rails 内容冲突。

CREATE TABLE `hash_ref` (
  `hash` varchar(512) NOT NULL,
  `cipher` varchar(512) DEFAULT NULL,
  `clear` varchar(512) DEFAULT NULL,
  `record_insert_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`hash`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

我可以给属性起别名,但这会在写入时引发同样的错误。

  alias_attribute :my_hash, :hash

很遗憾,此时重命名列不是一个选项。

我原以为有某种方法可以代理列名,所以我可以在代码中将其称为my_hash,但当它查询数据库时,它使用hash

我在另一个答案中看到了这个猴子补丁,这似乎有效,但我想知道这样做是否安全:

class << self
    def instance_method_already_implemented?(method_name)
      return true if method_name == 'hash'
      super
    end
  end

也有推荐的gem,但是很多年没更新了:

https://github.com/bjones/safe_attributes

我唯一能想到的就是使用原始 SQL 来写入这个 DB,但是在编写 RSpec 测试时无法使用模型或工厂非常尴尬。

感谢您的帮助

【问题讨论】:

老实说,我不知道什么最有效,这是你所处的一个非常不幸的位置。我需要尝试不同的想法来尝试什么有效,但我猜你我现在比我现在处于更好的位置。 但是,我在查看时发现的另一种可能性是here:也许您可以使用default_scope :select=&gt; 'hash as hash_value' 之类的东西来重命名属性? 但正如下面回答的那样,重命名列可能真的只是最简单的解决方案吗?...我知道你说“这不是一个选择”,但也许你对该声明的理由会被否决不同解决方法的复杂性/危险性。 【参考方案1】:

您与之冲突的方法是来自 Ruby 核心而不是 Rails 的 Object#hash。如果两个哈希键相同,这就是 Hash 类的查找方式。这就是为什么你会得到TypeError (no implicit conversion of String into Integer),因为该方法应该返回一个整数。

这很hacky,但绕过了这个问题:

# IMPORTANT!
# the 'hash' column is available as hash_value as the name collides a method from the 
# ruby core
class Hashref < ApplicationRecord
  self.primary_key = :hash
  
  # avoids clobbering the hash method from Object
  def self.define_attribute_method(attr_name, **opts)
    super unless attr_name == "hash"
  end

  # feel free to rename this to whatever you want
  def hash_value
    read_attribute(:hash)
  end

  def hash_value=(value)
    write_attribute(:hash, value)
  end
end
Loading development environment (Rails 6.1.3.2)
irb(main):001:0> hashref = Hashref.create(hash_value: 'abcd1234')
   (0.5ms)  SELECT sqlite_version(*)
  TRANSACTION (0.1ms)  begin transaction
  Hashref Create (8.2ms)  INSERT INTO "hashrefs" ("hash", "created_at", "updated_at") VALUES (?, ?, ?)  [["hash", "abcd1234"], ["created_at", "2021-08-06 12:54:56.406910"], ["updated_at", "2021-08-06 12:54:56.406910"]]
  TRANSACTION (5.7ms)  commit transaction
irb(main):002:0> hashref.hash
=> 2355933382791539607
irb(main):003:0> hashref.hash_value
=> "abcd1234"

这里的关键可能是大量的测试和文档,这样您就不会意外使用hash 而不是hash_value

【讨论】:

我怀疑 self.primary_key = :hash 可能会导致问题,例如如果 Object#hash 用于比较而不是实际 id。 YMMV。【参考方案2】:

是的,hash 是由 Active Record 定义的。您提供的解决方案很危险。我不建议使用它。相反,我建议您重命名字段名称

【讨论】:

这在技术上是不正确的,更多的是评论而不是实际答案。

以上是关于如何处理列名('hash')与 Rails 冲突的旧数据库表?的主要内容,如果未能解决你的问题,请参考以下文章

如何处理第三方组件依赖与 react native 或其他组件冲突?

咨询师如何处理冲突?

如何处理spring事务与多数据源冲突问题?

使用 pd.read_clipboard 时如何处理包含空格的列名?

如何处理 Rails 中的嵌套表单?

sql server 排序规则是不是意味着列名必须是正确的大小写?以及如何处理