Skip to content

Commit

Permalink
Drop support for ruby < 2.7 and rails < 6.1
Browse files Browse the repository at this point in the history
  • Loading branch information
fatkodima committed Nov 21, 2023
1 parent c77f430 commit 5f5efdd
Show file tree
Hide file tree
Showing 63 changed files with 308 additions and 1,025 deletions.
12 changes: 1 addition & 11 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,7 @@ jobs:
strategy:
matrix:
include:
- ruby-version: 2.4
gemfile: activerecord_42.gemfile
- ruby-version: 2.4
gemfile: activerecord_50.gemfile
- ruby-version: 2.4
gemfile: activerecord_51.gemfile
- ruby-version: 2.4
gemfile: activerecord_52.gemfile
- ruby-version: 2.5
gemfile: activerecord_60.gemfile
- ruby-version: 2.5
- ruby-version: 2.7
gemfile: activerecord_61.gemfile
- ruby-version: 2.7
gemfile: activerecord_70.gemfile
Expand Down
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ require:
- rubocop-disable_syntax

AllCops:
TargetRubyVersion: 2.3
TargetRubyVersion: 2.7
NewCops: enable
SuggestExtensions: false

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## master (unreleased)

- Drop support for Ruby < 2.7 and Rails < 6.1
- Fix `backfill_column_for_type_change_in_background` for cast expressions
- Fix copying indexes with long names when changing column type
- Enhance error messages with the link to the detailed description
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ PATH
remote: .
specs:
online_migrations (0.9.2)
activerecord (>= 4.2)
activerecord (>= 6.1)

