Skip to content

Commit

Permalink
Add support for multiple redis connections (#49)
Browse files Browse the repository at this point in the history
This PR adds support for more than one redis connection by providing
multiple configurations to `redis_options` like this:
```ruby
Modis.redis_options = {
  default: { url: 'redis://localhost:6379/0' },
  custom: { url: 'redis://localhost:6379/1' }
}
```
while still being backwards compatible to the old configuration style
```ruby
Modis.redis_options = {
 url: 'redis://localhost:6379/0
}
```

You can then select which connection to use on a per model basis
```ruby
class User
  include Modis::Model
  self.modis_connection = :custom

  attribute :name, :string
end
```

If you have any ideas on how to improve the tests I am more than happy
to change them.

---------

Co-authored-by: Anton Rieder <aried3r@gmail.com>
Co-authored-by: Ben Langfeld <ben@langfeld.me>
Co-authored-by: Ben Langfeld <blangfeld@powerhrg.com>
  • Loading branch information
4 people authored Jul 31, 2024
1 parent 78b4d09 commit 7967970
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 20 deletions.
37 changes: 26 additions & 11 deletions lib/modis.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,20 @@

module Modis
@mutex = Mutex.new

class << self
attr_writer :redis_options, :connection_pool_size, :connection_pool_timeout,
:connection_pool
attr_writer :connection_pool_size, :connection_pool_timeout,
: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
Expand All @@ -36,17 +43,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
4 changes: 2 additions & 2 deletions lib/modis/finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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(modis_connection) do |redis|
ids = redis.smembers(key_for(:all))
redis.pipelined do |pipeline|
ids.map { |id| record_for(pipeline, id) }
Expand All @@ -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(modis_connection) do |redis|
if ids.count == 1
ids.map { |id| record_for(redis, id) }
else
Expand Down
2 changes: 1 addition & 1 deletion lib/modis/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def where(query)
end

def index_for(attribute, value)
Modis.with_connection do |redis|
Modis.with_connection(modis_connection) do |redis|
key = index_key(attribute, value)
redis.smembers(key).map(&:to_i)
end
Expand Down
2 changes: 2 additions & 0 deletions lib/modis/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions lib/modis/persistence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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(modis_connection) { |redis| self.class.attributes_for(redis, id) }
initialize(new_attributes)
self
end
Expand Down Expand Up @@ -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(modis_connection) do |redis|
self.id = redis.incr("#{namespace}_id_seq")
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/modis/transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def self.included(base)

module ClassMethods
def transaction
Modis.with_connection { |redis| redis.multi { |transaction| yield(transaction) } }
Modis.with_connection(modis_connection) { |redis| redis.multi { |transaction| yield(transaction) } }
end
end
end
Expand Down
54 changes: 54 additions & 0 deletions spec/multi_redis_spec.rb
Original file line number Diff line number Diff line change
@@ -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
9 changes: 6 additions & 3 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 7967970

Please sign in to comment.