diff --git a/lib/ldclient-rb/impl/integrations/file_data_source.rb b/lib/ldclient-rb/impl/integrations/file_data_source.rb index 1e7ad078..8c62c909 100644 --- a/lib/ldclient-rb/impl/integrations/file_data_source.rb +++ b/lib/ldclient-rb/impl/integrations/file_data_source.rb @@ -35,6 +35,7 @@ def initialize(data_store, data_source_update_sink, logger, options={}) if @paths.is_a? String @paths = [ @paths ] end + @allow_duplicates = options[:allow_duplicates] || false @auto_update = options[:auto_update] @use_listen = @auto_update && @@have_listen && !options[:force_polling] @poll_interval = options[:poll_interval] || 1 @@ -139,7 +140,7 @@ def add_item(all_data, kind, item) items = all_data[kind] raise ArgumentError, "Received unknown item kind #{kind[:namespace]} in add_data" if items.nil? # shouldn't be possible since we preinitialize the hash key = item[:key].to_sym - unless items[key].nil? + unless items[key].nil? || @allow_duplicates raise ArgumentError, "#{kind[:namespace]} key \"#{item[:key]}\" was used more than once" end items[key] = Model.deserialize(kind, item) diff --git a/lib/ldclient-rb/integrations/file_data.rb b/lib/ldclient-rb/integrations/file_data.rb index fb85ad98..36e847ca 100644 --- a/lib/ldclient-rb/integrations/file_data.rb +++ b/lib/ldclient-rb/integrations/file_data.rb @@ -97,6 +97,9 @@ module FileData # @option options [Float] :poll_interval The minimum interval, in seconds, between checks for # file modifications - used only if auto_update is true, and if the native file-watching # mechanism from 'listen' is not being used. The default value is 1 second. + # @option options [Boolean] :allow_duplicates Do not raise an error if using multiple files + # that contain the same flag or segment key. If this is true, the last value for a given key + # will be used. The default is false. # @return an object that can be stored in {Config#data_source} # def self.data_source(options={}) diff --git a/spec/integrations/file_data_source_spec.rb b/spec/integrations/file_data_source_spec.rb index 8b22e902..7fa6f5b8 100644 --- a/spec/integrations/file_data_source_spec.rb +++ b/spec/integrations/file_data_source_spec.rb @@ -37,6 +37,22 @@ module Integrations EOF } + let(:alternate_flag_only_json) { <<-EOF +{ + "flags": { + "flag1": { + "key": "flag1", + "on": false, + "fallthrough": { + "variation": 2 + }, + "variations": [ "fall", "off", "on" ] + } + } +} +EOF + } + let(:segment_only_json) { <<-EOF { "segments": { @@ -243,6 +259,17 @@ def with_data_source(options, initialize_to_valid = false) end end + it "allows duplicate keys and uses the last loaded version when allow-duplicates is true" do + file1 = make_temp_file(flag_only_json) + file2 = make_temp_file(alternate_flag_only_json) + with_data_source({ paths: [ file1.path, file2.path ], allow_duplicates: true }) do |ds| + ds.start + expect(@store.initialized?).to eq(true) + expect(@store.all(LaunchDarkly::FEATURES).keys).to_not eq([]) + expect(@store.all(LaunchDarkly::FEATURES)[:flag1][:on]).to eq(false) + end + end + it "does not reload modified file if auto-update is off" do file = make_temp_file(flag_only_json)