GEM
remote: https://rubygems.org/
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ See [comparison to `strong_migrations`](#comparison-to-strong_migrations)

## Requirements

- Ruby 2.1+
- Rails 4.2+
- Ruby 2.7+
- Rails 6.1+
- PostgreSQL 9.6+

For older Ruby and Rails versions you can use '< 0.10' version of this gem.

**Note**: Since some migration helpers use database `VIEW`s to implement their logic, it is recommended to use `structure.sql` schema format, or otherwise add some gem (like [scenic](https://github.com/scenic-views/scenic)) to be able to dump them into the `schema.rb`.

## Installation
Expand Down
6 changes: 0 additions & 6 deletions gemfiles/activerecord_42.gemfile

This file was deleted.

5 changes: 0 additions & 5 deletions gemfiles/activerecord_50.gemfile

This file was deleted.

5 changes: 0 additions & 5 deletions gemfiles/activerecord_51.gemfile

This file was deleted.

5 changes: 0 additions & 5 deletions gemfiles/activerecord_52.gemfile

This file was deleted.

5 changes: 0 additions & 5 deletions gemfiles/activerecord_60.gemfile

This file was deleted.

10 changes: 3 additions & 7 deletions lib/generators/online_migrations/install_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,16 @@ def copy_initializer_file
end

def create_migration_file
migration_template("migration.rb", File.join(migrations_dir, "install_online_migrations.rb"))
migration_template("migration.rb", File.join(db_migrate_path, "install_online_migrations.rb"))
end

private
def migration_parent
Utils.migration_parent_string
"ActiveRecord::Migration[#{Utils.ar_version}]"
end

def start_after
self.class.current_migration_number(migrations_dir)
end

def migrations_dir
Utils.ar_version >= 5.1 ? db_migrate_path : "db/migrate"
self.class.current_migration_number(db_migrate_path)
end
end
end
5 changes: 0 additions & 5 deletions lib/online_migrations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ class UnsafeMigration < Error; end
autoload :Migration
autoload :Migrator
autoload :DatabaseTasks
autoload :ForeignKeyDefinition
autoload :ForeignKeysCollector
autoload :IndexDefinition
autoload :IndexesCollector
Expand Down Expand Up @@ -90,10 +89,6 @@ def load
else
ActiveRecord::ConnectionAdapters::SchemaCache.prepend(OnlineMigrations::SchemaCache)
end

if OnlineMigrations::Utils.ar_version <= 5.1
ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.prepend(OnlineMigrations::ForeignKeyDefinition)
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def active?
objid = lock_key & 0xffffffff
classid = (lock_key & (0xffffffff << 32)) >> 32

active = connection.select_value(<<-SQL.strip_heredoc)
active = connection.select_value(<<~SQL)
SELECT granted
FROM pg_locks
WHERE locktype = 'advisory'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def relation
quoted_column = connection.quote_column_name(column)
model.unscoped.where("#{quoted_column} != ? OR #{quoted_column} IS NULL", value)
else
Utils.ar_where_not_multiple_conditions(model.unscoped, updates)
model.unscoped.where.not(updates)
end
end

Expand Down
26 changes: 6 additions & 20 deletions lib/online_migrations/background_migrations/copy_column.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,10 @@ def initialize(table_name, copy_from, copy_to, model_name = nil, type_cast_funct
end

def relation
relation = model
model
.unscoped
.where(copy_to.map { |to_column| [to_column, nil] }.to_h)

Utils.ar_where_not_multiple_conditions(
relation,
copy_from.map { |from_column| [from_column, nil] }.to_h
)
.where(copy_to.index_with(nil))
.where.not(copy_from.index_with(nil))
end

def process_batch(relation)
Expand All @@ -43,13 +39,8 @@ def process_batch(relation)
old_value = arel_table[from_column]
if (type_cast_function = type_cast_functions[from_column])
old_value =
if type_cast_function =~ /\A\w+\z/
if Utils.ar_version <= 5.2
# Active Record <= 5.2 does not support quoting of Arel::Nodes::NamedFunction
Arel.sql("#{type_cast_function}(#{connection.quote_column_name(from_column)})")
else
Arel::Nodes::NamedFunction.new(type_cast_function, [old_value])
end
if type_cast_function.match?(/\A\w+\z/)
Arel::Nodes::NamedFunction.new(type_cast_function, [old_value])
else
# We got a cast expression.
Arel.sql(type_cast_function)
Expand All @@ -58,12 +49,7 @@ def process_batch(relation)
old_value
end

if Utils.ar_version <= 4.2
stmt = Arel::UpdateManager.new(arel.engine)
else
stmt = Arel::UpdateManager.new
end

stmt = Arel::UpdateManager.new
stmt.table(arel_table)
stmt.wheres = arel.constraints

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,11 @@ def initialize(model_name, associations, _options = {})
end

def relation
# For Active Record 6.1+ we can use `where.missing`
# https://github.com/rails/rails/pull/34727
associations.inject(model.unscoped) do |relation, association|
reflection = model.reflect_on_association(association)
if reflection.nil?
raise ArgumentError, "'#{model.name}' has no association called '#{association}'"
end

# left_joins was added in Active Record 5.0 - https://github.com/rails/rails/pull/12071
relation
.left_joins(association)
.where(reflection.table_name => { reflection.association_primary_key => nil })
end
model.unscoped.where.missing(*associations)
end

def process_batch(relation)
if Utils.ar_version > 5.0
relation.delete_all
else
# Older Active Record generates incorrect query when running delete_all
primary_key = model.primary_key
model.unscoped.where(primary_key => relation.select(primary_key)).delete_all
end
relation.delete_all
end

def count
Expand Down
14 changes: 4 additions & 10 deletions lib/online_migrations/background_migrations/migration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Migration < ActiveRecord::Base
for_migration_name(migration_name).where("arguments = ?", arguments.to_json)
end

enum status: STATUSES.map { |status| [status, status.to_s] }.to_h
enum status: STATUSES.index_with(&:to_s)

has_many :migration_jobs

Expand Down Expand Up @@ -138,16 +138,10 @@ def next_batch_range

# rubocop:disable Lint/UnreachableLoop
iterator.each_batch(of: batch_size, column: batch_column_name, start: next_min_value) do |relation|
if Utils.ar_version <= 4.2
# Active Record <= 4.2 does not support pluck with Arel nodes
quoted_column = self.class.connection.quote_column_name(batch_column_name)
batch_range = relation.pluck("MIN(#{quoted_column}), MAX(#{quoted_column})").first
else
min = relation.arel_table[batch_column_name].minimum
max = relation.arel_table[batch_column_name].maximum
min = relation.arel_table[batch_column_name].minimum
max = relation.arel_table[batch_column_name].maximum
batch_range = relation.pick(min, max)

batch_range = relation.pluck(min, max).first
end
break
end
# rubocop:enable Lint/UnreachableLoop
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,6 @@ def reset_counters_in_background(model_name, *counters, touch: nil, **options)
# For smaller tables it is probably better and easier to directly find and delete orpahed records.
#
def delete_orphaned_records_in_background(model_name, *associations, **options)
if Utils.ar_version <= 4.2
raise "#{__method__} does not support Active Record <= 4.2 yet"
end

model_name = model_name.name if model_name.is_a?(Class)

enqueue_background_migration(
Expand Down
14 changes: 5 additions & 9 deletions lib/online_migrations/background_migrations/migration_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ class MigrationJob < ActiveRecord::Base

self.table_name = :background_migration_jobs

# For Active Record <= 4.2 needs to fully specify enum values
scope :active, -> { where(status: [statuses[:enqueued], statuses[:running]]) }
scope :completed, -> { where(status: [statuses[:failed], statuses[:succeeded]]) }
scope :active, -> { where(status: [:enqueued, :running]) }
scope :completed, -> { where(status: [:failed, :succeeded]) }
scope :stuck, -> do
timeout = ::OnlineMigrations.config.background_migrations.stuck_jobs_timeout
active.where("updated_at <= ?", timeout.ago)
Expand All @@ -26,7 +25,7 @@ class MigrationJob < ActiveRecord::Base
stuck_sql = connection.unprepared_statement { stuck.to_sql }
failed_retriable_sql = connection.unprepared_statement { failed_retriable.to_sql }

from(Arel.sql(<<-SQL.strip_heredoc))
from(Arel.sql(<<~SQL))
(
(#{failed_retriable_sql})
UNION
Expand All @@ -35,19 +34,16 @@ class MigrationJob < ActiveRecord::Base
SQL
end

scope :except_succeeded, -> { where("status != ?", statuses[:succeeded]) }
scope :except_succeeded, -> { where.not(status: :succeeded) }
scope :attempts_exceeded, -> { where("attempts >= max_attempts") }

enum status: STATUSES.map { |status| [status, status.to_s] }.to_h
enum status: STATUSES.index_with(&:to_s)

delegate :migration_class, :migration_object, :migration_relation, :batch_column_name,
:arguments, :batch_pause, to: :migration

belongs_to :migration

# For Active Record 5.0+ this is validated by default from belongs_to
validates :migration, presence: true

validates :min_value, :max_value, presence: true, numericality: { greater_than: 0 }
validate :values_in_migration_range, if: :min_value?
validate :validate_values_order, if: :min_value?
Expand Down
12 changes: 3 additions & 9 deletions lib/online_migrations/background_migrations/reset_counters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def process_batch(relation)
counter_name = reflection.counter_cache_column

quoted_association_table = connection.quote_table_name(has_many_association.table_name)
count_subquery = <<-SQL.strip_heredoc
count_subquery = <<~SQL
SELECT COUNT(*)
FROM #{quoted_association_table}
WHERE #{quoted_association_table}.#{connection.quote_column_name(foreign_key)} =
Expand All @@ -41,8 +41,7 @@ def process_batch(relation)
names = Array.wrap(names)
options = names.extract_options!
touch_updates = touch_attributes_with_time(*names, **options)
# In Active Record 4.2 sanitize_sql_for_assignment is protected
updates << model.send(:sanitize_sql_for_assignment, touch_updates)
updates << model.sanitize_sql_for_assignment(touch_updates)
end

relation.update_all(updates.join(", "))
Expand All @@ -64,11 +63,6 @@ def has_many_association(counter_association) # rubocop:disable Naming/Predicate

has_many_association = has_many.find do |association|
counter_cache_column = association.counter_cache_column

# Active Record <= 4.2 is able to return only explicitly provided `counter_cache` column.
if !counter_cache_column && Utils.ar_version <= 4.2
counter_cache_column = "#{association.name}_count"
end
counter_cache_column && counter_cache_column.to_sym == counter_association.to_sym
end

Expand All @@ -86,7 +80,7 @@ def has_many_association(counter_association) # rubocop:disable Naming/Predicate
def touch_attributes_with_time(*names, time: nil)
attribute_names = timestamp_attributes_for_update & model.column_names
attribute_names |= names.map(&:to_s)
attribute_names.map { |attribute_name| [attribute_name, time || Time.current] }.to_h
attribute_names.index_with(time || Time.current)
end

def timestamp_attributes_for_update
Expand Down
Loading

0 comments on commit 5f5efdd

Please sign in to comment.