Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

RubyonRailsアプリケーションでstructure.sqlを使用することの長所と短所

今日の投稿では、structure.sqlを使用することの重要な違いと利点について説明します。 対デフォルトのschema.rb RubyonRailsアプリケーションのスキーマ形式。データ主導の世界では、データベースの豊富な機能をすべて活用する方法を知っていると、成功する企業と失敗する企業の違いが生まれます。

2つの形式の主な違いを理解した後、structure.sqlに切り替える方法の概要を説明します。 また、データの整合性と、他の方法では保持できない可能性のあるデータベース機能の確保にどのように役立つかを示します。

投稿では、structure.sqlを利用するRailsアプリの例を紹介します。 PostgreSQLデータベースを使用しますが、基礎となる概念を他のデータベースに置き換えることもできます。信頼できるデータベースがなければ、実際のWebアプリケーションは完全ではありません。

面倒なことはせずに、すぐに飛び込みましょう!

schema.rbとstructure.sqlの違い

Ruby on Railsプロジェクトを開始するときに最初に行う必要があることの1つは、データベースの移行を実行することです。たとえば、ユーザーモデルを生成する場合、Railsは必然的に移行を実行するように要求します。これにより、schema.rbが作成されます。 それに応じてファイルする:

rails g model User first_name:string last_name:string

Railsは次の移行を生成します:

class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      t.string :first_name
      t.string :last_name
 
      t.timestamps
    end
  end
end

移行が実行されると、Railsがschema.rbを生成したことがわかります。 あなたのためのファイル:

ActiveRecord::Schema.define(version: 2019_12_14_074018) do
 
  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"
 
  create_table "users", force: :cascade do |t|
    t.string "first_name"
    t.string "last_name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end
 
end

このschema.rb ファイルは、比較的基本的なアプリケーションやユースケースに最適です。

ここで注意すべき主な点が2つあります:

  1. これはデータベースのRuby表現です。 schema.rb データベースを検査し、Rubyを使用してその構造を表現することによって作成されます。
  2. データベースに依存しません(つまり、SQLite、PostgreSQL、MySQL、またはRailsがサポートするその他のデータベースを使用するかどうかに関係なく、構文と構造はほぼ同じです)

ただし、この戦略が成長するアプリにとって制限が厳しすぎる時期が来るかもしれません。

たとえば、数百または数千の移行ファイルがあるとします。

新しい本番システムを迅速に起動する必要がある場合は、それらすべてを順番に実行するのに時間がかかりすぎるというシナリオに遭遇する可能性があります。または、一部の移行に、古いバージョンのデータベースで実行することを目的としたコードが含まれているが、現在のバージョンでは実行できないという状況に直面する可能性があります。移行が無効になった特定のデータの仮定で作成された場合、移行が失敗する可能性があります。

これらのシナリオはすべて、単純なrails db:create db:migrateを使用して、アプリケーションの新しいインスタンスを効率的にセットアップすることを妨げます。 指図。この場合、正しいデータベーススキーマをどのように理解しますか?

確かに、1つの方法は、戻ってすべての壊れた移行を修正することです。それは決して悪い考えではありません!

戻って多数の移行を修正するのにコストがかかりすぎる場合は、rails db:setupを実行する別の方法があります。 仕事。このタスクは、schema.rbからデータベーススキーマを生成します ファイル。ただし、データベースにschema.rbで表されていない複雑なロジックが含まれている場合はどうなりますか。 データベースの表現?

幸い、Railsは代替手段を提供しています:structure.sql

structure.sql schema.rbとは異なります 次の方法で:

  • データベース構造の正確なコピーが可能になります。これは、チームで作業する場合や、rails db:setupから本番環境で新しいデータベースを迅速に生成する必要がある場合に重要です。 タスク。
  • 高度なデータベース機能の情報を保存できます。たとえば、PostgreSQLを使用している場合は、ビュー、マテリアライズドビュー、関数、制約などを使用できます。

アプリケーションが特定の成熟度レベルに達すると、効率を高め、データの正確性を維持し、非常に高速なパフォーマンスを確保するために、本のあらゆるトリックを使用する必要があります。 structure.sqlを使用する Railsデータベースの動作を管理することで、ユーザーはそれを行うことができます。

schema.rbからの切り替え structure.sql

schema.rbから変更を加える structure.sqlへ 比較的簡単なプロセスです。 config/application.rbに次の行を設定するだけです。 :

module YourApp
  class Application < Rails::Application
    config.load_defaults 6.0
 
    # Add this line:
    config.active_record.schema_format = :sql
  end
end

次に、rails db:migrateを実行します db/structure.sqlにファイルが表示されます。 。 Voilà! Railsは、使用しているデータベースに固有のツールを使用してデータベース構造をダンプします(PostgreSQLの場合、そのツールはpg_dumpです。 、MySQLまたはMariaDBの場合、SHOW CREATE TABLEの出力が含まれます テーブルごとなど)。チームの他のメンバーが同じデータベース構造を持つように、このファイルがバージョン管理下にあることを確認することをお勧めします。

