From 84624d34f7ba9c6368573879a0623d300e880a74 Mon Sep 17 00:00:00 2001 From: Ben Burkert Date: Wed, 17 Jul 2013 13:07:40 -0700 Subject: [PATCH 1/4] Allow the loader to ignore missing types. This allows replication between different versions of a common object graph. For example, we might want to dump from the latest ActiveRecord models & schema version, and load to an older version of the code. --- lib/replicate/loader.rb | 16 ++++++++++++++-- test/loader_test.rb | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/lib/replicate/loader.rb b/lib/replicate/loader.rb index 050b778..1d88e0f 100644 --- a/lib/replicate/loader.rb +++ b/lib/replicate/loader.rb @@ -71,6 +71,9 @@ def read(io) # Returns the new object instance. def load(type, id, attributes) model_class = constantize(type) + + return if model_class.nil? + translate_ids type, id, attributes begin new_id, instance = model_class.load_replicant(type, id, attributes) @@ -123,6 +126,15 @@ def register_id(object, type, remote_id, local_id) end end + # Silently ignore missing replication types instead of raising an + # uninitialized constant error. Allows for replication between mismatched + # object graphs. + def ignore_missing! + @ignore = true + end + + def ignore_missing? ; @ignore ; end + # Turn a string into an object by traversing constants. Identical to # ActiveSupport's String#constantize implementation. if Module.method(:const_get).arity == 1 @@ -132,7 +144,7 @@ def constantize(string) if namespace.const_defined?(name) namespace.const_get(name) else - namespace.const_missing(name) + namespace.const_missing(name) unless ignore_missing? end end end @@ -143,7 +155,7 @@ def constantize(string) if namespace.const_defined?(name, false) namespace.const_get(name) else - namespace.const_missing(name) + namespace.const_missing(name) unless ignore_missing? end end end diff --git a/test/loader_test.rb b/test/loader_test.rb index ad9765d..c581aba 100644 --- a/test/loader_test.rb +++ b/test/loader_test.rb @@ -90,4 +90,40 @@ def test_translating_multiple_id_attributes assert_equal 11, objects.size assert_equal 10, objects.last.related.size end + + def test_ignoring_a_missing_type + dumper = Replicate::Dumper.new + + objects = [] + dumper.listen { |type, id, attrs, obj| objects << [type, id, attrs, obj] } + + with_ghost_class do |klass| + dumper.dump(klass.new) + end + + begin + objects.each { |type, id, attrs, obj| @loader.feed type, id, attrs } + + assert_fail "NameError unexpectedly ignored" + rescue NameError + end + + @loader.ignore_missing! + + objects.each { |type, id, attrs, obj| @loader.feed type, id, attrs } + end + + def with_ghost_class + eval <<-RUBY + class ::Ghost + def dump_replicant(dumper) + dumper.write self.class, 3, {}, self + end + end + RUBY + + yield Ghost + ensure + Object.send(:remove_const, :Ghost) + end end From 772e66d537b8d0d789acc540956c527ff02957bd Mon Sep 17 00:00:00 2001 From: Ben Burkert Date: Wed, 17 Jul 2013 14:52:42 -0700 Subject: [PATCH 2/4] Warn when a type is missing. --- lib/replicate/loader.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/replicate/loader.rb b/lib/replicate/loader.rb index 1d88e0f..159e975 100644 --- a/lib/replicate/loader.rb +++ b/lib/replicate/loader.rb @@ -143,8 +143,10 @@ def constantize(string) string.split('::').inject ::Object do |namespace, name| if namespace.const_defined?(name) namespace.const_get(name) + elsif ignore_missing? + warn "ignoring missing type #{name}" else - namespace.const_missing(name) unless ignore_missing? + namespace.const_missing(name) end end end @@ -154,8 +156,10 @@ def constantize(string) string.split('::').inject ::Object do |namespace, name| if namespace.const_defined?(name, false) namespace.const_get(name) + elsif ignore_missing? + warn "ignoring missing type #{name}" else - namespace.const_missing(name) unless ignore_missing? + namespace.const_missing(name) end end end From b52fce17f03279925f62d8013c88a0bc11b88515 Mon Sep 17 00:00:00 2001 From: Ben Burkert Date: Wed, 17 Jul 2013 14:54:35 -0700 Subject: [PATCH 3/4] Ignore failed loads when ignore_missing! is set. --- lib/replicate/loader.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/replicate/loader.rb b/lib/replicate/loader.rb index 159e975..b0e7f1a 100644 --- a/lib/replicate/loader.rb +++ b/lib/replicate/loader.rb @@ -79,6 +79,9 @@ def load(type, id, attributes) new_id, instance = model_class.load_replicant(type, id, attributes) rescue => boom warn "error: loading #{type} #{id} #{boom.class} #{boom}" + + return if ignore_missing? + raise end register_id instance, type, id, new_id From 1b7924ac539688d637adb0d97ce5e9a4075fe921 Mon Sep 17 00:00:00 2001 From: Ben Burkert Date: Wed, 24 Jul 2013 11:53:54 -0700 Subject: [PATCH 4/4] Always call const_missing. Active Record overloads this to autoload models. --- lib/replicate/loader.rb | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/replicate/loader.rb b/lib/replicate/loader.rb index b0e7f1a..28d161d 100644 --- a/lib/replicate/loader.rb +++ b/lib/replicate/loader.rb @@ -146,10 +146,16 @@ def constantize(string) string.split('::').inject ::Object do |namespace, name| if namespace.const_defined?(name) namespace.const_get(name) - elsif ignore_missing? - warn "ignoring missing type #{name}" else - namespace.const_missing(name) + begin + namespace.const_missing(name) + rescue NameError => e + if ignore_missing? + warn "ignoring missing type #{name}" + else + raise e + end + end end end end @@ -159,10 +165,16 @@ def constantize(string) string.split('::').inject ::Object do |namespace, name| if namespace.const_defined?(name, false) namespace.const_get(name) - elsif ignore_missing? - warn "ignoring missing type #{name}" else - namespace.const_missing(name) + begin + namespace.const_missing(name) + rescue NameError => e + if ignore_missing? + warn "ignoring missing type #{name}" + else + raise e + end + end end end end