Module: Hijacker

Defined in:
lib/hijacker.rb,
lib/hijacker/alias.rb,
lib/hijacker/middleware.rb

Defined Under Namespace

Modules: ControllerMethods Classes: Alias, Database, InvalidDatabase, Middleware, UnparseableURL

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.configObject

Returns the value of attribute config.



10
11
12
# File 'lib/hijacker.rb', line 10

def config
  @config
end

.masterObject

Returns the value of attribute master.



10
11
12
# File 'lib/hijacker.rb', line 10

def master
  @master
end

.sisterObject

Returns the value of attribute sister.



10
11
12
# File 'lib/hijacker.rb', line 10

def sister
  @sister
end

.valid_routesObject



14
15
16
# File 'lib/hijacker.rb', line 14

def self.valid_routes
  @valid_routes ||= {}
end

Class Method Details

.check_connectionObject

just calling establish_connection doesn’t actually check to see if we’ve established a VALID connection. a call to connection will check this, and throw an error if the connection’s invalid. It is important to catch the error and reconnect to a known valid database or rails will get stuck. This is because once we establish a connection to an invalid database, the next request will do a courteousy touch to the invalid database before reaching establish_connection and throw an error, preventing us from retrying to establish a valid connection and effectively locking us out of the app.



164
165
166
# File 'lib/hijacker.rb', line 164

def self.check_connection
  ::ActiveRecord::Base.connection
end

.connect(target_name, sister_name = nil, options = {}) ⇒ Object

Manually establishes a new connection to the database.

Background: every time rails gets information from the database, it uses the last established connection. So, although we’ve already established a connection to a “dummy” db (“crystal”, in this case), if we establish a new connection, all subsequent database calls will use these settings instead (well, until it’s called again when it gets another request).

Note that you can manually call this from script/console (or wherever) to connect to the database you want, ex Hijacker.connect(“database”)



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/hijacker.rb', line 33

def self.connect(target_name, sister_name = nil, options = {})
  raise InvalidDatabase, 'master cannot be nil' if target_name.nil?

  target_name = target_name.downcase
  sister_name = sister_name.downcase unless sister_name.nil?

  return if already_connected?(target_name, sister_name) || test?

  verify = options.fetch(:verify, Hijacker.do_hijacking?)

  db_name = determine_database_name(target_name, sister_name, verify)

  establish_connection_to_database(db_name)

  check_connection

  self.master = db_name
  self.sister = sister_name

  # don't cache sister site
  cache_database_route(target_name, db_name) unless sister_name

  connect_sister_site_models(target_name)
  
  reenable_query_caching
  
  self.config[:after_hijack].call if self.config[:after_hijack]
rescue
  self.establish_root_connection
  raise
end

.connect_sister_site_models(db) ⇒ Object

very small chance this will raise, but if it does, we will still handle it the same as Hijacker.connect so we don’t lock up the app.

Also note that sister site models share a connection via minor management of AR’s connection_pool stuff, and will use ActiveRecord::Base.connection_pool if we’re not in a sister-site situation



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/hijacker.rb', line 71

def self.connect_sister_site_models(db)
  return if db.nil?
  
  sister_db_connection_pool = self.processing_sister_site? ? nil : ActiveRecord::Base.connection_pool
  self.config[:sister_site_models].each do |model_name|
    ar_model = model_name.constantize
    
    if !sister_db_connection_pool
      ar_model.establish_connection(self.root_connection.config.merge(:database => db))
      begin
        ar_model.connection
      rescue
        ar_model.establish_connection(self.root_connection.config)
        raise Hijacker::InvalidDatabase, db
      end
      sister_db_connection_pool = ar_model.connection_pool
    else
      ActiveRecord::Base.connection_handler.connection_pools[model_name] = sister_db_connection_pool
    end
  end
end

.connect_to_master(db_name) ⇒ Object



18
19
20
# File 'lib/hijacker.rb', line 18

def self.connect_to_master(db_name)
  connect(*Hijacker::Database.find_master_and_sister_for(db_name))
end

.current_clientObject



146
147
148
# File 'lib/hijacker.rb', line 146

def self.current_client
  sister || master
end

.do_hijacking?Boolean

Returns:

  • (Boolean)


150
151
152
153
# File 'lib/hijacker.rb', line 150

def self.do_hijacking?
  (Hijacker.config[:hosted_environments] || %w[staging production]).
    include?(ENV['RAILS_ENV'])
end

.establish_root_connectionObject

this should establish a connection to a database containing the bare minimum for loading the app, usually a sessions table if using sql-based sessions.



134
135
136
# File 'lib/hijacker.rb', line 134

def self.establish_root_connection
  ActiveRecord::Base.establish_connection('root')
end

.processing_sister_site?Boolean

Returns:

  • (Boolean)


138
139
140
# File 'lib/hijacker.rb', line 138

def self.processing_sister_site?
  !sister.nil?
end

.root_configObject



128
129
130
# File 'lib/hijacker.rb', line 128

def self.root_config
  ActiveRecord::Base.configurations['root']
end

.root_connectionObject

maintains and returns a connection to the “dummy” database.

The advantage of using this over just calling ActiveRecord::Base.establish_connection (without arguments) to reconnect to the dummy database is that reusing the same connection greatly reduces context switching overhead etc involved with establishing a connection to the database. It may seem trivial, but it actually seems to speed things up by ~ 1/3 for already fast requests (probably less noticeable on slower pages).

Note: does not hijack, just returns the root connection (i.e. AR::Base will maintain its connection)



117
118
119
120
121
122
123
124
125
126
# File 'lib/hijacker.rb', line 117

def self.root_connection
  unless $hijacker_root_connection
    current_config = ActiveRecord::Base.connection.config
    ActiveRecord::Base.establish_connection('root') # establish with defaults
    $hijacker_root_connection = ActiveRecord::Base.connection
    ActiveRecord::Base.establish_connection(current_config) # reconnect, we don't intend to hijack
  end

  return $hijacker_root_connection
end

.temporary_sister_connect(db, &block) ⇒ Object

connects the sister_site_models to db while calling the block if db and self.master differ



95
96
97
98
99
100
101
102
103
# File 'lib/hijacker.rb', line 95

def self.temporary_sister_connect(db, &block)
  processing_sister_site = (db != self.master && db != self.sister)
  self.sister = db if processing_sister_site
  self.connect_sister_site_models(db) if processing_sister_site
  result = block.call
  self.connect_sister_site_models(self.master) if processing_sister_site
  self.sister = nil if processing_sister_site
  return result
end

.test?Boolean

Returns:

  • (Boolean)


168
169
170
# File 'lib/hijacker.rb', line 168

def self.test?
  ['test', 'cucumber'].include?(RAILS_ENV)
end