简体   繁体   English

Ruby on Rails的ActiveRecord与DBMS脚本

[英]Ruby on Rails's ActiveRecord vs DBMS scripts

I'm learning Ruby on Rails and following some guides to get to know the framework. 我正在学习Ruby on Rails,并遵循一些指南来了解该框架。

Currently I'm reading about ActiveRecord migrations and it seems to be very good to keep a track of changes during development phases, specially when you work with agile and requirements change often. 目前,我正在阅读有关ActiveRecord迁移的信息,在开发阶段特别是当您使用敏捷并且经常更改需求时,最好跟踪一下更改。

However, I believe that if you have fine-tune your DB, is preferable to work with vendor-specific scripts (MySQL, Postgres, etc.). 但是,我相信,如果您对数据库进行了微调,最好使用特定于供应商的脚本(MySQL,Postgres等)。

Needless to say, it's not my intention to make this post opinion-based, so my first question is if you know pros and cons about this approaches to build an application with Rails ( ActiveRecord migrations vs DBMS specific scripts). 不用说,我并不是要使这篇文章基于观点,所以我的第一个问题是,您是否了解使用Rails构建应用程序的这种方法的优缺点( ActiveRecord迁移与DBMS特定脚本)。 I've been searching on internet but I didn't found any comparison. 我一直在互联网上搜索,但没有找到任何比较。

Also, I'd like to know if there is any risk, something I should pay attention to, or avoid to do if I need to combine these 2 approaches. 另外,我想知道是否存在任何风险,应该注意的事情,或者如果需要将这两种方法结合使用,可以避免这样做。

Thanks in advance for your comments/answers. 预先感谢您的评论/答案。 Best regards 最好的祝福

TLDR: TLDR:

You don't want to use 3rd party tools to manually manage your database. 您不想使用第三方工具来手动管理数据库。 Try keeping your database code as close to Rails' migrations as possible. 尝试使您的数据库代码尽可能接近Rails的迁移。

The story 故事

Rails migrations are just great if you are doing a CRUD application where you don't need any complex logic on either app or database side. 如果您正在执行CRUD应用程序,而在应用程序或数据库端都不需要任何复杂的逻辑,那么Rails迁移就很棒。 This feature allows you to write incremental changes to your database and roll them back without disrupting your application in production. 此功能使您可以将增量更改写入数据库并回滚它们,而不会中断生产中的应用程序。 Say you have an "users" table and want to add a field, let's call it "second_email_address". 假设您有一个“用户”表,并想添加一个字段,我们称之为“ second_email_address”。 You would do this: 您可以这样做:

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 keeps track of your database schema "version" based on migration file names and can tell you exactly where you are and enables you to roll back anything you change your mind about. Rails会根据迁移文件名跟踪数据库模式的“版本”,并能准确告诉您您的位置,并使您可以回滚任何您改变主意的事情。 You can add or remove this column without losing any of your data in your table except if course the data stored in this column. 您可以添加或删除此列,而不会丢失表中的任何数据,除非存储在该列中的数据除外。 This is very nice for basic use cases. 这对于基本用例非常好。

Things tend to get a bit more complicated when you want to get a bit more personal with your database. 当您想使数据库更具个性时,事情会变得更加复杂。

Take triggers for example, and let's say you have a table "Shops" that has_many :users and you want to keep track of the user count in an integer column on the shops table. 以触​​发器为例,假设您有一个表“ shops”,具有has_many:users,并且您希望在shops表的整数列中跟踪用户计数。 The Rails convention over configuration paradigm dictates that you do something like this in your users model: Rails关于配置的约定范式要求您在用户模型中执行以下操作:

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

With a trigger, you'd do... 有了触发器,您就可以...

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;

