database_specification 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,4 @@
1
+ class DatabaseSpecification
2
+ # version number
3
+ VERSION = "0.0.1"
4
+ 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: