首页 > 解决方案 > Ruby on Rails 的 ActiveRecord 与 DBMS 脚本

问题描述

我正在学习 Ruby on Rails 并遵循一些指南来了解该框架。

目前我正在阅读有关ActiveRecord迁移的内容,在开发阶段跟踪更改似乎非常好,特别是当您使用敏捷并且需求经常更改时。

但是,我相信如果您对数据库进行了微调,最好使用供应商特定的脚本(MySQL、Postgres 等)。

不用说,我无意让这篇文章基于意见,所以我的第一个问题是你是否知道这种使用 Rails 构建应用程序的方法的优缺点(ActiveRecord迁移与 DBMS 特定脚本)。我一直在互联网上搜索,但我没有找到任何比较。

另外,如果我需要结合这两种方法,我想知道是否有任何风险,我应该注意或避免做的事情。

提前感谢您的评论/回答。此致

标签: ruby-on-rails

解决方案


TLDR:

您不想使用 3rd 方工具来手动管理您的数据库。尝试使您的数据库代码尽可能接近 Rails 的迁移。

故事

如果您正在做一个 CRUD 应用程序,而您在应用程序或数据库端都不需要任何复杂的逻辑,Rails 迁移就非常棒。此功能允许您将增量更改写入数据库并回滚,而不会中断生产中的应用程序。假设您有一个“用户”表并想要添加一个字段,我们称之为“second_email_address”。你会这样做:

class AddSecondEmailAddressToUsers < ActiveRecord::Migration[5.2]

  def self.up
    add_column :users, :second_email_address, :string
  end

  def self.down
    remove_column :users, :second_email_address, :string
  end

end

Rails 会根据迁移文件名跟踪您的数据库模式“版本”,并可以准确地告诉您您的位置,并使您能够回滚您改变主意的任何内容。您可以添加或删除此列而不会丢失表中的任何数据,除非数据当然存储在此列中。这对于基本用例来说非常好。

当您想对数据库更加个性化时,事情往往会变得更加复杂。

以触​​发器为例,假设您有一个表“Shops”,它有一个 has_many :users,并且您想在 shop 表的整数列中跟踪用户计数。Rails 约定优于配置范式要求您在用户模型中执行以下操作:

after_create :increase_shop_user_count
def increase_shop_user_count
  self.shop.user_count+=1
end

用触发器,你会做...

create or replace trigger increase_shop_user_count
  after insert on users
  for each row
  begin
    update shops set user_count = user_count + 1 where shops.id = NEW.shop_id
  end;

使用触发器的性能提升是惊人的。然而,在大多数现实生活中,您并不关心这一点,您很乐意牺牲几毫秒的延迟,以方便将其全部放在 Rails 应用程序中,即 Rails 方式。但我保证,当你有几千家商店,每家都有几十万用户,并且你的应用程序中有许多其他类似甚至更复杂的功能时,你会改变主意。没错,成功的申请意味着你必须弄脏你的手(并学会忽略那些会告诉你的人的卑鄙评论,因为不尊重“Rails 方式”,你将在熔岩中沸腾直到永恒结束,nts nts )。

在我的应用程序中,我有大约 20K 行的 SQL 触发器、过程、计划事件,这些都是从 Rails 应用程序代码库中提取并移至数据库层的。当然,它从几个方面(编写 SQL、迁移、测试......)增加了一定程度的复杂性,但到月底,公司每月在 EC2 账单上少付 2 万美元。

例如,如果您想直接使用 phpmyadmin 执行此类操作,则必须手动执行每个操作,但是随着应用程序复杂性的增加,您将不堪重负,这会转化为在午夜和停机时间的支持调用。幸运的是,您仍然可以使用带有触发器/存储过程等的 Rails 迁移。

您可以按照上面的示例将 SQL 写入单独的迁移文件中。您可以使用诸如 hair_trigger 之类的 gem 来定义模型内的触发器并将它们导出到 schema.rb 或 structure.sql(尽管这不包括并且似乎没有过程/函数和事件的等效项)。您还可以将架构切换到 SQL,然后您会得到一个包含所有代码的大 SQL 文件,这有点难以管理。

但是由于这不再是定义表和列,而更像是应用程序功能,因此您还可以在 app/ 目录中拥有一个 sql/ 目录,并将您的 SQL 代码分组在其中,例如:

# app/sql/mysql/triggers/increase_shop_user_count.sql
    create or replace trigger increase_shop_user_count
      after insert on users
      for each row
      begin
        update shops set user_count = user_count + 1 where shops.id = NEW.shop_id
      end;

然后在 Rails 迁移文件中执行以下操作:

class IncreaseShopUserCountTrigger < ActiveRecord::Migration[5.2]
  def self.up
    execute File.read( Rails.root.join("app","sql","mysql","triggers","increase_shop_user_count.sql"))
  end
  def self.down
    execute "drop trigger increase_shop_user_count"
  end
end

当然,当你有数百个这样的婴儿时,你希望有一个发现机制,这样你就不会像野蛮人一样执行 File.read... 每个触发器/程序。

底线:一点也不差!因此,Rails 迁移使您能够对数据库进行增量更改,并使它们保持井井有条、版本化且易于管理!

但是测试呢?

好吧,你又一次偏离了 Rails 约定的漂亮铺砌的道路。您将像这样测试模型的行为:

it "changes shop user count after creation" do
  s = create :shop
  u = create :user, shop: s
  expect(s.reload.user_count).to eq 1 # because our database-side magic changed the count!
end

稍后编辑:上面的用户计数器示例当然可以使用 AR 的计数器缓存功能重构,但希望它证明了一点。


推荐阅读