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_idid 列的用途相同。

背景

使用 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的主要内容,如果未能解决你的问题,请参考以下文章

Rails 不返回特定模型的生成 ID

在视图中呈现现有类别,rails

Rails 4 在模型中添加新列或字段

Rails迁移以将主键添加到现有表

具有现有数据库的 Rails 一直说迁移未决

如何在 Rails 中为现有模型添加自动完成标记?