【问题标题】:Rails migration: only for schema change or also for updating data?Rails 迁移:仅用于模式更改还是用于更新数据?
【发布时间】:2013-10-15 17:33:15
【问题描述】:

我是一名初级 Rails 开发人员,在工作中我们遇到了以下问题:

只需要更新一条记录的列值。 我们所做的是创建这样的迁移:

class DisableAccessForUser < ActiveRecord::Migration
  def change
    User.where(name: "User").first.update_column(:access, false)
  end
end

迁移是否仅适用于架构更改?

您还有什么建议的解决方案?

PS:我只能用代码来改变它。无法访问控制台。

【问题讨论】:

  • 也许做一条特殊的路线,比如 /239fh2938fh239f8h2 ,在其中进行一次更改,部署,然后删除它并重新部署。
  • 创建一个rake任务怎么样?
  • 如果您无法访问生产控制台:尝试获得一份新工作,同时编写一个破坏很多内容的脚本并将其交给 OP 人员。跨度>

标签: ruby-on-rails migration


【解决方案1】:

简短的版本是,由于迁移仅用于架构更改,您不会希望使用它们来更改数据库中的实际数据。

主要问题是,如果其他开发人员使用rake db:schema:loadrake db:reset 加载数据库结构,您的数据操作迁移可能会被忽略。两者都只是使用 schema.rb 文件加载最新版本的结构,而不涉及迁移。

正如 Nikita Singh 在 cmets 中指出的那样,我也想说更改行数据的最佳方法是实现一个简单的 rake 任务,该任务可以根据需要运行,与迁移结构无关。或者,对于首次安装,seed.rb 文件非常适合加载初始系统数据。

希望漫谈有所帮助。

更新

在一些“官方”来源中找到了一些文档:

  • Rails Guide for Migrations - Using Models in your Migrations。本部分描述了迁移文件中的数据操作可能会给其他开发人员带来问题的场景。
  • Rails Guide for Migrations - Migrations and Seed Data。与上面相同的文档,并没有真正解释为什么在迁移中放置种子或数据操作是不好的,只是说将所有这些都放在 seed.rd 文件中。
  • This SO answer。这个人基本上说的和我上面写的一样,只是他们引用了Agile Web Development with Rails(第3版)一书,部分由David Heinemeier Hansson撰写, Rails 的创建者。我不会复制引用,因为您可以在该帖子中阅读它,但我相信它可以让您更好地了解为什么迁移中的种子或数据操作可能被视为一种不好的做法。

【讨论】:

  • 难道你不应该将 schema.rb 文件提交到代码存储库,这难道不是一个生成的文件吗? (顺便说一句,我同意不在迁移中播种的想法,但数据迁移对我来说似乎是另一回事。)
  • @bertbruynooghe 我曾经也出于同样的原因认为,但这个文件有点不同;您可能确实想提交schema.rb,以便您可以在任何其他环境(或同事)中使用db:schema:load 快速重建数据库结构,而不必担心运行每个该死的迁移文件的麻烦。我认为它还在文件中说“......强烈建议将其签入您的版本控制系统......”,所以我倾向于认为提交该文件被认为是最佳实践。
  • 所有引用的来源都建议在迁移中避免种子数据和模型 - 它们都不会阻止实际迁移您的数据。如果您需要更改表中某个列的所有实例的前缀以匹配您的新代码库,我还没有看到任何不使用迁移的好论据。设置 rake 任务来做到这一点只是重新发明轮子(或迁移过程,视情况而定)。
  • 诚然,我已经离开这个问题好几年了,rails 也是如此,但一般的想法是很好地将架构更改与数据更改分开,以便为您提供更多的灵活性。您可以轻松地“试运行” rake 任务,而无需实现特定的回滚逻辑,并且您可以选择是否将其包含在您的部署中。还有很多其他原因(也有很多支持迁移的原因);这是您在技术上“可以”做某事的情况之一,但这真的是一个好主意,还是适合给定的情况。我宁愿把它分开。
【解决方案2】:

迁移适用于架构更改。但是,当您处理大量协作项目时,例如每天从许多开发人员那里提取代码。

您可能会错过一些迁移(值更新迁移..架构更改没有问题)因为迁移取决于时间戳。

所以我们要做的是在单个命名空间中创建一个 rake 任务来更新一些表值(注意它不会覆盖)

每当我们从 Git 更新代码时,都会调用该 NameSpace 中的所有 rake 任务。