そのファイルを一目見ただけでは気が遠くなるかもしれません:schema.rb ファイルはわずか25行でしたが、structure.sql ファイルはなんと109行です!このような大きなファイルがアプリ開発にどのようなメリットをもたらす可能性がありますか?

データベースレベルの制約の追加

ActiveRecordは、Railsを使用する上で私のお気に入りの部分の1つです。これにより、ほとんど話されている言語のように、自然に感じる方法でデータベースにクエリを実行できます。たとえば、Danという名前の会社のすべてのユーザーを検索する場合、ActiveRecordを使用すると、次のようなクエリを実行できます。

company = Company.find(name: 'Some Company')
 
# Reads just like in a natural language!
company.users.where(first_name: 'Dan')

ただし、ActiveRecordが不足する場合があります。たとえば、ユーザーモデルに対して次の検証があるとします。

class User < ApplicationRecord
  validate :name_cannot_start_with_d
 
  private
 
  def name_cannot_start_with_d
    if first_name.present? && first_name[0].downcase == 'd'
      errors.add(:first_name, "cannot start with the letter 'D'")
    end
  end
end

「Dan」という名前のユーザーを作成しようとすると、検証の実行時にエラーが表示されます。

User.create!(first_name: 'Dan')
Traceback (most recent call last):
ActiveRecord::RecordInvalid (Validation failed: First name cannot start with the letter 'D')

これは問題ありませんが、あなたまたはあなたのチームメンバーの1人がActiveRecordの検証をバイパスしてデータを変更したとします:

u = User.create(first_name: 'Pan')
 
# The update_attribute method bypasses ActiveRecord validations
u.update_attribute :first_name, 'Dan'
u.first_name
=> "Dan"

示されているように、検証をバイパスするのは非常に簡単です。

これは、アプリケーションに悲惨な結果をもたらす可能性があります。 ActiveRecordは、祝福であると同時に呪いでもあります。非常にクリーンで自然なDSLを備えているため、作業が楽しくなりますが、モデルレベルの検証を実施する場合は、許容範囲が広すぎることがよくあります。ご存知かもしれませんが、解決策はデータベースレベルの制約を追加することです。

rails g migration AddFirstNameConstraintToUser

これにより、文字「D」で始まる名を禁止するロジックで編集できるファイルが生成されます:

class AddFirstNameConstraintToUser < ActiveRecord::Migration[6.0]
  def up
    execute "ALTER TABLE users ADD CONSTRAINT name_cannot_start_with_d CHECK (first_name !~* '^d')"
  end
 
  def down
    execute "ALTER TABLE users DROP CONSTRAINT IF EXISTS name_cannot_start_with_d"
  end
end

非常にであることに注意してください 移行を正常に元に戻すコードを追加することが重要です。上記の例では、upがあります およびdown ディレクティブ。 up メソッドは、移行の実行時に実行されます。down 移行がロールバックされると実行されます。データベース構造を適切に元に戻さないと、後で手動でハウスクリーニングを行う必要がある場合があります。 upの両方で実行できる移行ファイルを常に用意することをお勧めします およびdown 将来の頭痛を避けるため。

次に、移行を実行して、その制約をバイパスできるかどうかを確認します。

rails db:migrate
user = User.create first_name: 'Pan'
user.update_attribute :first_name, 'Dan'
 
ActiveRecord::StatementInvalid (PG::CheckViolation: ERROR:  new row for relation "users" violates check constraint "name_cannot_start_with_d")
DETAIL:  Failing row contains (2, Dan, null, 2019-12-14 09:40:11.809358, 2019-12-14 09:40:41.658974).

完全!制約は意図したとおりに機能しています。何らかの理由でActiveRecordの検証をバイパスした場合でも、データの整合性を維持するためにデータベース⁠(最終的なゴールキーパー⁠)に依存することができます。

これはstructure.sqlと何の関係がありますか ?

見てみると、以下が追加されていることがわかります。

CREATE TABLE public.users (
    id bigint NOT NULL,
    first_name character varying,
    last_name character varying,
    created_at timestamp(6) without time zone NOT NULL,
    updated_at timestamp(6) without time zone NOT NULL,
    CONSTRAINT name_cannot_start_with_d CHECK (((first_name)::text !~* '^d'::text)));

制約はスキーマ自体の中にあります!

schema.rb データベースレベルの制約もサポートします。トリガー、シーケンス、ストアドプロシージャ、チェック制約など、データベースがサポートする可能性のあるすべてを表現しているわけではないことを覚えておくことが重要です。たとえば、これは、まったく同じ移行(AddFirstNameConstraintToUser)でスキーマファイルに発生することです。 )schema.rbを使用するだけの場合 :

ActiveRecord::Schema.define(version: 2019_12_14_074018) do
 
  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"
 
  create_table "users", force: :cascade do |t|
    t.string "first_name"
    t.string "last_name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end
 