The performance gain by using the trigger is astonishing. 使用触发器的性能提升是惊人的。 In most real life situations however you don't care about this and you're happy to trade off a few ms of latency for the convenience of having it all in the Rails application, the Rails way. 但是,在大多数现实生活中,您都不关心此事,并且乐于权衡几毫秒的延迟,以方便将其全部以Rails方式包含在Rails应用程序中。 But I guarantee you will change your mind when you have a few thousand shops with a few hundred thousand users each, and lots of other similar or even more complicated features within your application. 但是,我保证当您有数千家商店,每个商店有数十万用户,并且应用程序中有许多其他类似甚至更复杂的功能时,您会改变主意。 That's right, having a successful application means you'll have to get your hands dirty (and learn to ignore mean comments from people who will inform you you will boil in lava until the end of eternity for disrespecting the "Rails way", nts nts). 没错,申请成功意味着您必须动手(并学会忽略别人的卑鄙评论,这些人会通知您您将在熔岩中煮沸直到永恒结束,因为他们不尊重“铁路方式”,nts nts )。

In my application I have about 20K lines of SQL in triggers, procedures, scheduled events that were pulled out of the Rails application code base and moved to the database layer. 在我的应用程序中,触发器,过程,计划的事件中有大约2万行SQL,这些行从Rails应用程序代码库中移出并移至数据库层。 Sure, it adds a certain degree of complexity from several respects (writing the SQL, migrating, testing...) but at the end of the month the company pays $20K less in EC2 bills every month. 当然,它从多个方面(编写SQL,迁移,测试...)都增加了一定程度的复杂性,但是到月底,公司每月要少支付2万美元的EC2账单。

If you wanted to do something like this directly with phpmyadmin for instance you'd have to do every single operation manually, however as your application grows in complexity you'll get overwhelmed and this translates to support calls at midnight and downtime. 例如,如果您想直接使用phpmyadmin进行此类操作,则必须手动执行每个操作,但是随着应用程序复杂性的提高,您将不知所措,这转化为支持午夜和停机时间的呼叫。 Fortunately you can still use Rails migrations with triggers/stored procedures and so on. 幸运的是,您仍然可以将Rails迁移与触发器/存储过程等配合使用。

You can write your SQL in separate migration files following the example above. 您可以按照上面的示例在单独的迁移文件中编写SQL。 You can use a gem such as hair_trigger to define your triggers inside models and have them exported to schema.rb or structure.sql (although this doesn't cover and there seems to be no equivalent for procedures/functions and events). 您可以使用例如hair_trigger之类的gem在模型内定义触发器,并将其导出到schema.rb或structure.sql(尽管这并不涵盖,并且过程/函数和事件似乎没有等效项)。 You can also switch your schema to SQL and you get a big fat SQL file with all your code, that's a bit too much to manage. 您还可以将模式切换到SQL,并且所有代码都会得到一个庞大的SQL文件,这太麻烦了。

But since this is no longer about defining tables and columns, but more like application features, you can also have a sql/ directory inside your app/ directory and group your SQL code in there, for instance: 但是,由于这不再是要定义表和列,而是更像是应用程序功能,因此您还可以在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;

Then in a Rails migration file you do: 然后在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

Of course, when you have hundreds of these babies you want to have a discovery mechanism in place so you don't execute File.read... every trigger/procedure like a barbarian. 当然,当您有数百个这样的婴儿时,您希望有一个发现机制,这样您就不必执行File.read ...每个触发器/过程都像蛮子一样。

Bottom line: not bad at all! 底线:一点都不差! So Rails migrations enable you to do incremental changes to your databases and keep them all organized, versioned and easy to manage! 因此,Rails迁移使您能够对数据库进行增量更改,并使它们保持井井有条,版本化且易于管理!

But how about testing? 但是测试呢?

Well, once again you'd go a bit out of the nice paved road that is the Rails convention. 好吧,您会再一次脱离Rails约定的铺砌好的道路。 You'd test a model's behavior like this: 您可以像这样测试模型的行为:

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

Later edit: the user counter example above can of course be refactored using AR's counter caching feature, but hopefully it proves a point. 以后的编辑:上面的用户计数器示例当然可以使用AR的计数器缓存功能进行重构,但希望可以证明这一点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM