Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add foreign keys #1646

Merged
merged 6 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contrib/dres_rails/spec/features/expose_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
end

before(:each) do
::ActiveRecord::Base.connection.execute("TRUNCATE event_store_events")
::ActiveRecord::Base.connection.execute("TRUNCATE event_store_events_in_streams")
::ActiveRecord::Base.connection.execute("DELETE FROM event_store_events")
end

let(:repository) { RubyEventStore::ActiveRecord::PgLinearizedEventRepository.new(serializer: YAML) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
require "spec_helper"
require "pp"
require_relative "../../support/helpers/silence_stdout"

module RailsEventStore
::RSpec.describe RubyEventStore::ActiveRecord::RailsForeignKeyOnEventIdMigrationGenerator do
around { |example| SilenceStdout.silence_stdout { example.run } }

around do |example|
begin
@dir = Dir.mktmpdir(nil, "./")
example.call
ensure
FileUtils.rm_r(@dir)
end
end

before { allow(Time).to receive(:now).and_return(Time.new(2016, 8, 9, 22, 22, 22)) }

subject do
generate_migration
File.read("#{@dir}/db/migrate/20160809222222_add_foreign_key_on_event_id_to_event_store_events_in_streams.rb")
end

it "uses particular migration version" do
expect(subject).to include("ActiveRecord::Migration[#{::ActiveRecord::Migration.current_version}]")
end

it "uses particular migration version for rails 6.0" do
skip unless ENV["BUNDLE_GEMFILE"].include?("rails_6_0")
expect(subject).to include("ActiveRecord::Migration[6.0]")
end

it "uses particular migration version for rails 6.1" do
skip unless ENV["BUNDLE_GEMFILE"].include?("rails_6_1")
expect(subject).to include("ActiveRecord::Migration[6.1]")
end

context "when postgresql adapter is used" do
before { allow(::ActiveRecord::Base).to receive(:connection).and_return(double(adapter_name: 'postgresql')) }

specify "should do migration in two steps" do
generate_migration
expect(second_step_migration_exists?(@dir)).to be_truthy
expect(generated_files_count(@dir)).to eq(2)
end
end

%w[mysql2 sqlite].each do |adapter|
context "when #{adapter} adapter is used" do
before { allow(::ActiveRecord::Base).to receive(:connection).and_return(double(adapter_name: adapter)) }
specify "should do migration in single step" do
generate_migration
expect(second_step_migration_exists?(@dir)).to be_falsey
expect(generated_files_count(@dir)).to eq(1)
end
end
end

def generate_migration
RubyEventStore::ActiveRecord::RailsForeignKeyOnEventIdMigrationGenerator.start([], destination_root: @dir)
end

def second_step_migration_exists?(dir)
File.exist?("#{dir}/db/migrate/20160809222222_validate_add_foreign_key_on_event_id_to_event_store_events_in_streams.rb")
end

def generated_files_count(dir)
Dir[File.join(dir, 'db/migrate', '*')].length
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
require_relative "active_record/generators/migration_generator"
require_relative "active_record/generators/rails_migration_generator"
require_relative "active_record/generators/templates/template_directory"
require_relative "active_record/generators/verify_adapter"
require_relative "active_record/generators/verify_data_type_for_adapter"
require_relative "active_record/generators/event_id_index_migration_generator"
require_relative "active_record/generators/rails_event_id_index_migration_generator"
require_relative "active_record/generators/foreign_key_on_event_id_migration_generator"
require_relative "active_record/generators/rails_foreign_key_on_event_id_migration_generator"
require_relative "active_record/event"
require_relative "active_record/with_default_models"
require_relative "active_record/with_abstract_base_class"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true

module RubyEventStore
module ActiveRecord
class ForeignKeyOnEventIdMigrationGenerator
def call(database_adapter, migration_path)
each_migration(database_adapter) do |migration_name|
path = build_path(migration_path, migration_name)
write_to_file(path, migration_code(database_adapter, migration_name))
end
end

private

def each_migration(database_adapter, &block)
case database_adapter
when 'postgresql'
[
'add_foreign_key_on_event_id_to_event_store_events_in_streams',
'validate_add_foreign_key_on_event_id_to_event_store_events_in_streams'
]
else
['add_foreign_key_on_event_id_to_event_store_events_in_streams']
end.each(&block)
end

def absolute_path(path)
File.expand_path(path, __dir__)
end

def migration_code(database_adapter, migration_name)
migration_template(template_root(database_adapter), migration_name).result_with_hash(migration_version: migration_version)
end

def migration_template(template_root, name)
ERB.new(File.read(File.join(template_root, "#{name}_template.erb")))
end

def template_root(database_adapter)
absolute_path("./templates/#{template_directory(database_adapter)}")
end

def template_directory(database_adapter)
TemplateDirectory.for_adapter(database_adapter)
end

def migration_version
::ActiveRecord::Migration.current_version
end

def timestamp
Time.now.strftime("%Y%m%d%H%M%S")
end

def write_to_file(path, migration_code)
File.write(path, migration_code)
end

def build_path(migration_path, migration_name)
File.join("#{migration_path}", "#{timestamp}_#{migration_name}.rb")
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

begin
require "rails/generators"
rescue LoadError
end

if defined?(Rails::Generators::Base)
module RubyEventStore
module ActiveRecord
class RailsForeignKeyOnEventIdMigrationGenerator < Rails::Generators::Base
class Error < Thor::Error
end

namespace "rails_event_store_active_record:migration_for_foreign_key_on_event_id"

source_root File.expand_path(File.join(File.dirname(__FILE__), "../generators/templates"))

def initialize(*args)
super

VerifyAdapter.new.call(adapter)
rescue UnsupportedAdapter => e
raise Error, e.message
end

def create_migration
case adapter
when 'postgresql'
template "#{template_directory}add_foreign_key_on_event_id_to_event_store_events_in_streams_template.erb",
"db/migrate/#{timestamp}_add_foreign_key_on_event_id_to_event_store_events_in_streams.rb"
template "#{template_directory}validate_add_foreign_key_on_event_id_to_event_store_events_in_streams_template.erb",
"db/migrate/#{timestamp}_validate_add_foreign_key_on_event_id_to_event_store_events_in_streams.rb"
else
template "#{template_directory}add_foreign_key_on_event_id_to_event_store_events_in_streams_template.erb",
"db/migrate/#{timestamp}_add_foreign_key_on_event_id_to_event_store_events_in_streams.rb"
end
end

private

def adapter
::ActiveRecord::Base.connection.adapter_name.downcase
end

def migration_version
::ActiveRecord::Migration.current_version
end

def timestamp
Time.now.strftime("%Y%m%d%H%M%S")
end

def template_directory
TemplateDirectory.for_adapter(adapter)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddForeginKeyOnEventIdToEventStoreEventsInStreams < ActiveRecord::Migration[<%= migration_version %>]
def change
add_foreign_key :event_store_events_in_streams, :event_store_events, column: :event_id, primary_key: :event_id, if_not_exists: true
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ class CreateEventStoreEvents < ActiveRecord::Migration[<%= migration_version %>]
t.datetime :created_at, null: false, precision: 6, index: true
t.datetime :valid_at, null: true, precision: 6, index: true
end

add_foreign_key "event_store_events_in_streams", "event_store_events", column: "event_id", primary_key: "event_id"
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddForeginKeyOnEventIdToEventStoreEventsInStreams < ActiveRecord::Migration[<%= migration_version %>]
def change
add_foreign_key :event_store_events_in_streams, :event_store_events, column: :event_id, primary_key: :event_id, if_not_exists: true
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ class CreateEventStoreEvents < ActiveRecord::Migration[<%= migration_version %>]
t.datetime :created_at, null: false, precision: 6, index: true
t.datetime :valid_at, null: true, precision: 6, index: true
end

add_foreign_key "event_store_events_in_streams", "event_store_events", column: "event_id", primary_key: "event_id"
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddForeginKeyOnEventIdToEventStoreEventsInStreams < ActiveRecord::Migration[<%= migration_version %>]
def change
add_foreign_key :event_store_events_in_streams, :event_store_events, column: :event_id, primary_key: :event_id, if_not_exists: true, validate: false
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ class CreateEventStoreEvents < ActiveRecord::Migration[<%= migration_version %>]
t.datetime :created_at, null: false, type: :timestamp, precision: 6, index: true
t.datetime :valid_at, null: true, type: :timestamp, precision: 6, index: true
end

add_foreign_key "event_store_events_in_streams", "event_store_events", column: "event_id", primary_key: "event_id"
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class ValidateAddForeginKeyOnEventIdToEventStoreEventsInStreams < ActiveRecord::Migration[<%= migration_version %>]
def change
validate_foreign_key :event_store_events_in_streams, :event_store_events, column: :event_id, primary_key: :event_id
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module RubyEventStore
module ActiveRecord
UnsupportedAdapter = Class.new(StandardError)

class VerifyAdapter
SUPPORTED_ADAPTERS = %w[mysql2 postgresql sqlite].freeze

def call(adapter)
raise UnsupportedAdapter, "Unsupported adapter" unless supported?(adapter)
end

private

private_constant :SUPPORTED_ADAPTERS

def supported?(adapter)
SUPPORTED_ADAPTERS.include?(adapter.downcase)
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
module RubyEventStore
module ActiveRecord
InvalidDataTypeForAdapter = Class.new(StandardError)
UnsupportedAdapter = Class.new(StandardError)

class VerifyDataTypeForAdapter
SUPPORTED_POSTGRES_DATA_TYPES = %w[binary json jsonb].freeze
SUPPORTED_MYSQL_DATA_TYPES = %w[binary json].freeze
SUPPORTED_SQLITE_DATA_TYPES = %w[binary].freeze

def call(adapter, data_type)
raise UnsupportedAdapter, "Unsupported adapter" unless supported?(adapter)
VerifyAdapter.new.call(adapter)
raise InvalidDataTypeForAdapter, "MySQL2 doesn't support #{data_type}" if is_mysql2?(adapter) && !SUPPORTED_MYSQL_DATA_TYPES.include?(data_type)
raise InvalidDataTypeForAdapter, "sqlite doesn't support #{data_type}" if is_sqlite?(adapter) && supported_by_sqlite?(data_type)
raise InvalidDataTypeForAdapter, "PostgreSQL doesn't support #{data_type}" unless supported_by_postgres?(data_type)
Expand All @@ -21,10 +20,6 @@ def call(adapter, data_type)

private_constant :SUPPORTED_POSTGRES_DATA_TYPES, :SUPPORTED_MYSQL_DATA_TYPES, :SUPPORTED_SQLITE_DATA_TYPES

def supported?(adapter)
%w[mysql2 postgresql sqlite].include?(adapter.downcase)
end

def is_sqlite?(adapter)
adapter.downcase.eql?("sqlite")
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,12 @@ task "db:migrations:fix_missing_event_id_index" do

puts "Migration file created #{path}"
end

desc "Generate migration for adding foreign key on event_store_events_in_streams.event_id"
task "db:migrations:add_foreign_key_on_event_id" do
::ActiveRecord::Base.establish_connection(ENV["DATABASE_URL"])

path = RubyEventStore::ActiveRecord::ForeignKeyOnEventIdMigrationGenerator.new.call(ENV["MIGRATION_PATH"] || "db/migrate")

puts "Migration file created #{path}"
end
Loading