database_specification 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/database_specification.rb +196 -0
- data/lib/database_specification/version.rb +4 -0
- data/spec/database_specification_spec.rb +98 -0
- metadata +100 -0
@@ -0,0 +1,196 @@
|
|
1
|
+
require "database_specification/version"
|
2
|
+
require 'uri'
|
3
|
+
require 'cgi'
|
4
|
+
|
5
|
+
# Translate beween different ways of describing a database connection
|
6
|
+
# Currently supports
|
7
|
+
#
|
8
|
+
# - ActiveRecord hash
|
9
|
+
# - Sequel hash
|
10
|
+
# - URL (Sequel/Heroku)
|
11
|
+
#
|
12
|
+
# Construct an object with the appropriate from-database constructor,
|
13
|
+
# and then use the appopriate to-database accessor.
|
14
|
+
class DatabaseSpecification
|
15
|
+
# The database driver type: postgres, sqlite, etc.
|
16
|
+
attr_reader :adapter
|
17
|
+
# Database name within the server
|
18
|
+
attr_reader :database
|
19
|
+
attr_reader :user
|
20
|
+
attr_reader :password
|
21
|
+
attr_reader :host
|
22
|
+
attr_reader :port
|
23
|
+
# Misc extra options, ex: pool, timeout
|
24
|
+
attr_reader :options
|
25
|
+
|
26
|
+
# How we store the information internally
|
27
|
+
STANDARD = [
|
28
|
+
:adapter,
|
29
|
+
:database,
|
30
|
+
:user,
|
31
|
+
:username,
|
32
|
+
:password,
|
33
|
+
:host,
|
34
|
+
:port,
|
35
|
+
]
|
36
|
+
|
37
|
+
# ActiveRecord constructor
|
38
|
+
# @param [Hash] config ActiveRecord style db hash
|
39
|
+
def self.active_record(config)
|
40
|
+
new.from_active_record(config)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Sequel hash constructor
|
44
|
+
# @param [Hash] config Sequel style db hash
|
45
|
+
def self.sequel(config)
|
46
|
+
new.from_sequel(config)
|
47
|
+
end
|
48
|
+
|
49
|
+
# URL constructor (Sequel/Heroku)
|
50
|
+
# @param [String] url
|
51
|
+
def self.url(url)
|
52
|
+
new.from_url(url)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Performs the work of setting up from an ActiveRecord style hash
|
56
|
+
# @param [Hash] config ActiveRecord style db hash
|
57
|
+
# @return [DatabaseSpecification]
|
58
|
+
def from_active_record(config)
|
59
|
+
@adapter = ar_to_sequel(config[:adapter])
|
60
|
+
@database = config[:database]
|
61
|
+
@user = config[:username]
|
62
|
+
@password = config[:password]
|
63
|
+
@host = config[:host]
|
64
|
+
@port = config[:port]
|
65
|
+
@options = (config.keys - STANDARD).map {|k| [k, config[k]]}
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# Performs the work of setting up from a Sequel style hash
|
70
|
+
# @param [Hash] config Sequel style db hash
|
71
|
+
# @return [DatabaseSpecification]
|
72
|
+
def from_sequel(config)
|
73
|
+
@adapter = config[:adapter]
|
74
|
+
@database = config[:database]
|
75
|
+
@user = config[:user]
|
76
|
+
@password = config[:password]
|
77
|
+
@host = config[:host]
|
78
|
+
@port = config[:port]
|
79
|
+
@options = (config.keys - STANDARD).map {|k| [k, config[k]]}
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
# Performs the work of setging up from a url
|
84
|
+
# @param [String] url
|
85
|
+
# @return [DatabaseSpecification]
|
86
|
+
def from_url(url)
|
87
|
+
begin
|
88
|
+
uri = URI.parse(url)
|
89
|
+
rescue URI::InvalidURIError
|
90
|
+
raise "Invalid DATABASE_URL"
|
91
|
+
end
|
92
|
+
@adapter = uri.scheme
|
93
|
+
@database = uri.path.split('/')[1]
|
94
|
+
@user = uri.user
|
95
|
+
@password = uri.password
|
96
|
+
@host = uri.host
|
97
|
+
@port = uri.port
|
98
|
+
@options = parse_query(uri.query)
|
99
|
+
|
100
|
+
if @adapter == 'sqlite'
|
101
|
+
@database = [@host, @database].join('/')
|
102
|
+
@host = nil
|
103
|
+
end
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
# @return [Hash] ActiveRecord style hash
|
108
|
+
def active_record
|
109
|
+
Hash[[
|
110
|
+
[:adapter, sequel_to_ar(adapter)],
|
111
|
+
[:database, database],
|
112
|
+
[:username, user],
|
113
|
+
[:password, password],
|
114
|
+
[:host, host],
|
115
|
+
[:port, port],
|
116
|
+
].select {|x| x.last} + options]
|
117
|
+
end
|
118
|
+
|
119
|
+
# @return [Hash] Sequel style hash
|
120
|
+
def sequel
|
121
|
+
Hash[[
|
122
|
+
[:adapter, adapter],
|
123
|
+
[:database, database],
|
124
|
+
[:user, user],
|
125
|
+
[:password, password],
|
126
|
+
[:host, host],
|
127
|
+
[:port, port],
|
128
|
+
].select {|x| x.last} + options]
|
129
|
+
end
|
130
|
+
|
131
|
+
# @return [String] DB url with query paramters
|
132
|
+
def url
|
133
|
+
[url_bare, query].compact.join('?')
|
134
|
+
end
|
135
|
+
|
136
|
+
# @return [String] DB url without query parameters
|
137
|
+
def url_bare
|
138
|
+
if adapter == 'postgres'
|
139
|
+
h = host || 'localhost'
|
140
|
+
else
|
141
|
+
h = host
|
142
|
+
end
|
143
|
+
uri = URI::Generic.build({
|
144
|
+
:scheme => adapter,
|
145
|
+
:userinfo => userinfo,
|
146
|
+
:host => h,
|
147
|
+
:port => port,
|
148
|
+
:path => '/' + database, # wants an absolute component
|
149
|
+
}).to_s
|
150
|
+
# URI is overly agressive in removing leading /
|
151
|
+
if adapter == 'sqlite'
|
152
|
+
uri.gsub!(':/', '://')
|
153
|
+
end
|
154
|
+
uri
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
def ar_to_sequel(adapter)
|
159
|
+
case adapter
|
160
|
+
when 'sqlite3'; 'sqlite'
|
161
|
+
when 'postgresql'; 'postgres'
|
162
|
+
else; adapter
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def sequel_to_ar(adapter)
|
167
|
+
case adapter
|
168
|
+
when 'sqlite'; 'sqlite3'
|
169
|
+
when 'postgres'; 'postgresql'
|
170
|
+
else; adapter
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def userinfo
|
175
|
+
if user || password
|
176
|
+
[user, password].join(':')
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def query
|
181
|
+
q = options.map do |k,v|
|
182
|
+
k.to_s + '=' + v.to_s
|
183
|
+
end.join('&')
|
184
|
+
q unless q.empty?
|
185
|
+
end
|
186
|
+
|
187
|
+
def parse_query(q)
|
188
|
+
CGI.parse(q || '').to_a.map do |k, v|
|
189
|
+
n = v.first
|
190
|
+
if n.match(/\d+/)
|
191
|
+
n = n.to_i
|
192
|
+
end
|
193
|
+
[k.to_sym, n]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'database_specification'
|
2
|
+
|
3
|
+
describe DatabaseSpecification do
|
4
|
+
let(:ar_sqlite) do
|
5
|
+
{
|
6
|
+
:adapter => 'sqlite3',
|
7
|
+
:database => 'db/development.sqlite3',
|
8
|
+
:pool => 5,
|
9
|
+
:timeout => 5000,
|
10
|
+
}
|
11
|
+
end
|
12
|
+
let(:sequel_sqlite) do
|
13
|
+
{
|
14
|
+
:adapter => 'sqlite',
|
15
|
+
:database => 'db/development.sqlite3',
|
16
|
+
:pool => 5,
|
17
|
+
:timeout => 5000,
|
18
|
+
}
|
19
|
+
end
|
20
|
+
let(:url_sqlite) {'sqlite://db/development.sqlite3?pool=5&timeout=5000'}
|
21
|
+
let(:url_sqlite_bare) {'sqlite://db/development.sqlite3'}
|
22
|
+
let(:ar_postgres) do
|
23
|
+
{
|
24
|
+
:adapter => 'postgresql',
|
25
|
+
:database => 'pacha_development',
|
26
|
+
:username => 'user',
|
27
|
+
:password => 'pass',
|
28
|
+
:host => 'localhost',
|
29
|
+
:port => 1111,
|
30
|
+
}
|
31
|
+
end
|
32
|
+
let(:ar_postgres_short) do
|
33
|
+
{
|
34
|
+
:adapter => 'postgresql',
|
35
|
+
:database => 'pacha_development',
|
36
|
+
}
|
37
|
+
end
|
38
|
+
let(:url_postgres_short) {'postgres://localhost/pacha_development'}
|
39
|
+
let(:sequel_postgres) do
|
40
|
+
{
|
41
|
+
:adapter => 'postgres',
|
42
|
+
:database => 'pacha_development',
|
43
|
+
:user => 'user',
|
44
|
+
:password => 'pass',
|
45
|
+
:host => 'localhost',
|
46
|
+
:port => 1111,
|
47
|
+
}
|
48
|
+
end
|
49
|
+
let(:url_postgres) {'postgres://user:pass@localhost:1111/pacha_development'}
|
50
|
+
|
51
|
+
describe 'ActiveRecord' do
|
52
|
+
describe 'sqlite' do
|
53
|
+
subject {DatabaseSpecification.active_record(ar_sqlite)}
|
54
|
+
its(:active_record) {should == ar_sqlite}
|
55
|
+
its(:sequel) {should == sequel_sqlite}
|
56
|
+
its(:url) {should == url_sqlite}
|
57
|
+
its(:url_bare) {should == url_sqlite_bare}
|
58
|
+
end
|
59
|
+
describe 'postgres' do
|
60
|
+
subject {DatabaseSpecification.active_record(ar_postgres)}
|
61
|
+
its(:active_record) {should == ar_postgres}
|
62
|
+
its(:sequel) {should == sequel_postgres}
|
63
|
+
its(:url) {should == url_postgres}
|
64
|
+
end
|
65
|
+
describe 'postgres short' do
|
66
|
+
subject {DatabaseSpecification.active_record(ar_postgres_short)}
|
67
|
+
its(:url) {should == url_postgres_short}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
describe 'Sequel' do
|
71
|
+
describe 'sqlite' do
|
72
|
+
subject {DatabaseSpecification.sequel(sequel_sqlite)}
|
73
|
+
its(:active_record) {should == ar_sqlite}
|
74
|
+
its(:sequel) {should == sequel_sqlite}
|
75
|
+
its(:url) {should == url_sqlite}
|
76
|
+
end
|
77
|
+
describe 'postgres' do
|
78
|
+
subject {DatabaseSpecification.sequel(sequel_postgres)}
|
79
|
+
its(:active_record) {should == ar_postgres}
|
80
|
+
its(:sequel) {should == sequel_postgres}
|
81
|
+
its(:url) {should == url_postgres}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
describe 'url' do
|
85
|
+
describe 'sqlite' do
|
86
|
+
subject {DatabaseSpecification.url(url_sqlite)}
|
87
|
+
its(:active_record) {should == ar_sqlite}
|
88
|
+
its(:sequel) {should == sequel_sqlite}
|
89
|
+
its(:url) {should == url_sqlite}
|
90
|
+
end
|
91
|
+
describe 'postgres' do
|
92
|
+
subject {DatabaseSpecification.url(url_postgres)}
|
93
|
+
its(:active_record) {should == ar_postgres}
|
94
|
+
its(:sequel) {should == sequel_postgres}
|
95
|
+
its(:url) {should == url_postgres}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: database_specification
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Justin Love
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-22 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: guard-rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Translate between different ways of specifying a database connection. Translates
|
63
|
+
between ActiveRecord-hash, Sequel-hash, and urls used by Sequel and Heroku
|
64
|
+
email:
|
65
|
+
- git@JustinLove.name
|
66
|
+
executables: []
|
67
|
+
extensions: []
|
68
|
+
extra_rdoc_files: []
|
69
|
+
files:
|
70
|
+
- lib/database_specification.rb
|
71
|
+
- lib/database_specification/version.rb
|
72
|
+
- spec/database_specification_spec.rb
|
73
|
+
homepage: ''
|
74
|
+
licenses: []
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project: database_specification
|
93
|
+
rubygems_version: 1.8.24
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: Translate between different ways of specifying a database connection - eg
|
97
|
+
AR-URL
|
98
|
+
test_files:
|
99
|
+
- spec/database_specification_spec.rb
|
100
|
+
has_rdoc:
|