end

ファイルは変更されていません!制約は追加されませんでした。

プロジェクトに取り組むために新しい開発者を採用する場合は、さまざまなデータベース規制の下で運用している可能性があります。

structure.sqlのコミット バージョン管理を行うと、チームが同じページにいることを確認できます。 rails db:setupを実行する場合 structure.sqlを持っている ファイルの場合、データベースの構造には上記の制約が含まれます。 schema.rbを使用 そのような保証はありません。

生産システムについても同じことが言えます。新しいデータベースを使用してアプリケーションの新しいインスタンスを迅速に起動する必要があり、すべての移行を順番に実行するのに長い時間がかかる場合は、structure.sqlからデータベースを設定します。 ファイルははるかに速くなります。 structure.sqlは、安心できます。 他のインスタンスとまったく同じ構造でデータベースを作成します。

成長痛

簡潔なschema.rbの管理 チーム全体のファイルは、冗長なstructure.sqlを管理するよりもはるかに簡単なタスクです。 ファイル。

structure.sqlに移行する際の最大の問題の1つ 必要な変更のみがそのファイルにコミットされるようにすることです。これは、実行が難しい場合があります。

たとえば、誰かのブランチをプルして、そのブランチに固有の移行を実行するとします。 structure.sql いくつかの変更が含まれるようになります。次に、独自のブランチでの作業に戻り、新しい移行を生成します。 structure.sql これで、ファイルには自分のブランチと他のブランチの両方の変更が含まれます。これは対処するのに少し面倒な場合があり、これらの競合の管理に関しては間違いなく少しの学習曲線があります。

このアプローチを使用することで、トレードオフが発生します。データベースの高度な機能を維持できるように、コードの複雑さを事前に処理する必要があります。次に、より単純なスキーマ表現を処理する必要があります。また、データベースのすべての機能をすぐに利用できるわけではありません。 db:setupからバックアップを設定する場合 仕事。本番システムで破損したデータや正しくないデータを修正したり、データベースが提供するすべての高度な機能を利用できなくなったりするよりも、バージョン管理の面倒な作業に我慢するのが最善だと思います。

一般的に言って、structure.sqlを確実にするために使用した戦略は2つあります。 ファイルには、特定のブランチに必要な変更のみが含まれています:

  • 移行を含むブランチでの作業が完了したら、必ずrails db:rollback STEP=nを実行してください。 ここで、n そのブランチでの移行の数です。これにより、データベース構造が元の状態に確実に戻ります。
  • ブランチで作業した後、ロールバックを忘れる可能性があります。その場合、新しいブランチで作業するときは、元のstructure.sqlをプルするようにしてください。 新しい移行を作成する前に、マスターからファイルを作成します。

経験則として、structure.sql ファイルには、マスターにマージする前に、ブランチに関連する変更のみを含める必要があります。

結論

一般的に、Railsアプリケーションが小さい場合、またはデータベースが提供するより高度な機能の一部を必要としない場合は、schema.rbを使用しても安全です。 、非常に読みやすく、簡潔で、管理が簡単です。

ただし、アプリケーションのサイズと複雑さが増すにつれて、データベース構造を正確に反映することが重要になります。これにより、チームは、他の方法では不可能だった適切な制約、データベースモジュール、関数、および演算子を維持できます。手入れの行き届いたstructure.sqlでRailsの使い方を学ぶ ファイルは、より単純なschema.rbよりも優れたエッジを提供します 単純にできません。

P.S。 Ruby Magicの投稿をマスコミから離れたらすぐに読みたい場合は、Ruby Magicニュースレターを購読して、投稿を1つも見逃さないでください。


  1. AWS SQLネイティブデータベースのバックアップ、復元、監視

    データベースのバックアップは、データベースの動作状態、アーキテクチャ、および保存されたデータをバックアップするときに行われます。プライマリデータベースがクラッシュしたり、破損したり、失われたりした場合に備えて、データベースの複製インスタンスまたはコピーを作成できます。 このブログ投稿では、Amazon®Web Services(AWS)リレーショナルデータベースサービス(RDS)でのSQLネイティブデータベースのバックアップ、復元、および監視について説明しています。 AWSRDSでのSQLネイティブフルバックアップ AWSでは、AWSRDSSQLインスタンスのSQLネイティブデータベースの

  2. Database-as-a-Serviceの長所と短所

    元々は2017年12月7日にObjectRocket.com/blogで公開されました 多くの企業は、特定の機能を外部委託するか、社内で行うかを決定する際に躊躇します。 Database-as-a-Service(DBaaS)を検討することは、何をすべきかを決定するのに問題が生じる可能性がある状況の1つです。 多くの企業(特に技術系企業)が最初に直面する問題は、特定の機能を外部委託するのか、それとも社内で行うのかということです。自分の乗組員を雇ったり、他の会社にお金を払って物事を成し遂げるのは費用がかかるように思われるため、この決定を下すのは難しい場合があります。 ただし、データベー