【讨论】:

    【解决方案3】:

    在迁移中使用类进行数据更改是危险的,因为它不能很好地证明未来。对类的更改很容易在将来破坏迁移。

    例如,假设您要向用户 (sample_group) 添加一个新列,并在对象加载时执行的 Rails 生命周期回调中访问该列(例如 after_initialize)。这将打破这种迁移。如果您没有跳过保存时的回调和验证(通过使用 update_column),那么以后还有更多方法可以打破这种迁移。

    当我想在迁移中更改数据时,我通常会使用 SQL。可以使用 execute() 方法在迁移中执行任何 SQL 语句。要使用的确切 SQL 取决于所使用的数据库,但您应该能够提出适当的 db 查询。例如在 MySQL 中,我相信以下应该可以工作:

     execute("UPDATE users SET access = 0 WHERE id IN (select id from users order by id limit 1);")
    

    这是更多的未来证明。

    【讨论】:

    • 看起来对我来说是一个虚假的论点。如果您应用 this article 中描述的技术并测试您的迁移,那么它们与任何其他代码一样具有未来的证明。
    • 实际上,他们不是。你没有读懂我的论点。如果您使用 after_initialize 引用在迁移到正在运行的未来的迁移中定义的列,即使调用重置列,它仍然会失败,因为该列在数据库中不存在正在运行迁移。
    • 我想我明白了你的意思:在迁移中使用 实际模型 类进行数据更改是危险的吗?我绝对同意这一点:您的迁移不应该引用任何以后可以更改的代码。但是,如果您按照前面的链接所述在迁移中创建虚拟类,我看不出会出现什么问题。此外,如果您测试迁移并使用 CI,您将在此问题发生时检测到它。
    • 您假设使用虚拟类就足够了。根据我在过去几年中为大约 10 位咨询客户和雇主解决此问题的经验,我从未发现过这种情况。根据我的经验,每当在实际迁移中使用模型类时,这是因为作者希望利用该模型的实际行为 - 使用现有方法/范围进行过滤或根据现有方法/范围进行数据库更改(并且通常是复杂的) ) 状态。如果您正在使用超出基本 AR 行为(位置等)的任何东西,那么虚拟类方法无法解决任何问题。
    • 附带说明,从开发团队的角度来看,这通常表现为迁移过程突然莫名其妙地失败 - 突然 'rake db:create db:migrate' 在中间失败该过程,通常在几个月前编写的迁移中。每个人都感到困惑,并且花费了大量的时间试图弄清楚这一点。新开发人员和承包商被告知克隆现有数据库,而不是处理有问题的迁移过程。很多喧嚣和醉酒,很容易避免。
    【解决方案4】:

    迁移只是一种结构化的方式来更改数据库,包括架构和数据。 在我看来,在某些情况下使用迁移来更改数据是合法的。

    例如:

    1. 如果您在数据库中保存的数据大部分是不变的,但每年都在变化,那么每年进行一次迁移以更新它是可以的。例如,如果您列出足球联赛中的球队,那么迁移将是每年更新当前球队的好方法。
    2. 如果您想批量更改大表的属性。例如,如果您的用户中有一个 slug 列,并且名称“some user”将被转换为 slug“some_user”,现在您想将其更改为“some.user”。这是我在迁移时要做的事情。

    话虽如此,我不会使用迁移来更改单个用户属性。如果这是偶尔发生的事情,您应该制作一个仪表板,以便您将来编辑此数据。否则,rake 任务可能是一个不错的选择。

    【讨论】:

    • 管理仪表板是一个非常整洁的东西。
    • 我不同意使用迁移更新数据。考虑一下……您使用 Rails 迁移更新 N 条记录,没有问题。稍后,当尝试在新服务上重新部署您的应用程序时,这需要您运行所有迁移。非架构变更迁移运行、崩溃,现在您正尝试调试一个您从未需要遇到的问题。我强烈主张将数据迁移置于 Rake 任务中。
    • 通常在从头开始部署应用程序时,您使用 rails 生成的 schema.rb 或 structure.sql 文件来设置数据库(如rake db:setup)。从未见过有人通过从头开始运行所有迁移来部署大型应用程序......
    【解决方案5】:

    在正确的情况下,如果操作正确,使用迁移来迁移数据库中的数据并没有错。

    在迁移过程中应该避免两件相关的事情(正如许多人所提到的那样),这两项都不排除迁移数据:

    1. 在迁移中使用模型是不安全的。 User 模型中的代码可能会改变,当这种情况发生时,没有人会更新你的迁移,所以如果某个同事休假 3 个月,回来,并尝试运行她发生的所有迁移消失了,但同时有人重命名了User 模型,您的迁移将被破坏,并阻止她追赶。这只是意味着您必须使用 SQL,或者(如果您决心保持与迁移实现无关)直接在迁移文件中包含 ActiveRecord 模型的独立副本(嵌套在迁移类下)。

    2. 对种子数据使用迁移也没有意义,特别是当有人第一次设置应用程序时用于填充新数据库的数据,因此应用程序将运行(或将在应用程序的全新实例中获得预期的数据)。您不能为此使用迁移,因为您在第一次设置数据库时不运行迁移,而是运行db:schema:load。因此用于维护种子数据的特殊文件:seeds.rb。这只是意味着,如果您确实需要在迁移中添加数据(以使生产和每个人的开发数据都加快速度),并且它有资格作为种子数据(应用程序运行所必需的) ,你也需要添加到seeds.rb

    然而,这些都不意味着您不应该使用迁移迁移现有数据库中的数据。这就是他们的目的。你应该使用它们!

    【讨论】:

      【解决方案6】:

      如果我没记错的话,更改特定记录可能会起作用,但我不确定。

      在任何情况下,这都不是一个好习惯,迁移应该仅由用户进行架构更改。 为了更新一条记录,我会使用控制台。只需在终端中输入“rails console”并输入代码即可更改属性。

      【讨论】:

      • 消息说“无法访问控制台”。
      【解决方案7】:

      这个问题很老了,我认为 Rails 方法随着时间的推移而改变。基于https://edgeguides.rubyonrails.org/active_record_migrations.html#migrations-and-seed-data,可以在这里为新列提供数据。更准确地说,您的 迁移 代码还应包含“向下”块:

      class DisableAccessForUser < ActiveRecord::Migration
        def up
          User.where(name: "User").first.update_column(:access, false)
        end
        
        def down
          User.where(name: "User").first.update_column(:access, true)
        end  
      end
      

      如果您使用 seeds.rb 预填充数据,也不要忘记在其中包含新列值:

      User.find_or_create_by(id: 0, name: 'User', access: false)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-09-10
        • 2010-12-30
        • 2013-07-27
        • 1970-01-01
        • 2014-03-08
        • 1970-01-01
        • 2019-07-14
        • 2012-08-08
        相关资源
        最近更新 更多