ruby-on-rails - Ruby on Rails 的 ActiveRecord 与 DBMS 脚本
问题描述
我正在学习 Ruby on Rails 并遵循一些指南来了解该框架。
目前我正在阅读有关ActiveRecord
迁移的内容,在开发阶段跟踪更改似乎非常好,特别是当您使用敏捷并且需求经常更改时。
但是,我相信如果您对数据库进行了微调,最好使用供应商特定的脚本(MySQL、Postgres 等)。
不用说,我无意让这篇文章基于意见,所以我的第一个问题是你是否知道这种使用 Rails 构建应用程序的方法的优缺点(ActiveRecord
迁移与 DBMS 特定脚本)。我一直在互联网上搜索,但我没有找到任何比较。
另外,如果我需要结合这两种方法,我想知道是否有任何风险,我应该注意或避免做的事情。
提前感谢您的评论/回答。此致
解决方案
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 的计数器缓存功能重构,但希望它证明了一点。
推荐阅读
- yocto - 更新 bitbake 文件后,旧版本的库包含在图像中
- go - 像'\u7cfb'这样的多个unicode与普通字符串混合,如何转换为普通文本
- node.js - 针对意外错误的 Angular 常规错误处理
- sql - 包含子组的总计
- python - 使用python在多行中查找交点
- sql-server - SQLEXPRESS 连接字符串失败:错误 4060
- c# - 实体框架:外国实体不要求外国实体实际存在实体存在
- javascript - 使用动态键名设置对象状态的最佳方法?- 反应js
- python - 将axes.table多索引划分为不同的列
- android - 模拟和存根 System.getProperty ,Mockito 无法模拟/监视,因为“最终”类