Rails:尝试保存现有行但 rails 生成 sql insert 而不是 update
Posted
技术标签:
【中文标题】Rails:尝试保存现有行但 rails 生成 sql insert 而不是 update【英文标题】:Rails: attempting save an existiing row but rails generates sql insert instead of update 【发布时间】:2018-03-24 11:55:47 【问题描述】:错误
当我有新记录时,我没有问题。
当我尝试更新现有记录时,会引发异常(见下文)。
当我希望更新的 email_addresses 表中有现有行时,以下两种 Rails 代码变体都会引发相同的异常:
ret = @email_address.update(email_address_id: @email_address.email_address_id)
我也试过了
ret = @email_address.save
并得到相同的异常。
异常
当我有一个想要更新的现有记录时,我得到的错误是
#<ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "email_addresses_pkey"
DETAIL: Key (email_address_id)=(2651) already exists.
: INSERT INTO "email_addresses" ("email_address_id", "email_address", "zipcode", "confirm_token") VALUES ($1, $2, $3, $4) RETURNING "email_address_id">
我认为问题出在哪里
我认为问题可能与我没有 id 列有关。
email_address_id 与 id 列的用途相同。
背景
使用 psql 的 \d+ 命令我得到
development=# \d+ email_addresses
Table "public.email_addresses"
Column | Type | Modifiers | Storage | Stats target | Description
--------------------+-------------------+----------------------------------------------------------------------------+----------+--------------+-----------------------------------
email_address_id | integer | not null default nextval('email_addresses_email_address_id_seq'::regclass) | plain | |
email_address | citext | not null | extended | |
unsubscribe_reason | text | not null default ''::text | extended | |
zipcode | text | | extended | | If non-NULL then check confirmed.
email_confirmed | boolean | default false | plain | |
confirm_token | character varying | | extended | |
Indexes:
"email_addresses_pkey" PRIMARY KEY, btree (email_address_id)
"email_address_idx" UNIQUE, btree (email_address)
Referenced by:
TABLE "xref__email_addresses__realtor_organizations" CONSTRAINT "email_address_id_fkey" FOREIGN KEY (email_address_id) REFERENCES email_addresses(email_address_id) ON UPDATE CASCADE ON DELETE RESTRICT
型号
class EmailAddress < ApplicationRecord
validates_presence_of :email_address
validates_format_of :email_address, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]2,)\Z/i, on: :create
validates_format_of :email_address, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]2,)\Z/i, on: :save
validates_format_of :email_address, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]2,)\Z/i, on: :update
def initialize(email_address_params)
super(email_address_params)
confirmation_token()
end
# zipcodes can be nil/null in the database but we should never allow a human user to create such a record.
validates_presence_of :zipcode
validates_format_of :zipcode, with: /\A\d5(-\d4)?\z/, message: "zipcode should be in the form 12345", on: :create
def email_activate
self.email_confirmed = true
self.confirm_token = nil
save!(:validate => false)
end
private
def confirmation_token
if self.confirm_token.blank?
self.confirm_token = SecureRandom.urlsafe_base64.to_s
end
end
end
生成的sql代码:
Rails 生成:
INSERT INTO "email_addresses" ("email_address_id", "email_address", "zipcode", "confirm_token") VALUES ($1, $2, $3, $4) RETURNING "email_address_id" [["email_address_id", 2651], ["email_address", "ralphs@dos32.com"], ["zipcode", "80503"], ["confirm_token", "r-UX4zOdOmHC7lO7Ta2pBg"]]
如果 email_address_id 已经存在于 email_addresses 表中,这显然会失败,因为
"email_addresses_pkey" PRIMARY KEY, btree (email_address_id)
对主键的隐式约束。
我可能可以通过直接在 Rails 中编写 SQL 代码来实现这一点,但我想继续使用 Rails 代码并了解我做错了什么。
我在网上搜索了几个小时都没有成功。我尝试过跟踪 rails 代码,但元编程代码变得如此复杂,以至于我迷路了。
问题
Rails 如何知道何时执行 SQL UPDATE 而不是 SQL INSERT?
如何根据现有的 email_addresses 表更新现有记录?
【问题讨论】:
【参考方案1】:由于您使用的是非标准 id,您需要告诉 rails 列用作主键,这样它可以评估保存是否应该插入或更新。
为此,您需要在模型中添加self.primary_key = 'email_address_id'
。
请记住,Rails 更喜欢约定而不是配置,所以无论何时你违反约定,都需要告诉 Rails。
P.S.:将 email_addresses
的 pkey 命名为 email_address_id
似乎是多余的;通常resource_id
的格式通常用于 fkeys,而对于 pkeys,您可以只定义列名。
【讨论】:
感谢您的回答,但这似乎不是我的问题。该问题似乎与 persist? 功能有关。了解更多,我会添加更多。 我想知道为什么这被否决了,以便我以后可以改进我的问题。以上是关于Rails:尝试保存现有行但 rails 生成 sql insert 而不是 update的主要内容,如果未能解决你的问题,请参考以下文章