diff --git a/lib/replicate/loader.rb b/lib/replicate/loader.rb index 050b778..28d161d 100644 --- a/lib/replicate/loader.rb +++ b/lib/replicate/loader.rb @@ -71,11 +71,17 @@ 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) rescue => boom warn "error: loading #{type} #{id} #{boom.class} #{boom}" + + return if ignore_missing? + raise end register_id instance, type, id, new_id @@ -123,6 +129,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 +147,15 @@ def constantize(string) if namespace.const_defined?(name) namespace.const_get(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 @@ -143,7 +166,15 @@ def constantize(string) if namespace.const_defined?(name, false) namespace.const_get(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 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