diff --git a/README.md b/README.md index 5fffa18..c6e13da 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/lib/redis/objects.rb b/lib/redis/objects.rb index 724f1c5..d599f9a 100644 --- a/lib/redis/objects.rb +++ b/lib/redis/objects.rb @@ -115,16 +115,16 @@ 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 @@ -132,7 +132,7 @@ def modern_redis_prefix(klass = self) #:nodoc: 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 @@ -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 < "#{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] || {} diff --git a/spec/redis_legacy_key_naming_spec.rb b/spec/redis_legacy_key_naming_spec.rb index 26663f3..85c1ebb 100644 --- a/spec/redis_legacy_key_naming_spec.rb +++ b/spec/redis_legacy_key_naming_spec.rb @@ -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 @@ -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