概述
在本文中,我们将讨论Ruby在Rauds应用程序中的数据库触发器的用法。我们将介绍它们的本质,为什么它们是必不可少的,他们解决的问题,他们的利弊,何时被允许使用,而当他们不使用时。此外,我们将提供三个简单的示例,说明如何在Rails应用程序中添加这些触发器。到本文结束时,您将对在Rails应用程序中使用DB触发器有更好的理解和更广泛的看法。
介绍
简单地说,数据库触发器是在表上执行插入,更新或删除操作时自动调用的函数。您可以将其视为在数据库级别执行的ActiverEcord回调。有时,它们甚至称为SQL回调。
用例
我们通常使用触发器来解决以下问题:
- 审计和记录
- 数据库架构进行了重构(非正式化,重命名列,将列分为两列等) )
- 填充摘要表
优点缺点
专利:
- 数据准确性和一致性
- 降低了应用程序代码的复杂性
cons:
- 当您使用触发器时,ActivereCord无法知道您的记录何时更改。这意味着activerecord对象将保留过时的数据,直到重新加载为止。
- 不要过分。将过多的业务逻辑委派给触发者可能会在将来造成问题(调试困难,测试挑战,绩效影响等) )
虽然触发器对于某些功能很有用,但必须明智地使用它们,并考虑在可能的情况下在应用程序代码中实施业务逻辑的替代方法。
示例1:在导轨控制台内添加扳机
在第一个示例中,我们将直接在导轨控制台中创建一个DB触发器,并观察其工作原理。让我们考虑两个数据库表animals
和removed_animals
,每个数据库表仅包含两个列-id
和name
。我们的目标是添加一个触发器,每次从主animals
桌子中删除记录时,将动物记录复制到removed_animals
表中。
让我们看看当前状态:
# rails c
Animal.create(name: 'Animal')
Animal.last.delete
RemovedAnimal.all
# => []
当前,removed_animals
表是空的,不包含任何删除的记录。
现在,让我们执行手动插入removed_animals
,以查看我们期望在触发中运行的内容:
# rails c
ActiveRecord::Base.connection.execute("INSERT INTO `removed_animals` (`id`, `name`) VALUES (1, 'animal_name')")
RemovedAnimal.all
# => [#<RemovedAnimal:0x000000010bc4be98 id: 1, name: "animal_name">]
如上所述,我们将记录手动插入removed_animals
。
现在,让我们实现触发器并检查其工作原理:
# rails c
ActiveRecord::Base.connection.execute(
"
CREATE TRIGGER save_removed_animal_trigger
AFTER DELETE ON animals
FOR EACH ROW
INSERT INTO `removed_animals` (`id`, `name`) VALUES (OLD.`id`, OLD.`name`)
"
)
在扳机到位,让我们进一步测试:
Animal.create(name: 'Animal')
Animal.create(name: 'Another Animal')
Animal.all
# => [#<Animal:0x000000010dc41f18 id: 10, name: "Animal">, #<Animal:0x000000010dc41e50 id: 11, name: "Another Animal">]
RemovedAnimal.all
# => []
Animal.destroy_all
Animal.all
# => []
RemovedAnimal.all
# => [#<RemovedAnimal:0x000000010de6b550 id: 10, name: "Animal">, #<RemovedAnimal:0x000000010de6b488 id: 11, name: "Another Animal">]
如上所示,我们所有被摧毁的动物记录都保存到了removed_animals
表。
现在,让我们放下此触发器并使用示例2中的正确导轨方法重新创建相同的逻辑:
# rails c
ActiveRecord::Base.connection.execute("DROP TRIGGER save_removed_animal_trigger")
# => nil
示例2:使用Hair_trigger Gem添加触发器
在第二个示例中,我们将使用hair_trigger Gem通过更清晰的方法实现相同的功能。让我们从安装开始:
# Gemfile
gem 'hairtrigger'
和运行:
bundle install
安装宝石后,我们可以在模型中声明触发器,并使用耙子任务自动生成适当的迁移:
# app/models/animal.rb
class Animal < ApplicationRecord
trigger.after(:delete) do
"INSERT INTO `removed_animals` (`id`, `name`) VALUES (OLD.`id`, OLD.`name`)"
end
end
要生成迁移,请运行以下命令:
rake db:generate_trigger_migration
此任务将为我们生成迁移文件:
# db/migrate/20230725122552_create_trigger_animals_delete.rb
# This migration was auto-generated via `rake db:generate_trigger_migration'.
# While you can edit this file, any changes you make to the definitions here
# will be undone by the next auto-generated trigger migration.
class CreateTriggerAnimalsDelete < ActiveRecord::Migration[7.0]
def up
create_trigger("animals_after_delete_row_tr", :generated => true, :compatibility => 1).
on("animals").
after(:delete) do
"INSERT INTO `removed_animals` (`id`, `name`) VALUES (OLD.`id`, OLD.`name`);"
end
end
def down
drop_trigger("animals_after_delete_row_tr", "animals", :generated => true)
end
end
现在,执行迁移:
rails db:migrate
并测试触发器是否按预期工作,类似于我们在示例1:
中所做的类似
# rails c
Animal.create(name: 'Animal')
Animal.create(name: 'Another Animal')
Animal.all
# => [#<Animal:0x000000010bbb1730 id: 12, name: "Animal">, #<Animal:0x000000010bbb15c8 id: 13, name: "Another Animal">]
RemovedAnimal.all
# => []
Animal.destroy_all
Animal.all
# => []
RemovedAnimal.all
# => [#<RemovedAnimal:0x000000010bd81498 id: 12, name: "Animal">, #<RemovedAnimal:0x000000010bd813d0 id: 13, name: "Another Animal">]
您可以看到,一切都按预期工作。
示例3:添加触发器以支持列重命名
在第三个示例中,我们将添加一个触发器,使我们可以重命名为零停机时间的列。为了实现这一目标,我们将遵循以下步骤:
- 添加一个新列。
- 将触发器添加到两列的双写。
- 回填新列,上面有旧列值的副本。
- 在整个应用程序中开始使用新列。
- 放下旧列。
目前,我们专注于步骤2-创建一个新的触发器,同时写入两个列。让我们检查一下我们要实现的目标。假设我们有一个带有两个列的animals
表 - > id
和name
,例如,我们决定将name
列重命名为full_name
。因此,我们需要添加此新列并观察当前行为:
# rails c
Animal.create(name: 'Animal')
Animal.last
# => #<Animal:0x000000010e823980 id: 14, name: "Animal", full_name: nil>
Animal.last.update(name: 'Updated Animal')
Animal.last
# => #<Animal:0x000000010e9f9d90 id: 14, name: "Updated Animal", full_name: nil>
如上所述,当我们创建或更新动物名称时,full_name
列保持空白。但是,如果我们用full_name
替换name
列,则所有值都应同步。这是触发器可以极有帮助的地方:
# app/models/animal.rb
class Animal < ApplicationRecord
trigger.before(:insert) do
"SET NEW.full_name = NEW.name;"
end
trigger.before(:update).of(:name) do
"SET NEW.full_name = NEW.name;"
end
end
将触发器添加到模型后,我们需要运行以下命令来生成迁移:
rake db:generate_trigger_migration
此耙任务为我们生成迁移文件:
# db/migrate/20230725154353_create_triggers_animals_insert_or_animals_update.rb
# This migration was auto-generated via `rake db:generate_trigger_migration'.
# While you can edit this file, any changes you make to the definitions here
# will be undone by the next auto-generated trigger migration.
class CreateTriggersAnimalsInsertOrAnimalsUpdate < ActiveRecord::Migration[7.0]
def up
create_trigger("animals_before_insert_row_tr", :generated => true, :compatibility => 1).
on("animals").
before(:insert) do
"SET NEW.full_name = NEW.name;"
end
create_trigger("animals_before_update_of_name_row_tr", :generated => true, :compatibility => 1).
on("animals").
before(:update).
of(:name) do
"SET NEW.full_name = NEW.name;"
end
end
def down
drop_trigger("animals_before_insert_row_tr", "animals", :generated => true)
drop_trigger("animals_before_update_of_name_row_tr", "animals", :generated => true)
end
end
让我们运行此迁移:
rails db:migrate
并检查是否有效:
# rails c
animal = Animal.create(name: 'Animal')
# => #<Animal:0x000000010e5723f8 id: 21, name: "Animal", full_name: nil>
animal.reload
# => #<Animal:0x000000010e5723f8 id: 21, name: "Animal", full_name: "Animal">
animal.update(name: 'New Name')
animal
# => #<Animal:0x000000010e5723f8 id: 21, name: "New Name", full_name: "Animal">
您可以看到,一切都按预期工作,所有名称值都重复到full_name
列中。
结论
在本文中,我们探讨了将触发器添加到Rails应用程序中的三个示例。从在轨道控制台中创建简单的触发器到使用hair_trigger
宝石以进行更清晰的方法。触发器提供数据的准确性和自动化,但应明智地使用以避免潜在的挑战。