Skip to content

Commit

Permalink
Added migrate_redis_legacy_keys class method in support of issue #231
Browse files Browse the repository at this point in the history
  • Loading branch information
nateware committed Jun 23, 2022
1 parent b825a2b commit f9d7526
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 10 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -598,5 +598,5 @@ end

Author
=======
Copyright (c) 2009-2019 [Nate Wiger](http://nateware.com). All Rights Reserved.
Copyright (c) 2009-2022 [Nate Wiger](http://nateware.com). All Rights Reserved.
Released under the [Artistic License](http://www.opensource.org/licenses/artistic-license-2.0.php).
46 changes: 38 additions & 8 deletions lib/redis/objects.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,24 +115,24 @@ def redis_prefix=(redis_prefix)
def redis_prefix(klass = self) #:nodoc:
@redis_prefix ||=
if redis_legacy_naming
legacy_redis_prefix(klass)
redis_legacy_prefix(klass)
else
legacy_naming_warning_message(klass)
modern_redis_prefix(klass)
redis_legacy_naming_warning_message(klass)
redis_modern_prefix(klass)
end

@redis_prefix
end

def modern_redis_prefix(klass = self) #:nodoc:
def redis_modern_prefix(klass = self) #:nodoc:
klass.name.to_s.
gsub(/::/, '__'). # Nested::Class => Nested__Class
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). # ClassName => Class_Name
gsub(/([a-z\d])([A-Z])/,'\1_\2'). # className => class_Name
downcase
end

def legacy_redis_prefix(klass = self) #:nodoc:
def redis_legacy_prefix(klass = self) #:nodoc:
klass.name.to_s.
sub(%r{(.*::)}, ''). # Nested::Class => Class (problematic)
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). # ClassName => Class_Name
Expand All @@ -141,11 +141,11 @@ def legacy_redis_prefix(klass = self) #:nodoc:
end

# Temporary warning to help with migrating key names
def legacy_naming_warning_message(klass)
def redis_legacy_naming_warning_message(klass)
# warn @silence_warnings_as_redis_prefix_was_set_manually.inspect
unless redis_legacy_naming || redis_silence_warnings || @silence_warnings_as_redis_prefix_was_set_manually
modern = modern_redis_prefix(klass)
legacy = legacy_redis_prefix(klass)
modern = redis_modern_prefix(klass)
legacy = redis_legacy_prefix(klass)
if modern != legacy
warn <<EOW
WARNING: In redis-objects 2.0.0, key naming will change to fix longstanding bugs.
Expand All @@ -158,6 +158,36 @@ def legacy_naming_warning_message(klass)
end
end

def migrate_redis_legacy_keys
cursor = 0
legacy = redis_legacy_prefix
total_keys = 0
if legacy == redis_prefix
raise "Failed to migrate keys for #{self.name.to_s} as legacy and new redis_prefix are the same (#{redis_prefix})"
end
warn "Migrating keys from #{legacy} prefix to #{redis_prefix}"

loop do
cursor, keys = redis.scan(cursor, :match => "#{legacy}:*")
total_keys += keys.length
keys.each do |key|
# Split key name apart on ':'
base_class, id, name = key.split(':')

# Figure out the new name
new_key = redis_field_key(name, id=id, context=self)

# Rename the key
warn "Rename '#{key}', '#{new_key}'"
ok = redis.rename(key, new_key)
warn "Warning: Rename '#{key}', '#{new_key}' failed: #{ok}" if ok != 'OK'
end
break if cursor == "0"
end

warn "Migrated #{total_keys} total number of redis keys"
end

def redis_options(name)
klass = first_ancestor_with(name)
return klass.redis_objects[name.to_sym] || {}
Expand Down
122 changes: 121 additions & 1 deletion spec/redis_legacy_key_naming_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ def id
obj.class.redis_prefix.should == 'single_level_two'
end


it 'verifies nested classes do NOT work the same' do
module Nested
class NamingOne
Expand Down Expand Up @@ -296,4 +295,125 @@ def id

captured_output.should =~ /Warning:/i
end

it 'supports a method to migrate legacy key names' do
module Nested
class Legacy
include Redis::Objects
self.redis = Redis.new(:host => REDIS_HOST, :port => REDIS_PORT)
self.redis_legacy_naming = true

# override this for testing - need two classes as if we imagine an old and new one
# also use the legacy flat prefix that ignores the nested class name
self.redis_prefix = 'modern'

def initialize(id)
@id = id
end
def id
@id
end

value :redis_value
counter :redis_counter
hash_key :redis_hash
list :redis_list
set :redis_set
sorted_set :redis_sorted_set

# global class counters
value :global_value, :global => true
counter :global_counter, :global => true
hash_key :global_hash_key, :global => true
list :global_list, :global => true
set :global_set, :global => true
sorted_set :global_sorted_set, :global => true

#callable as key
value :global_proc_value, :global => true, :key => Proc.new { |roster| "#{roster.name}:#{Time.now.strftime('%Y-%m-%dT%H')}:daily" }
end
end

module Nested
class Modern
include Redis::Objects
self.redis = Redis.new(:host => REDIS_HOST, :port => REDIS_PORT)

def initialize(id)
@id = id
end
def id
@id
end

value :redis_value
counter :redis_counter
hash_key :redis_hash
list :redis_list
set :redis_set
sorted_set :redis_sorted_set

# global class counters
value :global_value, :global => true
counter :global_counter, :global => true
hash_key :global_hash_key, :global => true
list :global_list, :global => true
set :global_set, :global => true
sorted_set :global_sorted_set, :global => true

#callable as key
value :global_proc_value, :global => true, :key => Proc.new { |roster| "#{roster.name}:#{Time.now.strftime('%Y-%m-%dT%H')}:daily" }
end
end

# Iterate over them
Nested::Modern.redis_objects.length.should == 13
Nested::Modern.redis_objects.length.should == Nested::Legacy.redis_objects.length.should
Nested::Legacy.redis_prefix.should == 'modern'
Nested::Modern.redis_prefix.should == 'nested__modern'

# Create a whole bunch of keys using the legacy names
30.times do |i|
# warn i.inspect
obj = Nested::Legacy.new(i)
obj.redis_value = i
obj.redis_value.to_i.should == i
obj.redis_counter.increment
obj.redis_hash[:key] = i
obj.redis_list << i
obj.redis_set << i
obj.redis_sorted_set[i] = i
end

obj = Nested::Legacy.new(99)
obj.global_value = 42
obj.global_counter.increment
obj.global_counter.increment
obj.global_counter.increment
obj.global_hash_key[:key] = 'value'
obj.global_set << 'a' << 'b'
obj.global_sorted_set[:key] = 2.2

Nested::Modern.migrate_redis_legacy_keys

# Try to access the keys through modern names now
30.times do |i|
# warn i.inspect
obj = Nested::Modern.new(i)
obj.redis_value.to_i.should == i
obj.redis_counter.to_i.should == 1
obj.redis_hash[:key].to_i.should == i
obj.redis_list[0].to_i.should == i
obj.redis_set.include?(i).should == true
obj.redis_sorted_set[i].should == i
end

obj = Nested::Modern.new(99)
obj.global_value.to_i.should == 42
obj.global_counter.to_i.should == 3
obj.global_hash_key[:key].should == 'value'
obj.global_set.should.include?('a').should == true
obj.global_set.should.include?('b').should == true
obj.global_sorted_set[:key].should == 2.2
end
end

0 comments on commit f9d7526

Please sign in to comment.