如何处理列名('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=> '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 或其他组件冲突?