ChatGPT解决这个技术问题 Extra ChatGPT

如何在 Rails 迁移中将可为空的列更改为不可为空?

我在之前的迁移中创建了一个日期列,并将其设置为可为空。现在我想将其更改为不可为空。假设该数据库中有空行,我该怎么做?如果它们当前为空,我可以将这些列设置为 Time.now。


D
Dorian

如果你在迁移中这样做,那么你可能会这样做:

# Make sure no null value exist
MyModel.where(date_column: nil).update_all(date_column: Time.now)

# Change the column to not allow null
change_column :my_models, :date_column, :datetime, null: false

只是一个注释,因为这让我破坏了我的开发数据库。而是使用显式哈希语法,例如:MyModel.update_all({:date_column => Time.now}, {:date_column => nil})。您原始形式的查询只是使我所有的模型在该字段中都具有 nil 值。
您是否在此迁移中使用了“向上”/“向下”方法,或者您可以在迁移中使用简单的更改方法吗?
change 方法不太适合这种情况,因为 (1) update_all 方法将在迁移和潜在还原上执行。这可能不是最糟糕的事情,但因为 (2) 迁移无法知道列在潜在还原中的更改内容。所以对于这种情况,我会坚持使用 updown
您永远不应该使用模态本身在迁移中进行数据更改。例如,您可能会在以后的迁移中删除模型(本例中为 MyModel)。如果有人然后运行您的所有迁移,它将中断。而是直接使用 ActiveRecord::Base.connection.execute('your sql here') 执行 SQL 语句
对于任何感兴趣的人,my answer 展示了如何一步完成。
J
Joshua Pinter

在 Rails 4 中,这是一个更好的(DRYer)解决方案:

change_column_null :my_models, :date_column, false

要确保该列中不存在具有 NULL 值的记录,您可以传递第四个参数,这是用于具有 NULL 值的记录的默认值:

change_column_null :my_models, :date_column, false, Time.now

当表已经有空值时,这会导致问题。 See my answer
也可在 3.2 中使用。也有第 4 个参数用于设置默认值为 null。
change_column_null 加 1。然而,里克史密斯的上述评论指出了一个非常有效的案例。
更新以添加用于更新空值的查询。第 4 个参数(默认值)仅在您实际上也希望为将来的记录设置默认值时才有用。
实际上,根据 Rails 4.2 文档,第四个参数没有为未来的记录设置默认值:“该方法接受可选的第四个参数来替换现有的 +NULL+s 与其他值。请注意第四个参数没有设置列的默认值。”
R
Rick Smith

Rails 4(其他 Rails 4 答案有问题):

def change
  change_column_null(:users, :admin, false, <put a default value here> )
  # change_column(:users, :admin, :string, :default => "")
end

将其中包含 NULL 值的列更改为不允许 NULL 会导致问题。这正是可以在您的开发设置中正常工作的代码类型,然后在您尝试将其部署到您的 LIVE 生产环境时崩溃。您应该首先将 NULL 值更改为有效的值,然后 然后 不允许 NULL。 change_column_null 中的第 4 个值正是这样做的。有关详细信息,请参阅 documentation

此外,我通常更喜欢为该字段设置一个默认值,这样我就不需要在每次创建新对象时都指定该字段的值。我也包含了注释掉的代码来做到这一点。


对于 Rails 4,这似乎是最准确和最完整的答案,包括注释掉的默认设置。
如果您要向表中添加新列并希望为 null 插入新值,但不想为该列添加默认值,则可以在迁移中执行此操作:add_column :users, :admin, :string 然后 change_column_null(:admin, :string, false, "new_value_for_existing_records")
S
Soviut

创建具有 :default => 值的 change_column 语句的迁移。

change_column :my_table, :my_column, :integer, :default => 0, :null => false

请参阅:change_column

根据您可能需要使用的数据库引擎 change_column_null


这对我有用。在本地使用 MySql。当在 Heroku (Postgres) 中推送和运行应用程序时,当我将它写为 null 时,它会在不为 null 的列上出错——这是正确的。只有“change_column_null”可以工作,不能在 MySql 上使用“change_column ... :null => false”。谢谢。
那么你在 change_column_null 之后的迁移是什么
Postges 比 MySQL 更严格——我希望它需要 change_column_null
@rtfminc 我强烈建议您在开发和生产中使用相同的数据库引擎,因为它在涉及边缘情况时避免了很多问题。
J
James Chevalier

导轨 4:

def change
  change_column_null(:users, :admin, false )
end

请描述您的答案。
j
jmarceli

根据 docs,在 Rails 4.02+ 中,没有像 update_all 这样带有 2 个参数的方法。相反,可以使用以下代码:

# Make sure no null value exist
MyModel.where(date_column: nil).update_all(date_column: Time.now)

# Change the column to not allow null
change_column :my_models, :date_column, :datetime, null: false

D
Dorian

如果您有现有记录,则不能使用 add_timestamps 和 null:false ,因此这是解决方案:

def change
  add_timestamps(:buttons, null: true)

  Button.find_each { |b| b.update(created_at: Time.zone.now, updated_at: Time.zone.now) }

  change_column_null(:buttons, :created_at, false)
  change_column_null(:buttons, :updated_at, false)
end

M
MattD

根据 Strong Migrations gem,在生产中使用 change_column_null 是一个坏主意,因为它会在检查所有记录时阻止读取和写入。

处理这些迁移(特定于 Postgres)的推荐方法是将这个过程分成两个迁移。

一种使用约束更改表:

class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
  def change
    safety_assured do
      execute 'ALTER TABLE "users" ADD CONSTRAINT "users_some_column_null" CHECK ("some_column" IS NOT NULL) NOT VALID'
    end
  end
end

以及一个单独的迁移来验证它:

class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
  def change
    safety_assured do
      execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
    end
  end
end

上面的例子是从链接的文档中提取的(并略有改动)。显然对于 Postgres 12+,您还可以将 NOT NULL 添加到架构中,然后在运行验证后删除约束:

class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
  def change
    safety_assured do
      execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
    end

    # in Postgres 12+, you can then safely set NOT NULL on the column
    change_column_null :users, :some_column, false
    safety_assured do
      execute 'ALTER TABLE "users" DROP CONSTRAINT "users_some_column_null"'
    end
  end
end

自然,这意味着您的架构不会显示该列对于 Postgres 的早期版本是 NOT NULL,因此我还建议设置模型级别验证以要求该值存在(尽管我建议即使对于允许此步骤的 PG 版本)。

此外,在运行这些迁移之前,您需要使用 null 以外的值更新所有现有记录,并确保写入表的任何生产代码都没有为值写入 null