From 51a6586504005712939c80902cab749d503bf964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81=20Koller?= Date: Tue, 30 Jan 2024 15:45:37 +0100 Subject: [PATCH] Support multiple redis connections --- lib/modis.rb | 35 ++++++++++++++++++-------- lib/modis/finder.rb | 4 +-- lib/modis/index.rb | 2 +- lib/modis/model.rb | 2 ++ lib/modis/persistence.rb | 4 +-- lib/modis/transaction.rb | 2 +- spec/multi_redis_spec.rb | 54 ++++++++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 9 ++++--- 8 files changed, 93 insertions(+), 19 deletions(-) create mode 100644 spec/multi_redis_spec.rb diff --git a/lib/modis.rb b/lib/modis.rb index c065995..08d5d39 100644 --- a/lib/modis.rb +++ b/lib/modis.rb @@ -18,13 +18,20 @@ module Modis @mutex = Mutex.new - class << self attr_writer :redis_options, :connection_pool_size, :connection_pool_timeout, - :connection_pool + :connection_pools def redis_options - @redis_options ||= {} + @redis_options ||= { default: {} } + end + + def redis_options=(options) + if options.is_a?(Hash) && options.values.first.is_a?(Hash) + @redis_options = options.transform_values(&:dup) + else + @redis_options[:default] = options + end end def connection_pool_size @@ -35,17 +42,25 @@ def connection_pool_timeout @connection_pool_timeout ||= 5 end - def connection_pool - return @connection_pool if @connection_pool + def connection_pools + @connection_pools ||= {} + end - @mutex.synchronize do - options = { size: connection_pool_size, timeout: connection_pool_timeout } - @connection_pool = ConnectionPool.new(options) { Redis.new(redis_options) } + def connection_pool(pool_name = :default) + connection_pools[pool_name] ||= begin + @mutex.synchronize do + ConnectionPool.new( + size: connection_pool_size, + timeout: connection_pool_timeout + ) do + Redis.new(redis_options[pool_name]) + end + end end end - def with_connection - connection_pool.with { |connection| yield(connection) } + def with_connection(pool_name = :default) + connection_pool(pool_name).with { |connection| yield(connection) } end end end diff --git a/lib/modis/finder.rb b/lib/modis/finder.rb index 4d2ebbb..5f810e9 100644 --- a/lib/modis/finder.rb +++ b/lib/modis/finder.rb @@ -18,7 +18,7 @@ def all "because you disabled all index. See :enable_all_index for more." end - records = Modis.with_connection do |redis| + records = Modis.with_connection(self.modis_connection) do |redis| ids = redis.smembers(key_for(:all)) redis.pipelined do |pipeline| ids.map { |id| record_for(pipeline, id) } @@ -41,7 +41,7 @@ def attributes_for(redis, id) def find_all(ids) raise RecordNotFound, "Couldn't find #{name} without an ID" if ids.empty? - records = Modis.with_connection do |redis| + records = Modis.with_connection(self.modis_connection) do |redis| if ids.count == 1 ids.map { |id| record_for(redis, id) } else diff --git a/lib/modis/index.rb b/lib/modis/index.rb index 07c77ae..dca0d65 100644 --- a/lib/modis/index.rb +++ b/lib/modis/index.rb @@ -36,7 +36,7 @@ def where(query) end def index_for(attribute, value) - Modis.with_connection do |redis| + Modis.with_connection(self.modis_connection) do |redis| key = index_key(attribute, value) redis.smembers(key).map(&:to_i) end diff --git a/lib/modis/model.rb b/lib/modis/model.rb index 2dbb765..7672c08 100644 --- a/lib/modis/model.rb +++ b/lib/modis/model.rb @@ -21,6 +21,8 @@ def self.included(base) include Modis::Index base.extend(ClassMethods) + base.class_attribute :modis_connection + base.modis_connection = :default end end diff --git a/lib/modis/persistence.rb b/lib/modis/persistence.rb index 7a6fcf1..5dcd45a 100644 --- a/lib/modis/persistence.rb +++ b/lib/modis/persistence.rb @@ -150,7 +150,7 @@ def destroy end def reload - new_attributes = Modis.with_connection { |redis| self.class.attributes_for(redis, id) } + new_attributes = Modis.with_connection(self.modis_connection) { |redis| self.class.attributes_for(redis, id) } initialize(new_attributes) self end @@ -253,7 +253,7 @@ def coerced_attributes def set_id namespace = self.class.sti_child? ? self.class.sti_base_absolute_namespace : self.class.absolute_namespace - Modis.with_connection do |redis| + Modis.with_connection(self.modis_connection) do |redis| self.id = redis.incr("#{namespace}_id_seq") end end diff --git a/lib/modis/transaction.rb b/lib/modis/transaction.rb index 837ec9c..1500edb 100644 --- a/lib/modis/transaction.rb +++ b/lib/modis/transaction.rb @@ -8,7 +8,7 @@ def self.included(base) module ClassMethods def transaction - Modis.with_connection { |redis| redis.multi { |transaction| yield(transaction) } } + Modis.with_connection(self.modis_connection) { |redis| redis.multi { |transaction| yield(transaction) } } end end end diff --git a/spec/multi_redis_spec.rb b/spec/multi_redis_spec.rb new file mode 100644 index 0000000..52ac06c --- /dev/null +++ b/spec/multi_redis_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module MultiRedisSpec + class DefaultUserModel + include Modis::Model + + attribute :name, :string + end + + class CustomUserModel + include Modis::Model + self.modis_connection = :custom + + attribute :name, :string + end +end + +describe 'Multiple redis support' do + before do + Modis.redis_options = { + default: { url: 'redis://localhost:6379/0' }, + custom: { url: 'redis://localhost:6379/1' } + } + end + + it 'uses the default redis connection' do + expect(Modis).to receive(:with_connection).with(:default).at_least(3).times.and_call_original + user = MultiRedisSpec::DefaultUserModel.create!(name: 'Ian') + + expect(Modis).to receive(:with_connection).with(:default).at_least(3).times.and_call_original + MultiRedisSpec::DefaultUserModel.find(user.id) + end + + it 'uses the specified redis connection when set up' do + expect(Modis).to receive(:with_connection).with(:custom).at_least(3).times.and_call_original + user = MultiRedisSpec::CustomUserModel.create!(name: 'Tanya') + + expect(Modis).to receive(:with_connection).with(:custom).at_least(3).times.and_call_original + MultiRedisSpec::CustomUserModel.find(user.id) + end +end + +describe 'backwards compatibility' do + before do + Modis.redis_options = { + url: 'redis://localhost:6379/0' + } + end + + it 'uses the default redis connection' do + expect(Modis).to receive(:with_connection).with(:default).at_least(3).times.and_call_original + MultiRedisSpec::DefaultUserModel.create!(name: 'Ian') + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 181b82a..00d0734 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -20,9 +20,12 @@ RSpec.configure do |config| config.after :each do - Modis.with_connection do |connection| - keys = connection.keys "#{Modis.config.namespace}:*" - connection.del(*keys) unless keys.empty? + RSpec::Mocks.space.proxy_for(Modis).reset + Modis.connection_pools.each do |key, _| + Modis.with_connection(key) do |connection| + keys = connection.keys "#{Modis.config.namespace}:*" + connection.del(*keys) unless keys.empty? + end end end end