Skip to content

Commit a1ee11c

Browse files
committed
Merge pull request #7 from krists/connection-fix
When initializing Refile::Postgres::Backend you must pass PG::Connect…
2 parents da3369e + bf69ff5 commit a1ee11c

File tree

6 files changed

+131
-121
lines changed

6 files changed

+131
-121
lines changed
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require "refile"
22
Refile.configure do |config|
3-
config.store = Refile::Postgres::Backend.new(proc { ActiveRecord::Base.connection.raw_connection } )
3+
connection = lambda { |&blk| ActiveRecord::Base.connection_pool.with_connection { |con| blk.call(con.raw_connection) } }
4+
config.store = Refile::Postgres::Backend.new(connection)
45
end

lib/refile/postgres/backend.rb

Lines changed: 48 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ class Backend
88
DEFAULT_NAMESPACE = "default"
99
PG_LARGE_OBJECT_METADATA_TABLE = "pg_largeobject_metadata"
1010
READ_CHUNK_SIZE = 3000
11-
INIT_CONNECTION_ARG_ERROR_MSG = "When initializing new Refile::Postgres::Backend first argument should be an instance of PG::Connection or a lambda/proc that returns it. When using ActiveRecord it is available as ActiveRecord::Base.connection.raw_connection"
1211

1312
def initialize(connection_or_proc, max_size: nil, namespace: DEFAULT_NAMESPACE, registry_table: DEFAULT_REGISTRY_TABLE)
1413
@connection_or_proc = connection_or_proc
@@ -22,50 +21,43 @@ def initialize(connection_or_proc, max_size: nil, namespace: DEFAULT_NAMESPACE,
2221

2322
def registry_table
2423
unless @registry_table_validated
25-
connection.exec %{
26-
SELECT count(*) from pg_catalog.pg_tables
27-
WHERE tablename = '#{@registry_table}';
28-
} do |result|
29-
unless result[0]["count"].to_i > 0
30-
raise RegistryTableDoesNotExistError.new(%{Please create a table "#{@registry_table}" where backend could store list of attachments})
24+
with_connection do |connection|
25+
connection.exec_params("SELECT * FROM pg_catalog.pg_tables WHERE tablename = $1::varchar;", [@registry_table]) do |result|
26+
if result.count != 1
27+
raise RegistryTableDoesNotExistError.new(%{Please create a table "#{@registry_table}" where backend could store list of attachments})
28+
end
3129
end
3230
end
3331
@registry_table_validated = true
3432
end
3533
@registry_table
3634
end
3735

38-
def connection
39-
if has_active_connection?
40-
@connection
41-
else
42-
obtain_new_connection
43-
end
44-
end
45-
4636
verify_uploadable def upload(uploadable)
47-
oid = connection.lo_creat
48-
ensure_in_transaction do
49-
begin
50-
handle = connection.lo_open(oid, PG::INV_WRITE)
51-
connection.lo_truncate(handle, 0)
52-
buffer = "" # reuse the same buffer
53-
until uploadable.eof?
54-
uploadable.read(READ_CHUNK_SIZE, buffer)
55-
connection.lo_write(handle, buffer)
37+
with_connection do |connection|
38+
oid = connection.lo_creat
39+
ensure_in_transaction(connection) do
40+
begin
41+
handle = connection.lo_open(oid, PG::INV_WRITE)
42+
connection.lo_truncate(handle, 0)
43+
buffer = "" # reuse the same buffer
44+
until uploadable.eof?
45+
uploadable.read(READ_CHUNK_SIZE, buffer)
46+
connection.lo_write(handle, buffer)
47+
end
48+
uploadable.close
49+
connection.exec_params("INSERT INTO #{registry_table} VALUES ($1::integer, $2::varchar);", [oid, namespace])
50+
Refile::File.new(self, oid.to_s)
51+
ensure
52+
connection.lo_close(handle)
5653
end
57-
uploadable.close
58-
connection.exec_params("INSERT INTO #{registry_table} VALUES ($1::integer, $2::varchar);", [oid, namespace])
59-
Refile::File.new(self, oid.to_s)
60-
ensure
61-
connection.lo_close(handle)
6254
end
6355
end
6456
end
6557

6658
verify_id def open(id)
6759
if exists?(id)
68-
Reader.new(connection, id)
60+
Reader.new(@connection_or_proc, id)
6961
else
7062
raise ArgumentError.new("No such attachment with ID: #{id}")
7163
end
@@ -84,14 +76,16 @@ def connection
8476
end
8577

8678
verify_id def exists?(id)
87-
connection.exec_params(%{
88-
SELECT count(*) FROM #{registry_table}
89-
INNER JOIN #{PG_LARGE_OBJECT_METADATA_TABLE}
90-
ON #{registry_table}.id = #{PG_LARGE_OBJECT_METADATA_TABLE}.oid
91-
WHERE #{registry_table}.namespace = $1::varchar
92-
AND #{registry_table}.id = $2::integer;
93-
}, [namespace, id.to_s.to_i]) do |result|
94-
result[0]["count"].to_i > 0
79+
with_connection do |connection|
80+
connection.exec_params(%{
81+
SELECT count(*) FROM #{registry_table}
82+
INNER JOIN #{PG_LARGE_OBJECT_METADATA_TABLE}
83+
ON #{registry_table}.id = #{PG_LARGE_OBJECT_METADATA_TABLE}.oid
84+
WHERE #{registry_table}.namespace = $1::varchar
85+
AND #{registry_table}.id = $2::integer;
86+
}, [namespace, id.to_s.to_i]) do |result|
87+
result[0]["count"].to_i > 0
88+
end
9589
end
9690
end
9791

@@ -105,43 +99,34 @@ def connection
10599

106100
verify_id def delete(id)
107101
if exists?(id)
108-
ensure_in_transaction do
109-
connection.lo_unlink(id.to_s.to_i)
110-
connection.exec_params("DELETE FROM #{registry_table} WHERE id = $1::integer;", [id])
102+
with_connection do |connection|
103+
ensure_in_transaction(connection) do
104+
connection.lo_unlink(id.to_s.to_i)
105+
connection.exec_params("DELETE FROM #{registry_table} WHERE id = $1::integer;", [id])
106+
end
111107
end
112108
end
113109
end
114110

115111
def clear!(confirm = nil)
116112
raise Refile::Confirm unless confirm == :confirm
117113
registry_table
118-
ensure_in_transaction do
119-
connection.exec_params(%{
120-
SELECT * FROM #{registry_table}
121-
INNER JOIN #{PG_LARGE_OBJECT_METADATA_TABLE} ON #{registry_table}.id = #{PG_LARGE_OBJECT_METADATA_TABLE}.oid
122-
WHERE #{registry_table}.namespace = $1::varchar;
123-
}, [namespace]) do |result|
124-
result.each_row do |row|
125-
connection.lo_unlink(row[0].to_s.to_i)
114+
with_connection do |connection|
115+
ensure_in_transaction(connection) do
116+
connection.exec_params(%{
117+
SELECT * FROM #{registry_table}
118+
INNER JOIN #{PG_LARGE_OBJECT_METADATA_TABLE} ON #{registry_table}.id = #{PG_LARGE_OBJECT_METADATA_TABLE}.oid
119+
WHERE #{registry_table}.namespace = $1::varchar;
120+
}, [namespace]) do |result|
121+
result.each_row do |row|
122+
connection.lo_unlink(row[0].to_s.to_i)
123+
end
126124
end
125+
connection.exec_params("DELETE FROM #{registry_table} WHERE namespace = $1::varchar;", [namespace])
127126
end
128-
connection.exec_params("DELETE FROM #{registry_table} WHERE namespace = $1::varchar;", [namespace])
129127
end
130128
end
131129

132-
private
133-
134-
def has_active_connection?
135-
@connection && !@connection.finished?
136-
end
137-
138-
def obtain_new_connection
139-
candidate = @connection_or_proc.is_a?(Proc) ? @connection_or_proc.call : @connection_or_proc
140-
unless candidate.is_a?(PG::Connection)
141-
raise ArgumentError.new(INIT_CONNECTION_ARG_ERROR_MSG)
142-
end
143-
@connection = candidate
144-
end
145130
end
146131
end
147132
end

lib/refile/postgres/backend/reader.rb

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,52 +4,66 @@ class Backend
44
class Reader
55
include SmartTransaction
66

7-
def initialize(connection, oid)
8-
@connection = connection
7+
def initialize(connection_or_proc, oid)
8+
@connection_or_proc = connection_or_proc
99
@oid = oid.to_s.to_i
1010
@closed = false
1111
@pos = 0
1212
end
1313

14-
attr_reader :connection, :oid, :pos
14+
attr_reader :oid, :pos
1515

1616
def read(length = nil, buffer = nil)
1717
result = if length
1818
raise "closed" if @closed
19-
smart_transaction do |descriptor|
20-
connection.lo_lseek(descriptor, @pos, PG::SEEK_SET)
21-
data = connection.lo_read(descriptor, length)
22-
@pos = connection.lo_tell(descriptor)
23-
data
19+
with_connection do |connection|
20+
smart_transaction(connection) do |descriptor|
21+
connection.lo_lseek(descriptor, @pos, PG::SEEK_SET)
22+
data = connection.lo_read(descriptor, length)
23+
@pos = connection.lo_tell(descriptor)
24+
data
25+
end
2426
end
2527
else
26-
smart_transaction do |descriptor|
27-
connection.lo_read(descriptor, size)
28+
with_connection do |connection|
29+
smart_transaction(connection) do |descriptor|
30+
connection.lo_read(descriptor, size)
31+
end
2832
end
2933
end
3034
buffer.replace(result) if buffer and result
3135
result
3236
end
3337

3438
def eof?
35-
smart_transaction do |descriptor|
36-
@pos == size
39+
with_connection do |connection|
40+
smart_transaction(connection) do |descriptor|
41+
@pos == size
42+
end
3743
end
3844
end
3945

4046
def size
41-
@size ||= smart_transaction do |descriptor|
42-
current_position = connection.lo_tell(descriptor)
43-
end_position = connection.lo_lseek(descriptor, 0, PG::SEEK_END)
44-
connection.lo_lseek(descriptor, current_position, PG::SEEK_SET)
45-
end_position
46-
end
47+
@size ||= fetch_size
4748
end
4849

4950
def close
5051
@closed = true
5152
end
5253

54+
private
55+
56+
def fetch_size
57+
with_connection do |connection|
58+
smart_transaction(connection) do |descriptor|
59+
current_position = connection.lo_tell(descriptor)
60+
end_position = connection.lo_lseek(descriptor, 0, PG::SEEK_END)
61+
connection.lo_lseek(descriptor, current_position, PG::SEEK_SET)
62+
end_position
63+
end
64+
end
65+
end
66+
5367
end
5468
end
5569
end
Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
module Refile
22
module Postgres
33
module SmartTransaction
4-
4+
INIT_CONNECTION_ARG_ERROR_MSG = "When initializing new Refile::Postgres::Backend first argument should be an instance of PG::Connection or a lambda/proc that yields it."
55
PQTRANS_INTRANS = 2 # (idle, within transaction block)
66

7-
def smart_transaction
7+
def smart_transaction(connection)
88
result = nil
9-
ensure_in_transaction do
9+
ensure_in_transaction(connection) do
1010
begin
1111
handle = connection.lo_open(oid)
1212
result = yield handle
@@ -16,7 +16,7 @@ def smart_transaction
1616
result
1717
end
1818

19-
def ensure_in_transaction
19+
def ensure_in_transaction(connection)
2020
if connection.transaction_status == PQTRANS_INTRANS
2121
yield
2222
else
@@ -26,6 +26,26 @@ def ensure_in_transaction
2626
end
2727
end
2828

29+
def with_connection
30+
if @connection_or_proc.is_a?(PG::Connection)
31+
yield @connection_or_proc
32+
else
33+
if @connection_or_proc.is_a?(Proc)
34+
block_has_been_executed = false
35+
value = nil
36+
@connection_or_proc.call do |connection|
37+
block_has_been_executed = true
38+
raise ArgumentError.new(INIT_CONNECTION_ARG_ERROR_MSG) unless connection.is_a?(PG::Connection)
39+
value = yield connection
40+
end
41+
raise ArgumentError.new(INIT_CONNECTION_ARG_ERROR_MSG) unless block_has_been_executed
42+
value
43+
else
44+
raise ArgumentError.new(INIT_CONNECTION_ARG_ERROR_MSG)
45+
end
46+
end
47+
end
48+
2949
end
3050
end
3151
end

refile-postgres.gemspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ Gem::Specification.new do |spec|
1818
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
1919
spec.require_paths = ["lib"]
2020

21-
spec.add_dependency "refile", "~> 0.6.1"
21+
spec.add_dependency "refile", "~> 0.6.2"
2222
spec.add_dependency "pg"
2323

2424
spec.add_development_dependency "bundler"
2525
spec.add_development_dependency "rspec"
2626
spec.add_development_dependency "webmock"
2727
spec.add_development_dependency "pry"
2828
spec.add_development_dependency "pry-stack_explorer"
29-
spec.add_development_dependency "rails", "~> 4.2.1"
29+
spec.add_development_dependency "rails", "~> 4.2.5"
3030
spec.add_development_dependency "rake"
3131
spec.add_development_dependency "codeclimate-test-reporter"
3232
end
Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,37 @@
11
require "spec_helper"
22

3-
RSpec.describe Refile::Postgres::Backend do
4-
let(:connection_or_proc) { PG.connect(dbname: 'refile_test') }
3+
describe Refile::Postgres::Backend do
4+
let(:connection) { PG.connect(dbname: 'refile_test') }
55
let(:backend) { Refile::Postgres::Backend.new(connection_or_proc, max_size: 100) }
6-
it_behaves_like :backend
76

87
context "Connection tests" do
9-
def connection
10-
PG.connect(dbname: 'refile_test')
8+
context "when not using procs and providing PG::Connection directly" do
9+
let(:connection_or_proc) { connection }
10+
it "reuses the same PG::Connection" do
11+
expect(backend.with_connection { |c| c.db }).to eq("refile_test")
12+
end
1113
end
1214

1315
context "when using proc" do
14-
def connection_or_proc
15-
proc { connection }
16-
end
17-
18-
it "reuses the same PG::Connection if connection is ok" do
19-
expect(backend.connection).to eq(backend.connection)
16+
context "when lambda does not yield a block but returns connection" do
17+
let(:connection_or_proc) { lambda { connection } }
18+
it "raises argument error" do
19+
expect {
20+
backend.with_connection { |c| c.db }
21+
}.to raise_error(ArgumentError, "When initializing new Refile::Postgres::Backend first argument should be an instance of PG::Connection or a lambda/proc that yields it.")
22+
end
2023
end
21-
22-
it "executes proc and obtains new connection if old one is closed" do
23-
old = backend.connection
24-
old.close
25-
expect(backend.connection).not_to eq(old)
26-
expect(backend.connection.finished?).to be_falsey
24+
context "when lambda does yield a PG::Connection" do
25+
let(:connection_or_proc) { lambda { |&blk| blk.call(connection) } }
26+
it "is usable in queries" do
27+
expect(backend.with_connection { |c| c.db }).to eq("refile_test")
28+
end
2729
end
2830
end
31+
end
2932

30-
context "when not using procs and providing PG::Connection directly" do
31-
def connection_or_proc
32-
connection
33-
end
34-
35-
it "reuses the same PG::Connection" do
36-
expect(backend.connection).to eq(backend.connection)
37-
end
38-
39-
it "continues to use old connection if the old one is closed" do
40-
old = backend.connection
41-
old.close
42-
expect(backend.connection).to eq(old)
43-
expect(backend.connection.finished?).to be_truthy
44-
end
45-
end
33+
context "Refile Provided tests" do
34+
let(:connection_or_proc) { connection }
35+
it_behaves_like :backend
4636
end
4737
end

0 commit comments

Comments
 (0)