-
Notifications
You must be signed in to change notification settings - Fork 21.9k
Closed
Description
The ActiveRecord::Migration::CommandRecorder
records commands and then replays them later. However it does not invoke the original commands correctly on Ruby 3, because it does not consider keyword arguments.
Steps to reproduce
require 'active_record'
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
class ActiveRecord::Migration::CommandRecorder
def update_view(*args) = record(:update_view, args) # https://github.com/scenic-views/scenic/blob/v1.5.4/lib/scenic/command_recorder.rb#L14
def invert_update_view(*args) = [:update_view, *args] # https://github.com/scenic-views/scenic/blob/v1.5.4/lib/scenic/command_recorder.rb#L31
end
# https://github.com/scenic-views/scenic/blob/v1.5.4/lib/scenic/statements.rb#L25
class ActiveRecord::ConnectionAdapters::SQLite3Adapter
def update_view(name, version:, revert_to_version:) =
puts("UPDATE VIEW: #{name}, version: #{version}, revert_to_version: #{revert_to_version}")
end
class SomeMigration < ActiveRecord::Migration[6.1]
def change() = update_view(:my_view, version: 2, revert_to_version: 1)
end
pp RUBY_VERSION: RUBY_VERSION, 'ActiveRecord::VERSION': ActiveRecord::VERSION::STRING
puts
SomeMigration.migrate :down
Expected behavior
$ ruby example.rb
{:RUBY_VERSION=>"3.0.1", :"ActiveRecord::VERSION"=>"6.1.3.2"}
== SomeMigration: reverting ==================================================
-- update_view(:my_view, {:version=>2, :revert_to_version=>1})
UPDATE VIEW: my_view, version: 2, revert_to_version: 1
-> 0.0000s
== SomeMigration: reverted (0.0001s) =========================================
Actual behavior
$ ruby example.rb
{:RUBY_VERSION=>"3.0.1", :"ActiveRecord::VERSION"=>"6.1.3.2"}
== SomeMigration: reverting ==================================================
-- update_view(:my_view, {:version=>2, :revert_to_version=>1})
ar.example.rb:11:in `update_view': wrong number of arguments (given 2, expected 1; required keywords: version, revert_to_version) (ArgumentError)
from /Users/josh/.gem/ruby/3.0.1/gems/activerecord-6.1.3.2/lib/active_record/migration.rb:931:in `block in method_missing'
from /Users/josh/.gem/ruby/3.0.1/gems/activerecord-6.1.3.2/lib/active_record/migration.rb:899:in `block in say_with_time'
from /Users/josh/.rubies/ruby-3.0.1/lib/ruby/3.0.0/benchmark.rb:293:in `measure'
from /Users/josh/.gem/ruby/3.0.1/gems/activerecord-6.1.3.2/lib/active_record/migration.rb:899:in `say_with_time'
from /Users/josh/.gem/ruby/3.0.1/gems/activerecord-6.1.3.2/lib/active_record/migration.rb:920:in `method_missing'
from /Users/josh/.gem/ruby/3.0.1/gems/activerecord-6.1.3.2/lib/active_record/migration/command_recorder.rb:126:in `block in replay'
from /Users/josh/.gem/ruby/3.0.1/gems/activerecord-6.1.3.2/lib/active_record/migration/command_recorder.rb:125:in `each'
from /Users/josh/.gem/ruby/3.0.1/gems/activerecord-6.1.3.2/lib/active_record/migration/command_recorder.rb:125:in `replay'
from /Users/josh/.gem/ruby/3.0.1/gems/activerecord-6.1.3.2/lib/active_record/migration.rb:743:in `revert'
from /Users/josh/.gem/ruby/3.0.1/gems/activerecord-6.1.3.2/lib/active_record/migration.rb:867:in `exec_migration'
from /Users/josh/.gem/ruby/3.0.1/gems/activerecord-6.1.3.2/lib/active_record/migration.rb:853:in `block (2 levels) in migrate'
from /Users/josh/.rubies/ruby-3.0.1/lib/ruby/3.0.0/benchmark.rb:293:in `measure'
from /Users/josh/.gem/ruby/3.0.1/gems/activerecord-6.1.3.2/lib/active_record/migration.rb:852:in `block in migrate'
from /Users/josh/.gem/ruby/3.0.1/gems/activerecord-6.1.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:462:in `with_connection'
from /Users/josh/.gem/ruby/3.0.1/gems/activerecord-6.1.3.2/lib/active_record/migration.rb:851:in `migrate'
from /Users/josh/.gem/ruby/3.0.1/gems/activerecord-6.1.3.2/lib/active_record/migration.rb:665:in `migrate'
from ar.example.rb:24:in `<main>'
System configuration
Rails version: 6.1.3.2
Ruby version: ruby 3.0.1p64 (2021-04-05 revision 0fb782ee38) [arm64-darwin20]
Fixing it
For the "expected behaviour" step, I got it to work by changing the replay
method to this (I'm not asserting this is correct, I find this keyword change very confusing, TBH):
class ActiveRecord::Migration::CommandRecorder
def replay(migration)
commands.each do |cmd, args, block|
if args.last.respond_to?(:to_hash) && args.last.to_hash.all? { |k, _| k.is_a? Symbol }
*ordinals, keywords = args
else
ordinals, keywords = args, {}
end
migration.send(cmd, *ordinals, **keywords, &block)
end
end
end
Metadata
Metadata
Assignees
Labels
No labels