db-hijacker 0.3.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/Gemfile +3 -0
- data/Gemfile.lock +61 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +67 -0
- data/Rakefile +33 -0
- data/VERSION +1 -0
- data/example_root_schema.rb +16 -0
- data/hijacker.gemspec +70 -0
- data/init.rb +7 -0
- data/install.rb +1 -0
- data/lib/hijacker.rb +218 -0
- data/lib/hijacker/active_record_ext.rb +8 -0
- data/lib/hijacker/alias.rb +7 -0
- data/lib/hijacker/controller_methods.rb +42 -0
- data/lib/hijacker/database.rb +91 -0
- data/lib/hijacker/middleware.rb +18 -0
- data/spec/hijacker/alias_spec.rb +7 -0
- data/spec/hijacker/database_spec.rb +46 -0
- data/spec/hijacker/middleware_spec.rb +33 -0
- data/spec/hijacker_spec.rb +176 -0
- data/spec/spec_helper.rb +28 -0
- data/tasks/hijacker_tasks.rake +4 -0
- data/uninstall.rb +1 -0
- metadata +205 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
db-hijacker (0.3.1)
|
5
|
+
rails (~> 2.3.14)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
actionmailer (2.3.14)
|
11
|
+
actionpack (= 2.3.14)
|
12
|
+
actionpack (2.3.14)
|
13
|
+
activesupport (= 2.3.14)
|
14
|
+
rack (~> 1.1.0)
|
15
|
+
activerecord (2.3.14)
|
16
|
+
activesupport (= 2.3.14)
|
17
|
+
activeresource (2.3.14)
|
18
|
+
activesupport (= 2.3.14)
|
19
|
+
activesupport (2.3.14)
|
20
|
+
columnize (0.3.6)
|
21
|
+
diff-lcs (1.1.3)
|
22
|
+
linecache (0.46)
|
23
|
+
rbx-require-relative (> 0.0.4)
|
24
|
+
rack (1.1.3)
|
25
|
+
rack-test (0.6.1)
|
26
|
+
rack (>= 1.0)
|
27
|
+
rails (2.3.14)
|
28
|
+
actionmailer (= 2.3.14)
|
29
|
+
actionpack (= 2.3.14)
|
30
|
+
activerecord (= 2.3.14)
|
31
|
+
activeresource (= 2.3.14)
|
32
|
+
activesupport (= 2.3.14)
|
33
|
+
rake (>= 0.8.3)
|
34
|
+
rake (0.9.2.2)
|
35
|
+
rbx-require-relative (0.0.5)
|
36
|
+
rspec (2.8.0)
|
37
|
+
rspec-core (~> 2.8.0)
|
38
|
+
rspec-expectations (~> 2.8.0)
|
39
|
+
rspec-mocks (~> 2.8.0)
|
40
|
+
rspec-core (2.8.0)
|
41
|
+
rspec-expectations (2.8.0)
|
42
|
+
diff-lcs (~> 1.1.2)
|
43
|
+
rspec-mocks (2.8.0)
|
44
|
+
ruby-debug (0.10.4)
|
45
|
+
columnize (>= 0.1)
|
46
|
+
ruby-debug-base (~> 0.10.4.0)
|
47
|
+
ruby-debug-base (0.10.4)
|
48
|
+
linecache (>= 0.3)
|
49
|
+
sqlite3 (1.3.5)
|
50
|
+
|
51
|
+
PLATFORMS
|
52
|
+
ruby
|
53
|
+
|
54
|
+
DEPENDENCIES
|
55
|
+
db-hijacker!
|
56
|
+
rack (~> 1.1.0)
|
57
|
+
rack-test (~> 0.6.1)
|
58
|
+
rake (~> 0.9.2)
|
59
|
+
rspec (~> 2.8.0)
|
60
|
+
ruby-debug (~> 0.10.4)
|
61
|
+
sqlite3 (~> 1.3.5)
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
Hijacker
|
2
|
+
========
|
3
|
+
|
4
|
+
One application, multiple client databases. Although customizable, by default uses a combination of database and regular expression matching against the host domain to figure out which database to connect to.
|
5
|
+
|
6
|
+
Example
|
7
|
+
=======
|
8
|
+
|
9
|
+
class ApplicationController < ActionController::Base
|
10
|
+
hijack_connection({
|
11
|
+
# First thing it does is look for static routes. If this option
|
12
|
+
# exists and returns a string, it'll use that as the database
|
13
|
+
:static_routes => Proc.new {
|
14
|
+
case RAILS_ENV
|
15
|
+
when "development" then "site_development"
|
16
|
+
when "test" then "site_test"
|
17
|
+
end
|
18
|
+
},
|
19
|
+
# If it can't find the host in root.databases, it'll try pattern matching.
|
20
|
+
# Grabs $1 after a successful match.
|
21
|
+
:domain_patterns => [
|
22
|
+
/^(.+)\.domain\.com/, /^.+\.(.+)\..+/, /^(.+)\..+/
|
23
|
+
],
|
24
|
+
:after_hijack => Proc.new {
|
25
|
+
# Classes using acts_as_nested_set load the table info when preloading code in production.
|
26
|
+
# This is wrong 'cause at that point AR is connected to the root database.
|
27
|
+
Category.reset_column_information
|
28
|
+
}
|
29
|
+
})
|
30
|
+
end
|
31
|
+
|
32
|
+
For copy/pasters, a shorter version:
|
33
|
+
|
34
|
+
hijack_connection({
|
35
|
+
:static_routes => Proc.new { "site_#{Rails.env}" if !(Rails.env == "production") },
|
36
|
+
:domain_patterns => [/^(.+)\.site\.com/, /^.+\.(.+)\..+/, /^(.+)\..+/],
|
37
|
+
:after_hijack => Proc.new { Category.reset_column_information }
|
38
|
+
})
|
39
|
+
|
40
|
+
Configuration
|
41
|
+
=============
|
42
|
+
|
43
|
+
Your database.yml needs a "root" connection like so:
|
44
|
+
|
45
|
+
...
|
46
|
+
|
47
|
+
root: &root
|
48
|
+
database: root
|
49
|
+
<<: *defaults
|
50
|
+
|
51
|
+
production:
|
52
|
+
<<: *root
|
53
|
+
|
54
|
+
...
|
55
|
+
|
56
|
+
Other parts of database.yml will remain the same (development, test) but production
|
57
|
+
apps will initially start up on this root database, then hijack when the first connection
|
58
|
+
comes in.
|
59
|
+
|
60
|
+
Running tests
|
61
|
+
=============
|
62
|
+
|
63
|
+
To run the tests, just invoke RSpec:
|
64
|
+
|
65
|
+
rspec spec
|
66
|
+
|
67
|
+
Copyright (c) 2012 Michael Xavier, Donald Plummer, Woody Peterson, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
desc 'Test the hijacker plugin.'
|
9
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
10
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Generate documentation for the hijacker plugin.'
|
14
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
15
|
+
rdoc.rdoc_dir = 'rdoc'
|
16
|
+
rdoc.title = 'Hijacker'
|
17
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
18
|
+
rdoc.rdoc_files.include('README')
|
19
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
20
|
+
end
|
21
|
+
|
22
|
+
begin
|
23
|
+
require 'jeweler'
|
24
|
+
Jeweler::Tasks.new do |gemspec|
|
25
|
+
gemspec.name = "hijacker"
|
26
|
+
gemspec.summary = "One application, multiple client databases"
|
27
|
+
gemspec.description = "Allows a single Rails appliation to access many different databases"
|
28
|
+
gemspec.email = "woody@crystalcommerce.com"
|
29
|
+
gemspec.authors = ["Woody Peterson"]
|
30
|
+
end
|
31
|
+
rescue LoadError
|
32
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
33
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,16 @@
|
|
1
|
+
ActiveRecord::Schema.define(:version => 1) do
|
2
|
+
create_table "databases", :force => true do |t|
|
3
|
+
t.string "database"
|
4
|
+
t.integer "master_id"
|
5
|
+
end
|
6
|
+
|
7
|
+
add_index "databases", "database"
|
8
|
+
add_index "databases", "master_id"
|
9
|
+
|
10
|
+
create_table "aliases", :force => true do |t|
|
11
|
+
t.integer "database_id"
|
12
|
+
t.string "name"
|
13
|
+
end
|
14
|
+
|
15
|
+
add_index "aliases", "name"
|
16
|
+
end
|
data/hijacker.gemspec
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{db-hijacker}
|
5
|
+
s.homepage = "https://github.com/crystalcommerce/hijacker"
|
6
|
+
s.version = "0.3.1"
|
7
|
+
|
8
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
9
|
+
s.authors = ["Michael Xavier", "Donald Plummer", "Woody Peterson"]
|
10
|
+
s.date = %q{2012-03-21}
|
11
|
+
s.description = %q{Allows a single Rails appliation to access many different databases}
|
12
|
+
s.email = %q{developers@crystalcommerce.com}
|
13
|
+
s.add_dependency("rails", "~>2.3.14")
|
14
|
+
s.add_development_dependency("rake", "~>0.9.2")
|
15
|
+
s.add_development_dependency("rack-test", "~>0.6.1")
|
16
|
+
s.add_development_dependency("rack", "~>1.1.0")
|
17
|
+
s.add_development_dependency("rspec", "~>2.8.0")
|
18
|
+
s.add_development_dependency("sqlite3", "~>1.3.5")
|
19
|
+
s.add_development_dependency("ruby-debug", "~>0.10.4")
|
20
|
+
s.extra_rdoc_files = [
|
21
|
+
"README.rdoc"
|
22
|
+
]
|
23
|
+
s.files = %w{
|
24
|
+
Gemfile
|
25
|
+
Gemfile.lock
|
26
|
+
MIT-LICENSE
|
27
|
+
README.rdoc
|
28
|
+
Rakefile
|
29
|
+
VERSION
|
30
|
+
example_root_schema.rb
|
31
|
+
hijacker.gemspec
|
32
|
+
init.rb
|
33
|
+
install.rb
|
34
|
+
lib/hijacker.rb
|
35
|
+
lib/hijacker/active_record_ext.rb
|
36
|
+
lib/hijacker/alias.rb
|
37
|
+
lib/hijacker/controller_methods.rb
|
38
|
+
lib/hijacker/database.rb
|
39
|
+
lib/hijacker/middleware.rb
|
40
|
+
spec/hijacker/alias_spec.rb
|
41
|
+
spec/hijacker/database_spec.rb
|
42
|
+
spec/hijacker/middleware_spec.rb
|
43
|
+
spec/hijacker_spec.rb
|
44
|
+
spec/spec_helper.rb
|
45
|
+
tasks/hijacker_tasks.rake
|
46
|
+
uninstall.rb
|
47
|
+
}
|
48
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
49
|
+
s.require_paths = ["lib"]
|
50
|
+
s.rubygems_version = %q{1.8.15}
|
51
|
+
s.summary = %q{One application, multiple client databases}
|
52
|
+
s.test_files = %w{
|
53
|
+
spec/hijacker/alias_spec.rb
|
54
|
+
spec/hijacker/database_spec.rb
|
55
|
+
spec/hijacker/middleware_spec.rb
|
56
|
+
spec/hijacker_spec.rb
|
57
|
+
spec/spec_helper.rb
|
58
|
+
}
|
59
|
+
|
60
|
+
if s.respond_to? :specification_version then
|
61
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
62
|
+
s.specification_version = 3
|
63
|
+
|
64
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
65
|
+
else
|
66
|
+
end
|
67
|
+
else
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
data/init.rb
ADDED
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
data/lib/hijacker.rb
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'hijacker/active_record_ext'
|
2
|
+
require 'active_record'
|
3
|
+
require 'action_controller'
|
4
|
+
|
5
|
+
module Hijacker
|
6
|
+
class UnparseableURL < StandardError;end
|
7
|
+
class InvalidDatabase < StandardError;end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_accessor :config, :master, :sister
|
11
|
+
attr_writer :valid_routes
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.valid_routes
|
15
|
+
@valid_routes ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.connect_to_master(db_name)
|
19
|
+
connect(*Hijacker::Database.find_master_and_sister_for(db_name))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Manually establishes a new connection to the database.
|
23
|
+
#
|
24
|
+
# Background: every time rails gets information
|
25
|
+
# from the database, it uses the last established connection. So,
|
26
|
+
# although we've already established a connection to a "dummy" db
|
27
|
+
# ("crystal", in this case), if we establish a new connection, all
|
28
|
+
# subsequent database calls will use these settings instead (well,
|
29
|
+
# until it's called again when it gets another request).
|
30
|
+
#
|
31
|
+
# Note that you can manually call this from script/console (or wherever)
|
32
|
+
# to connect to the database you want, ex Hijacker.connect("database")
|
33
|
+
def self.connect(target_name, sister_name = nil, options = {})
|
34
|
+
raise InvalidDatabase, 'master cannot be nil' if target_name.nil?
|
35
|
+
|
36
|
+
target_name = target_name.downcase
|
37
|
+
sister_name = sister_name.downcase unless sister_name.nil?
|
38
|
+
|
39
|
+
return if already_connected?(target_name, sister_name) || test?
|
40
|
+
|
41
|
+
verify = options.fetch(:verify, Hijacker.do_hijacking?)
|
42
|
+
|
43
|
+
db_name = determine_database_name(target_name, sister_name, verify)
|
44
|
+
|
45
|
+
establish_connection_to_database(db_name)
|
46
|
+
|
47
|
+
check_connection
|
48
|
+
|
49
|
+
self.master = db_name
|
50
|
+
self.sister = sister_name
|
51
|
+
|
52
|
+
# don't cache sister site
|
53
|
+
cache_database_route(target_name, db_name) unless sister_name
|
54
|
+
|
55
|
+
connect_sister_site_models(target_name)
|
56
|
+
|
57
|
+
reenable_query_caching
|
58
|
+
|
59
|
+
self.config[:after_hijack].call if self.config[:after_hijack]
|
60
|
+
rescue
|
61
|
+
self.establish_root_connection
|
62
|
+
raise
|
63
|
+
end
|
64
|
+
|
65
|
+
# very small chance this will raise, but if it does, we will still handle it the
|
66
|
+
# same as +Hijacker.connect+ so we don't lock up the app.
|
67
|
+
#
|
68
|
+
# Also note that sister site models share a connection via minor management of
|
69
|
+
# AR's connection_pool stuff, and will use ActiveRecord::Base.connection_pool if
|
70
|
+
# we're not in a sister-site situation
|
71
|
+
def self.connect_sister_site_models(db)
|
72
|
+
return if db.nil?
|
73
|
+
|
74
|
+
sister_db_connection_pool = self.processing_sister_site? ? nil : ActiveRecord::Base.connection_pool
|
75
|
+
self.config[:sister_site_models].each do |model_name|
|
76
|
+
ar_model = model_name.constantize
|
77
|
+
|
78
|
+
if !sister_db_connection_pool
|
79
|
+
ar_model.establish_connection(self.root_connection.config.merge(:database => db))
|
80
|
+
begin
|
81
|
+
ar_model.connection
|
82
|
+
rescue
|
83
|
+
ar_model.establish_connection(self.root_connection.config)
|
84
|
+
raise Hijacker::InvalidDatabase, db
|
85
|
+
end
|
86
|
+
sister_db_connection_pool = ar_model.connection_pool
|
87
|
+
else
|
88
|
+
ActiveRecord::Base.connection_handler.connection_pools[model_name] = sister_db_connection_pool
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# connects the sister_site_models to +db+ while calling the block
|
94
|
+
# if +db+ and self.master differ
|
95
|
+
def self.temporary_sister_connect(db, &block)
|
96
|
+
processing_sister_site = (db != self.master && db != self.sister)
|
97
|
+
self.sister = db if processing_sister_site
|
98
|
+
self.connect_sister_site_models(db) if processing_sister_site
|
99
|
+
result = block.call
|
100
|
+
self.connect_sister_site_models(self.master) if processing_sister_site
|
101
|
+
self.sister = nil if processing_sister_site
|
102
|
+
return result
|
103
|
+
end
|
104
|
+
|
105
|
+
# maintains and returns a connection to the "dummy" database.
|
106
|
+
#
|
107
|
+
# The advantage of using this over just calling
|
108
|
+
# ActiveRecord::Base.establish_connection (without arguments) to reconnect
|
109
|
+
# to the dummy database is that reusing the same connection greatly reduces
|
110
|
+
# context switching overhead etc involved with establishing a connection to
|
111
|
+
# the database. It may seem trivial, but it actually seems to speed things
|
112
|
+
# up by ~ 1/3 for already fast requests (probably less noticeable on slower
|
113
|
+
# pages).
|
114
|
+
#
|
115
|
+
# Note: does not hijack, just returns the root connection (i.e. AR::Base will
|
116
|
+
# maintain its connection)
|
117
|
+
def self.root_connection
|
118
|
+
unless $hijacker_root_connection
|
119
|
+
current_config = ActiveRecord::Base.connection.config
|
120
|
+
ActiveRecord::Base.establish_connection('root') # establish with defaults
|
121
|
+
$hijacker_root_connection = ActiveRecord::Base.connection
|
122
|
+
ActiveRecord::Base.establish_connection(current_config) # reconnect, we don't intend to hijack
|
123
|
+
end
|
124
|
+
|
125
|
+
return $hijacker_root_connection
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.root_config
|
129
|
+
ActiveRecord::Base.configurations['root']
|
130
|
+
end
|
131
|
+
|
132
|
+
# this should establish a connection to a database containing the bare minimum
|
133
|
+
# for loading the app, usually a sessions table if using sql-based sessions.
|
134
|
+
def self.establish_root_connection
|
135
|
+
ActiveRecord::Base.establish_connection('root')
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.processing_sister_site?
|
139
|
+
!sister.nil?
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.master
|
143
|
+
@master || ActiveRecord::Base.configurations[ENV['RAILS_ENV']]['database']
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.current_client
|
147
|
+
sister || master
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.do_hijacking?
|
151
|
+
(Hijacker.config[:hosted_environments] || %w[staging production]).
|
152
|
+
include?(ENV['RAILS_ENV'])
|
153
|
+
end
|
154
|
+
|
155
|
+
# just calling establish_connection doesn't actually check to see if
|
156
|
+
# we've established a VALID connection. a call to connection will check
|
157
|
+
# this, and throw an error if the connection's invalid. It is important
|
158
|
+
# to catch the error and reconnect to a known valid database or rails
|
159
|
+
# will get stuck. This is because once we establish a connection to an
|
160
|
+
# invalid database, the next request will do a courteousy touch to the
|
161
|
+
# invalid database before reaching establish_connection and throw an error,
|
162
|
+
# preventing us from retrying to establish a valid connection and effectively
|
163
|
+
# locking us out of the app.
|
164
|
+
def self.check_connection
|
165
|
+
::ActiveRecord::Base.connection
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.test?
|
169
|
+
['test', 'cucumber'].include?(RAILS_ENV)
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def self.already_connected?(new_master, new_sister)
|
175
|
+
current_client == new_master && sister == new_sister
|
176
|
+
end
|
177
|
+
|
178
|
+
def self.determine_database_name(target_name, sister_name, verify)
|
179
|
+
if sister_name
|
180
|
+
raise(Hijacker::InvalidDatabase, sister_name) unless Hijacker::Database.exists?(:database => sister_name)
|
181
|
+
db_name = sister_name
|
182
|
+
elsif valid_routes[target_name]
|
183
|
+
db_name = valid_routes[target_name] # cached valid name
|
184
|
+
else
|
185
|
+
db_name = target_name unless verify
|
186
|
+
db_name ||= Hijacker::Alias.find_by_name(target_name).try(:database).try(:database)
|
187
|
+
db_name ||= Hijacker::Database.find_by_database(target_name).try(:database)
|
188
|
+
raise(Hijacker::InvalidDatabase, target_name) if db_name.nil?
|
189
|
+
end
|
190
|
+
db_name
|
191
|
+
end
|
192
|
+
|
193
|
+
def self.cache_database_route(requested_db_name, actual_db_name)
|
194
|
+
valid_routes[requested_db_name] ||= actual_db_name
|
195
|
+
end
|
196
|
+
|
197
|
+
def self.establish_connection_to_database(db_name)
|
198
|
+
hijacked_config = self.root_connection.config.dup
|
199
|
+
::ActiveRecord::Base.establish_connection(hijacked_config.merge(:database => db_name))
|
200
|
+
end
|
201
|
+
|
202
|
+
# This is a hack to get query caching back on. For some reason when we
|
203
|
+
# reconnect the database during the request, it stops doing query caching.
|
204
|
+
# We couldn't find how it's used by rails originally, but if you turn on
|
205
|
+
# query caching then start a cache block to initialize the @query_cache
|
206
|
+
# instance variable in the connection, AR will from then on build on that
|
207
|
+
# empty @query_cache hash. You have to do both 'cuz without the latter there
|
208
|
+
# will be no @query_cache available. Maybe someday we'll submit a ticket to Rails.
|
209
|
+
def self.reenable_query_caching
|
210
|
+
if ::ActionController::Base.perform_caching
|
211
|
+
::ActiveRecord::Base.connection.instance_variable_set("@query_cache_enabled", true)
|
212
|
+
::ActiveRecord::Base.connection.cache do;end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
require 'hijacker/database'
|
218
|
+
require 'hijacker/alias'
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Hijacker::ControllerMethods
|
2
|
+
module Instance
|
3
|
+
def hijack_connection
|
4
|
+
host = request.host
|
5
|
+
|
6
|
+
master, sister = determine_databases(host)
|
7
|
+
|
8
|
+
Hijacker.connect(master, sister)
|
9
|
+
|
10
|
+
return true
|
11
|
+
rescue Hijacker::InvalidDatabase => e
|
12
|
+
render_invalid_db
|
13
|
+
|
14
|
+
# If we've encountered a bad database connection, we don't want
|
15
|
+
# to continue rendering the rest of the before_filters on this, which it will
|
16
|
+
# try to do even when just rendering the bit of text above. If any filters
|
17
|
+
# return false, though, it will halt the filter chain.
|
18
|
+
return false
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns 2-member array of the main database to connect to, and the sister
|
22
|
+
# (sister will be nil if no master is found, which means we are on the master).
|
23
|
+
def determine_databases(host)
|
24
|
+
if Hijacker.do_hijacking?
|
25
|
+
Hijacker.config[:domain_patterns].find {|pattern| host =~ pattern}
|
26
|
+
client = $1
|
27
|
+
else # development, test, etc
|
28
|
+
client = ActiveRecord::Base.configurations[Rails.env]['database']
|
29
|
+
end
|
30
|
+
|
31
|
+
raise Hijacker::UnparseableURL, "cannot parse '#{host}'" if client.nil?
|
32
|
+
|
33
|
+
master, sister = Hijacker::Database.find_master_and_sister_for(client)
|
34
|
+
|
35
|
+
return [master, sister]
|
36
|
+
end
|
37
|
+
|
38
|
+
def render_invalid_db
|
39
|
+
render :text => "You do not appear to have an account with us (#{request.host})"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
class Hijacker::Database < ActiveRecord::Base
|
2
|
+
establish_connection(Hijacker.root_config)
|
3
|
+
|
4
|
+
validates_uniqueness_of :database
|
5
|
+
|
6
|
+
has_many :aliases, :class_name => "Hijacker::Alias"
|
7
|
+
belongs_to :master, :foreign_key => 'master_id', :class_name => 'Hijacker::Database'
|
8
|
+
has_many :sisters, :foreign_key => 'master_id', :class_name => 'Hijacker::Database'
|
9
|
+
|
10
|
+
def self.current
|
11
|
+
find(:first, :conditions => {:database => Hijacker.current_client})
|
12
|
+
end
|
13
|
+
|
14
|
+
# returns a string or nil
|
15
|
+
def self.find_master_for(client)
|
16
|
+
@masters ||= {}
|
17
|
+
@masters[client] ||= self.connection.select_values(
|
18
|
+
"SELECT master.database
|
19
|
+
FROM `databases` AS master, `databases` AS sister
|
20
|
+
WHERE sister.database = #{ActiveRecord::Base.connection.quote(client)}
|
21
|
+
AND sister.master_id = master.id"
|
22
|
+
).first
|
23
|
+
end
|
24
|
+
|
25
|
+
# always returns a master, sister can be nil
|
26
|
+
def self.find_master_and_sister_for(client)
|
27
|
+
master = self.find_master_for(client)
|
28
|
+
sister = master.nil? ? nil : client
|
29
|
+
master ||= client
|
30
|
+
|
31
|
+
return master, sister
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.shared_sites
|
35
|
+
self.find_shared_sites_for(Hijacker.current_client)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.connect_to_each_shared_site(&block)
|
39
|
+
connect_each(find_shared_sites_for(Hijacker.current_client), &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.connect_to_each_sister_site(&block)
|
43
|
+
sites = find_shared_sites_for(Hijacker.current_client)
|
44
|
+
sites.delete(Hijacker.current_client)
|
45
|
+
connect_each(sites, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.find_shared_sites_for(client)
|
49
|
+
@shared_sites ||= {}
|
50
|
+
return @shared_sites[client] if @shared_sites[client].present?
|
51
|
+
|
52
|
+
current = self.find(:first, :conditions => {:database => client})
|
53
|
+
master_id = current.master_id || current.id
|
54
|
+
|
55
|
+
@shared_sites[client] = self.connection.select_values(
|
56
|
+
"SELECT `database`
|
57
|
+
FROM `databases`
|
58
|
+
WHERE master_id = '#{master_id}' OR id = '#{master_id}'
|
59
|
+
ORDER BY id"
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.connect_each(sites = all.map(&:database))
|
64
|
+
original_database = Hijacker.current_client
|
65
|
+
begin
|
66
|
+
sites.each do |db|
|
67
|
+
Hijacker.connect_to_master(db)
|
68
|
+
yield db
|
69
|
+
end
|
70
|
+
ensure
|
71
|
+
begin
|
72
|
+
Hijacker.connect_to_master(original_database)
|
73
|
+
rescue Hijacker::InvalidDatabase
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.disabled_databases
|
79
|
+
Hijacker::Database.connection.select_values("SELECT `database_name` FROM `disabled_databases`")
|
80
|
+
end
|
81
|
+
|
82
|
+
def disable!
|
83
|
+
Hijacker::Database.connection.
|
84
|
+
execute("REPLACE INTO `disabled_databases` (`database_name`) VALUES ('#{database}')")
|
85
|
+
end
|
86
|
+
|
87
|
+
def enable!
|
88
|
+
Hijacker::Database.connection.
|
89
|
+
execute("DELETE FROM `disabled_databases` WHERE `database_name` = '#{database}'")
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Hijacker
|
2
|
+
class Middleware
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(env)
|
8
|
+
if env['HTTP_X_HIJACKER_DB'].present?
|
9
|
+
begin
|
10
|
+
Hijacker.connect(env['HTTP_X_HIJACKER_DB'])
|
11
|
+
rescue Hijacker::InvalidDatabase => e
|
12
|
+
return [404, {}, ""]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
@app.call(env)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Hijacker
|
4
|
+
describe Database do
|
5
|
+
it "has many aliases" do
|
6
|
+
lambda { subject.aliases }.should_not raise_error
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#connect_each" do
|
10
|
+
def db(name)
|
11
|
+
mock("#{name}_db", :database => name)
|
12
|
+
end
|
13
|
+
|
14
|
+
before (:each) do
|
15
|
+
Database.stub!(:all).and_return([ db("one"), db("two"), db("three") ])
|
16
|
+
Hijacker.stub!(:connect)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "Calls the block once for each database" do
|
20
|
+
count = 0
|
21
|
+
Database.connect_each do |db|
|
22
|
+
count += 1
|
23
|
+
end
|
24
|
+
count.should == Database.all.size
|
25
|
+
end
|
26
|
+
|
27
|
+
it "Passes the name of the database to the block" do
|
28
|
+
db_names = []
|
29
|
+
Database.connect_each do |db|
|
30
|
+
db_names << db
|
31
|
+
end
|
32
|
+
db_names.should == Database.all.map(&:database)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "connects to each of the database and reconnects to the original" do
|
36
|
+
original_db = Hijacker::Database.current
|
37
|
+
Hijacker.should_receive(:connect).exactly(Database.all.size + 1).times
|
38
|
+
Database.connect_each do |db|
|
39
|
+
# noop
|
40
|
+
end
|
41
|
+
|
42
|
+
Hijacker::Database.current.should == original_db
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rack/test'
|
3
|
+
|
4
|
+
require 'hijacker/middleware'
|
5
|
+
|
6
|
+
module Hijacker
|
7
|
+
describe Middleware do
|
8
|
+
include Rack::Test::Methods
|
9
|
+
|
10
|
+
def app
|
11
|
+
Rack::Builder.new do
|
12
|
+
use Hijacker::Middleware
|
13
|
+
run lambda { |env| [200, { 'blah' => 'blah' }, "success"] }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#call" do
|
18
|
+
context "When the 'X-Hijacker-DB' header is set" do
|
19
|
+
it "connects to the database from the header" do
|
20
|
+
Hijacker.should_receive(:connect).with("sample-db")
|
21
|
+
get '/',{}, 'HTTP_X_HIJACKER_DB' => 'sample-db'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "When the 'X-Hijacker-DB' header is not set" do
|
26
|
+
it "doesn't connect to any database" do
|
27
|
+
Hijacker.should_not_receive(:connect)
|
28
|
+
get '/',{}, "x-not-db-header" => "something"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Hijacker do
|
4
|
+
let(:hosted_environments) { %w[staging production] }
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
Hijacker.config = {
|
8
|
+
:hosted_environments => hosted_environments
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
let!(:master) { Hijacker::Database.create(:database => "master_db") }
|
13
|
+
|
14
|
+
describe ".find_shared_sites_for" do
|
15
|
+
let!(:sister) {Hijacker::Database.create(:database => "sister_db",
|
16
|
+
:master => master)}
|
17
|
+
let!(:sister2) {Hijacker::Database.create(:database => "sister_db2",
|
18
|
+
:master => master)}
|
19
|
+
let!(:unrelated) {Hijacker::Database.create(:database => "unrelated_db")}
|
20
|
+
let!(:unrelated_sister) {Hijacker::Database.create(:database => "unrelated_sister",
|
21
|
+
:master => unrelated)}
|
22
|
+
|
23
|
+
it "find shared sites given a master or sister database" do
|
24
|
+
dbs = ["master_db","sister_db","sister_db2"]
|
25
|
+
Hijacker::Database.find_shared_sites_for("master_db").should == dbs
|
26
|
+
Hijacker::Database.find_shared_sites_for("sister_db").should == dbs
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "class methods" do
|
31
|
+
subject { Hijacker }
|
32
|
+
|
33
|
+
describe ".connect" do
|
34
|
+
let(:perform_caching) { false }
|
35
|
+
|
36
|
+
|
37
|
+
before(:each) do
|
38
|
+
subject.master = nil
|
39
|
+
subject.sister = nil
|
40
|
+
subject.valid_routes = {}
|
41
|
+
subject.stub(:test?).and_return(false)
|
42
|
+
ActiveRecord::Base.stub(:establish_connection)
|
43
|
+
subject.stub(:root_connection).and_return(stub(:config => {}))
|
44
|
+
subject.stub(:connect_sister_site_models)
|
45
|
+
Hijacker.stub(:do_hijacking?).and_return(true)
|
46
|
+
::ActionController::Base.stub(:perform_caching).
|
47
|
+
and_return(perform_caching)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "raises an InvalidDatabase exception if master is nil" do
|
51
|
+
expect { subject.connect(nil) }.to raise_error(Hijacker::InvalidDatabase)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "establishes a connection merging in the db name" do
|
55
|
+
Hijacker::Database.create!(:database => 'elsewhere')
|
56
|
+
ActiveRecord::Base.should_receive(:establish_connection).
|
57
|
+
with({:database => 'elsewhere'})
|
58
|
+
subject.connect('elsewhere')
|
59
|
+
end
|
60
|
+
|
61
|
+
it "checks the connection by calling ActiveRecord::Base.connection" do
|
62
|
+
subject.should_receive(:check_connection)
|
63
|
+
subject.connect("master_db")
|
64
|
+
end
|
65
|
+
|
66
|
+
it "attempts to find an alias" do
|
67
|
+
Hijacker::Alias.should_receive(:find_by_name).with('alias_db')
|
68
|
+
subject.connect('alias_db') rescue nil
|
69
|
+
end
|
70
|
+
|
71
|
+
it "caches the valid route at the class level :(" do
|
72
|
+
subject.connect('master_db')
|
73
|
+
subject.valid_routes['master_db'].should == 'master_db'
|
74
|
+
end
|
75
|
+
|
76
|
+
context "there's an alias for the master" do
|
77
|
+
let!(:alias_db) { Hijacker::Alias.create(:name => 'alias_db', :database => master)}
|
78
|
+
|
79
|
+
it "connects with the alias" do
|
80
|
+
ActiveRecord::Base.should_receive(:establish_connection).
|
81
|
+
with({:database => 'master_db'})
|
82
|
+
subject.connect('alias_db')
|
83
|
+
end
|
84
|
+
|
85
|
+
it "caches the valid route at the class level :(" do
|
86
|
+
subject.connect('alias_db')
|
87
|
+
subject.valid_routes['alias_db'].should == 'master_db'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "ActiveRecord reports the connection is invalid" do
|
92
|
+
before(:each) do
|
93
|
+
subject.stub(:check_connection).and_raise("oh no you didn't")
|
94
|
+
end
|
95
|
+
|
96
|
+
it "reestablishes the root connection" do
|
97
|
+
ActiveRecord::Base.should_receive(:establish_connection).with('root')
|
98
|
+
subject.connect('master_db') rescue nil
|
99
|
+
end
|
100
|
+
|
101
|
+
it "re-raises the error" do
|
102
|
+
expect { subject.connect("master_db") }.to raise_error("oh no you didn't")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "already connected to database" do
|
107
|
+
before(:each) do
|
108
|
+
Hijacker.master = 'master_db'
|
109
|
+
Hijacker.sister = nil
|
110
|
+
end
|
111
|
+
|
112
|
+
after(:each) do
|
113
|
+
Hijacker.master = nil
|
114
|
+
end
|
115
|
+
|
116
|
+
it "does not reconnect" do
|
117
|
+
ActiveRecord::Base.should_not_receive(:establish_connection)
|
118
|
+
subject.connect('master_db')
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "sister site specified" do
|
123
|
+
let!(:sister_db) { Hijacker::Database.create!(:database => 'sister_db',
|
124
|
+
:master => master)}
|
125
|
+
it "does reconnect if specifying a different sister" do
|
126
|
+
ActiveRecord::Base.should_receive(:establish_connection)
|
127
|
+
subject.connect('master_db', 'sister_db')
|
128
|
+
end
|
129
|
+
|
130
|
+
it "does not cache the route" do
|
131
|
+
subject.connect('master_db', 'sister_db')
|
132
|
+
subject.valid_routes.should_not have_key('sister_db')
|
133
|
+
end
|
134
|
+
|
135
|
+
it "raises InvalidDatabase if the sister does not exist" do
|
136
|
+
expect do
|
137
|
+
subject.connect("master_db", "adopted_sister_db")
|
138
|
+
end.to raise_error(Hijacker::InvalidDatabase)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context "actioncontroller configured for caching" do
|
143
|
+
let(:perform_caching) { true }
|
144
|
+
|
145
|
+
it "enables the query cache on ActiveRecord::Base" do
|
146
|
+
subject.connect('master_db')
|
147
|
+
::ActiveRecord::Base.connection.query_cache_enabled.should be_true
|
148
|
+
end
|
149
|
+
|
150
|
+
it "calls cache on the connection" do
|
151
|
+
::ActiveRecord::Base.connection.should_receive(:cache)
|
152
|
+
subject.connect('master_db')
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
context "after_hijack call specified" do
|
157
|
+
let(:spy) { stub.as_null_object }
|
158
|
+
before(:each) do
|
159
|
+
Hijacker.config.merge!(:after_hijack => spy)
|
160
|
+
end
|
161
|
+
|
162
|
+
it "calls the callback" do
|
163
|
+
spy.should_receive(:call)
|
164
|
+
subject.connect('master_db')
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe ".check_connection" do
|
170
|
+
it "calls connection on ActiveRecord::Base" do
|
171
|
+
::ActiveRecord::Base.should_receive(:connection)
|
172
|
+
subject.check_connection
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/test_case'
|
4
|
+
require 'ruby-debug'
|
5
|
+
|
6
|
+
require 'active_record'
|
7
|
+
|
8
|
+
RAILS_ENV="test"
|
9
|
+
ENV['RAILS_ENV'] = 'test'
|
10
|
+
$:.unshift '../lib'
|
11
|
+
ActiveRecord::Base.configurations = {
|
12
|
+
"test" => {
|
13
|
+
:adapter => 'sqlite3',
|
14
|
+
:database => File.dirname(__FILE__) + "/test_database.sqlite3"
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
ActiveRecord::Base.establish_connection
|
19
|
+
require File.dirname(__FILE__) + "/../example_root_schema"
|
20
|
+
|
21
|
+
require 'hijacker'
|
22
|
+
|
23
|
+
RSpec.configure do |config|
|
24
|
+
config.before(:each) do
|
25
|
+
Hijacker::Database.delete_all
|
26
|
+
Hijacker::Alias.delete_all
|
27
|
+
end
|
28
|
+
end
|
data/uninstall.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Uninstall hook code here
|
metadata
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: db-hijacker
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 17
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 3
|
9
|
+
- 1
|
10
|
+
version: 0.3.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Michael Xavier
|
14
|
+
- Donald Plummer
|
15
|
+
- Woody Peterson
|
16
|
+
autorequire:
|
17
|
+
bindir: bin
|
18
|
+
cert_chain: []
|
19
|
+
|
20
|
+
date: 2012-03-21 00:00:00 Z
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: rails
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 31
|
31
|
+
segments:
|
32
|
+
- 2
|
33
|
+
- 3
|
34
|
+
- 14
|
35
|
+
version: 2.3.14
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id001
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rake
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 63
|
47
|
+
segments:
|
48
|
+
- 0
|
49
|
+
- 9
|
50
|
+
- 2
|
51
|
+
version: 0.9.2
|
52
|
+
type: :development
|
53
|
+
version_requirements: *id002
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: rack-test
|
56
|
+
prerelease: false
|
57
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 5
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
- 6
|
66
|
+
- 1
|
67
|
+
version: 0.6.1
|
68
|
+
type: :development
|
69
|
+
version_requirements: *id003
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rack
|
72
|
+
prerelease: false
|
73
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ~>
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
hash: 19
|
79
|
+
segments:
|
80
|
+
- 1
|
81
|
+
- 1
|
82
|
+
- 0
|
83
|
+
version: 1.1.0
|
84
|
+
type: :development
|
85
|
+
version_requirements: *id004
|
86
|
+
- !ruby/object:Gem::Dependency
|
87
|
+
name: rspec
|
88
|
+
prerelease: false
|
89
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ~>
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
hash: 47
|
95
|
+
segments:
|
96
|
+
- 2
|
97
|
+
- 8
|
98
|
+
- 0
|
99
|
+
version: 2.8.0
|
100
|
+
type: :development
|
101
|
+
version_requirements: *id005
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: sqlite3
|
104
|
+
prerelease: false
|
105
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
hash: 17
|
111
|
+
segments:
|
112
|
+
- 1
|
113
|
+
- 3
|
114
|
+
- 5
|
115
|
+
version: 1.3.5
|
116
|
+
type: :development
|
117
|
+
version_requirements: *id006
|
118
|
+
- !ruby/object:Gem::Dependency
|
119
|
+
name: ruby-debug
|
120
|
+
prerelease: false
|
121
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ~>
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
hash: 63
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
- 10
|
130
|
+
- 4
|
131
|
+
version: 0.10.4
|
132
|
+
type: :development
|
133
|
+
version_requirements: *id007
|
134
|
+
description: Allows a single Rails appliation to access many different databases
|
135
|
+
email: developers@crystalcommerce.com
|
136
|
+
executables: []
|
137
|
+
|
138
|
+
extensions: []
|
139
|
+
|
140
|
+
extra_rdoc_files:
|
141
|
+
- README.rdoc
|
142
|
+
files:
|
143
|
+
- Gemfile
|
144
|
+
- Gemfile.lock
|
145
|
+
- MIT-LICENSE
|
146
|
+
- README.rdoc
|
147
|
+
- Rakefile
|
148
|
+
- VERSION
|
149
|
+
- example_root_schema.rb
|
150
|
+
- hijacker.gemspec
|
151
|
+
- init.rb
|
152
|
+
- install.rb
|
153
|
+
- lib/hijacker.rb
|
154
|
+
- lib/hijacker/active_record_ext.rb
|
155
|
+
- lib/hijacker/alias.rb
|
156
|
+
- lib/hijacker/controller_methods.rb
|
157
|
+
- lib/hijacker/database.rb
|
158
|
+
- lib/hijacker/middleware.rb
|
159
|
+
- spec/hijacker/alias_spec.rb
|
160
|
+
- spec/hijacker/database_spec.rb
|
161
|
+
- spec/hijacker/middleware_spec.rb
|
162
|
+
- spec/hijacker_spec.rb
|
163
|
+
- spec/spec_helper.rb
|
164
|
+
- tasks/hijacker_tasks.rake
|
165
|
+
- uninstall.rb
|
166
|
+
homepage: https://github.com/crystalcommerce/hijacker
|
167
|
+
licenses: []
|
168
|
+
|
169
|
+
post_install_message:
|
170
|
+
rdoc_options:
|
171
|
+
- --charset=UTF-8
|
172
|
+
require_paths:
|
173
|
+
- lib
|
174
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
175
|
+
none: false
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
hash: 3
|
180
|
+
segments:
|
181
|
+
- 0
|
182
|
+
version: "0"
|
183
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
184
|
+
none: false
|
185
|
+
requirements:
|
186
|
+
- - ">="
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
hash: 3
|
189
|
+
segments:
|
190
|
+
- 0
|
191
|
+
version: "0"
|
192
|
+
requirements: []
|
193
|
+
|
194
|
+
rubyforge_project:
|
195
|
+
rubygems_version: 1.8.15
|
196
|
+
signing_key:
|
197
|
+
specification_version: 3
|
198
|
+
summary: One application, multiple client databases
|
199
|
+
test_files:
|
200
|
+
- spec/hijacker/alias_spec.rb
|
201
|
+
- spec/hijacker/database_spec.rb
|
202
|
+
- spec/hijacker/middleware_spec.rb
|
203
|
+
- spec/hijacker_spec.rb
|
204
|
+
- spec/spec_helper.rb
|
205
|
+
has_rdoc:
|