Skip to content

Commit dcdd7c6

Browse files
Better Redis configuration
1 parent 0de4740 commit dcdd7c6

File tree

7 files changed

+184
-116
lines changed

7 files changed

+184
-116
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## master
44

5+
- [PR#7](https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries/pull/7) Improved Redis configuration – added `Proc` and `ConnectionPool` support ([@DmitryTsepelev][])
6+
57
## 0.1.0 (2019-10-21)
68

79
- Initial version ([@DmitryTsepelev][])

README.md

+18-2
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,27 @@ All the queries are stored in memory by default, but you can easily switch to _r
6363

6464
```ruby
6565
class GraphqlSchema < GraphQL::Schema
66-
use GraphQL::PersistedQueries, store: :redis, redis_url: ENV["MY_REDIS_URL"]
66+
use GraphQL::PersistedQueries, store: :redis, redis_client: { redis_url: ENV["MY_REDIS_URL"] }
6767
end
6868
```
6969

70-
If you have `ENV["REDIS_URL"]` configured – you don't need to pass it explicitly. Also, you can pass `:redis_host`, `:redis_port` and `:redis_db_name` to build the URL from scratch or configure Redis client as you want and pass it as the `:client` option.
70+
If you have `ENV["REDIS_URL"]` configured – you don't need to pass it explicitly. Also, you can pass `:redis_host`, `:redis_port` and `:redis_db_name` inside the `:redis_client` hash to build the URL from scratch or pass the configured `Redis` or `ConnectionPool` object:
71+
72+
```ruby
73+
class GraphqlSchema < GraphQL::Schema
74+
use GraphQL::PersistedQueries,
75+
store: :redis,
76+
redis_client: { redis_host: "127.0.0.2", redis_port: "2214", redis_db_name: "7" }
77+
# or
78+
use GraphQL::PersistedQueries,
79+
store: :redis,
80+
redis_client: Redis.new(url: "redis://127.0.0.2:2214/7")
81+
# or
82+
use GraphQL::PersistedQueries,
83+
store: :redis,
84+
redis_client: ConnectionPool.new { Redis.new(url: "redis://127.0.0.2:2214/7") }
85+
end
86+
```
7187

7288
## Alternative hash functions
7389

graphql-persisted_queries.gemspec

+1
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ Gem::Specification.new do |spec|
2828
spec.add_development_dependency "rake", ">= 10.0"
2929
spec.add_development_dependency "rubocop", "0.75"
3030
spec.add_development_dependency "redis"
31+
spec.add_development_dependency "connection_pool"
3132
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# frozen_string_literal: true
2+
3+
module GraphQL
4+
module PersistedQueries
5+
module StoreAdapters
6+
# Builds Redis object instance based on passed hash
7+
class RedisClientBuilder
8+
def initialize(redis_url: nil, redis_host: nil, redis_port: nil, redis_db_name: nil)
9+
require "redis"
10+
11+
@redis_url = redis_url
12+
@redis_host = redis_host
13+
@redis_port = redis_port
14+
@redis_db_name = redis_db_name
15+
rescue LoadError => e
16+
msg = "Could not load the 'redis' gem, please add it to your gemfile or " \
17+
"configure a different adapter, e.g. use GraphQL::PersistedQueries, store: :memory"
18+
raise e.class, msg, e.backtrace
19+
end
20+
21+
def build
22+
if @redis_url && (@redis_host || @redis_port || @redis_db_name)
23+
raise ArgumentError, "redis_url cannot be passed along with redis_host, redis_port " \
24+
"or redis_db_name options"
25+
end
26+
27+
::Redis.new(url: @redis_url || build_redis_url)
28+
end
29+
30+
private
31+
32+
DEFAULT_REDIS_DB = "0"
33+
34+
def build_redis_url
35+
db_name = @redis_db_name || DEFAULT_REDIS_DB
36+
base_url = ENV["REDIS_URL"] || "redis://#{@redis_host}:#{@redis_port}"
37+
URI.join(base_url, db_name).to_s
38+
end
39+
end
40+
end
41+
end
42+
end

lib/graphql/persisted_queries/store_adapters/redis_store_adapter.rb

+24-40
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,49 @@
11
# frozen_string_literal: true
22

3+
require "graphql/persisted_queries/store_adapters/redis_client_builder"
4+
35
module GraphQL
46
module PersistedQueries
57
module StoreAdapters
68
# Redis adapter for storing persisted queries
79
class RedisStoreAdapter < BaseStoreAdapter
8-
attr_reader :storage
9-
10-
def initialize(client: nil, **options)
11-
require "redis"
12-
13-
if client && options.any?
14-
raise ArgumentError, "client cannot be passed along with redis_url, redis_host" \
15-
", redis_port or redis_db_name options"
16-
end
17-
18-
@storage = client || configure_redis_client(options)
19-
rescue LoadError => e
20-
msg = "Could not load the 'redis' gem, please add it to your gemfile or " \
21-
"configure a different adapter, e.g. use GraphQL::PersistedQueries, store: :memory"
22-
raise e.class, msg, e.backtrace
10+
def initialize(redis_client:)
11+
@redis_proc = build_redis_proc(redis_client)
2312
end
2413

2514
def fetch_query(hash)
26-
storage.get(key_for(hash))
15+
@redis_proc.call { |redis| redis.get(key_for(hash)) }
2716
end
2817

2918
def save_query(hash, query)
30-
storage.set(key_for(hash), query)
19+
@redis_proc.call { |redis| redis.set(key_for(hash), query) }
3120
end
3221

3322
private
3423

3524
def key_for(hash)
36-
"persisted-query-#{hash}"
25+
"graphql-persisted-query-#{hash}"
3726
end
3827

39-
# rubocop:disable Metrics/LineLength
40-
def configure_redis_client(redis_url: nil, redis_host: nil, redis_port: nil, redis_db_name: nil)
41-
if redis_url && (redis_host || redis_port || redis_db_name)
42-
raise ArgumentError, "redis_url cannot be passed along with redis_host, redis_port " \
43-
"or redis_db_name options"
28+
# rubocop: disable Metrics/MethodLength
29+
# rubocop: disable Metrics/CyclomaticComplexity
30+
# rubocop: disable Metrics/PerceivedComplexity
31+
def build_redis_proc(redis_client)
32+
if redis_client.is_a?(Hash)
33+
build_redis_proc(RedisClientBuilder.new(redis_client).build)
34+
elsif redis_client.is_a?(Proc)
35+
redis_client
36+
elsif defined?(::Redis) && redis_client.is_a?(::Redis)
37+
proc { |&b| b.call(redis_client) }
38+
elsif defined?(ConnectionPool) && redis_client.is_a?(ConnectionPool)
39+
proc { |&b| redis_client.with { |r| b.call(r) } }
40+
else
41+
raise ArgumentError, ":redis_client accepts Redis, ConnectionPool, Hash or Proc only"
4442
end
45-
46-
redis_url ||= build_redis_url(
47-
redis_host: redis_host,
48-
redis_port: redis_port,
49-
redis_db_name: redis_db_name
50-
)
51-
52-
Redis.new(url: redis_url)
53-
end
54-
# rubocop:enable Metrics/LineLength
55-
56-
DEFAULT_REDIS_DB = "0"
57-
58-
def build_redis_url(redis_host: nil, redis_port: nil, redis_db_name: nil)
59-
redis_db_name ||= DEFAULT_REDIS_DB
60-
redis_base_url = ENV["REDIS_URL"] || "redis://#{redis_host}:#{redis_port}"
61-
URI.join(redis_base_url, redis_db_name).to_s
6243
end
44+
# rubocop: enable Metrics/MethodLength
45+
# rubocop: enable Metrics/CyclomaticComplexity
46+
# rubocop: enable Metrics/PerceivedComplexity
6347
end
6448
end
6549
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# frozen_string_literal: true
2+
3+
require "spec_helper"
4+
5+
RSpec.describe GraphQL::PersistedQueries::StoreAdapters::RedisClientBuilder do
6+
describe "#initialize" do
7+
let(:options) { {} }
8+
9+
subject { described_class.new(options).build }
10+
11+
context "when redis_host, redis_port and redis_db_name are passed" do
12+
let(:options) do
13+
{ redis_host: "127.0.0.2", redis_port: "2214", redis_db_name: "7" }
14+
end
15+
16+
it "builds redis URL" do
17+
expect(subject.connection[:id]).to eq("redis://127.0.0.2:2214/7")
18+
end
19+
end
20+
21+
context "when REDIS_URL is configured" do
22+
let(:redis_url) { "redis://127.0.0.1:6379" }
23+
24+
before do
25+
allow(ENV).to receive(:[]).with("REDIS_URL").and_return(redis_url)
26+
end
27+
28+
it "uses default db" do
29+
expect(subject.connection[:id]).to eq("redis://127.0.0.1:6379/0")
30+
end
31+
32+
context "when redis_db_name is configured" do
33+
let(:options) { { redis_db_name: "42" } }
34+
35+
it "uses configured db" do
36+
expect(subject.connection[:id]).to eq("redis://127.0.0.1:6379/42")
37+
end
38+
end
39+
end
40+
41+
context "when redis_url is passed" do
42+
let(:options) { { redis_url: "redis://127.0.0.4:2177/22" } }
43+
44+
it "uses passed redis_url" do
45+
expect(subject.connection[:id]).to eq("redis://127.0.0.4:2177/22")
46+
end
47+
48+
context "when passed along with other parameters" do
49+
let(:options) do
50+
{
51+
redis_url: "redis://127.0.0.1:6379",
52+
redis_host: "127.0.0.1",
53+
redis_port: "6379",
54+
redis_db_name: "0"
55+
}
56+
end
57+
58+
it "raises error" do
59+
expect { subject }.to raise_error(
60+
ArgumentError,
61+
"redis_url cannot be passed along with redis_host, redis_port or redis_db_name options"
62+
)
63+
end
64+
end
65+
end
66+
end
67+
end

spec/graphql/persisted_queries/store_adapters/redis_store_adapter_spec.rb

+30-74
Original file line numberDiff line numberDiff line change
@@ -3,95 +3,51 @@
33
require "spec_helper"
44

55
require "redis"
6+
require "connection_pool"
67

78
RSpec.describe GraphQL::PersistedQueries::StoreAdapters::RedisStoreAdapter do
8-
describe "#initialize" do
9-
let(:options) { {} }
9+
subject { described_class.new(redis_client: redis_client) }
1010

11-
subject { described_class.new(options) }
11+
context "when Hash instance is passed" do
12+
let(:redis_client) { { redis_url: "redis://127.0.0.3:8791/3" } }
1213

13-
context "when client is passed" do
14-
let(:options) { { client: Redis.new(url: "redis://127.0.0.3:8791/3") } }
15-
16-
it "uses client" do
17-
expect(subject.storage.connection[:id]).to eq("redis://127.0.0.3:8791/3")
18-
end
19-
20-
context "when passed along with other parameters" do
21-
let(:options) do
22-
{
23-
client: Redis.new(url: "redis://127.0.0.3:8791/3"),
24-
redis_url: "redis://127.0.0.1:6379",
25-
redis_host: "127.0.0.1",
26-
redis_port: "6379",
27-
redis_db_name: "0"
28-
}
29-
end
30-
31-
it "raises error" do
32-
expect { subject }.to raise_error(
33-
ArgumentError,
34-
"client cannot be passed along with redis_url, redis_host, " \
35-
"redis_port or redis_db_name options"
36-
)
37-
end
38-
end
14+
it "wraps with proc" do
15+
expect(subject.instance_variable_get("@redis_proc")).to be_kind_of(Proc)
3916
end
17+
end
4018

41-
context "when redis_host, redis_port and redis_db_name are passed" do
42-
let(:options) do
43-
{ redis_host: "127.0.0.2", redis_port: "2214", redis_db_name: "7" }
44-
end
19+
context "when Proc instance is passed" do
20+
let(:redis_client) { proc {} }
4521

46-
it "builds redis URL" do
47-
expect(subject.storage.connection[:id]).to eq("redis://127.0.0.2:2214/7")
48-
end
22+
it "wraps with proc" do
23+
expect(subject.instance_variable_get("@redis_proc")).to be_kind_of(Proc)
4924
end
25+
end
5026

51-
context "when REDIS_URL is configured" do
52-
let(:redis_url) { "redis://127.0.0.1:6379" }
53-
54-
before do
55-
allow(ENV).to receive(:[]).with("REDIS_URL").and_return(redis_url)
56-
end
57-
58-
it "uses default db" do
59-
expect(subject.storage.connection[:id]).to eq("redis://127.0.0.1:6379/0")
60-
end
61-
62-
context "when redis_db_name is configured" do
63-
let(:options) { { redis_db_name: "42" } }
27+
context "when Redis instance is passed" do
28+
let(:redis_client) { Redis.new(url: "redis://127.0.0.3:8791/3") }
6429

65-
it "uses configured db" do
66-
expect(subject.storage.connection[:id]).to eq("redis://127.0.0.1:6379/42")
67-
end
68-
end
30+
it "wraps with proc" do
31+
expect(subject.instance_variable_get("@redis_proc")).to be_kind_of(Proc)
6932
end
33+
end
7034

71-
context "when redis_url is passed" do
72-
let(:options) { { redis_url: "redis://127.0.0.4:2177/22" } }
35+
context "when ConnectionPool instance is passed" do
36+
let(:redis_client) { ConnectionPool.new { Redis.new(url: "redis://127.0.0.3:8791/3") } }
7337

74-
it "uses passed redis_url" do
75-
expect(subject.storage.connection[:id]).to eq("redis://127.0.0.4:2177/22")
76-
end
38+
it "wraps with proc" do
39+
expect(subject.instance_variable_get("@redis_proc")).to be_kind_of(Proc)
40+
end
41+
end
7742

78-
context "when passed along with other parameters" do
79-
let(:options) do
80-
{
81-
redis_url: "redis://127.0.0.1:6379",
82-
redis_host: "127.0.0.1",
83-
redis_port: "6379",
84-
redis_db_name: "0"
85-
}
86-
end
43+
context "when not supported object is passed" do
44+
let(:redis_client) { 42 }
8745

88-
it "raises error" do
89-
expect { subject }.to raise_error(
90-
ArgumentError,
91-
"redis_url cannot be passed along with redis_host, redis_port or redis_db_name options"
92-
)
93-
end
94-
end
46+
it "raises error" do
47+
expect { subject }.to raise_error(
48+
ArgumentError,
49+
":redis_client accepts Redis, ConnectionPool, Hash or Proc only"
50+
)
9551
end
9652
end
9753
end

0 commit comments

Comments
 (0)