Sharing a database connection with included classes in a Sinatra application - mysql

I'm converting a part of a rails application to its own sinatra application. It has some beefy work to do and rather than have a million helps in app.rb, I've separated some of it out into classes. Without access to rails I'm rewriting finder several methods and needing access to the database inside of my class. What's the best way to share a database connection between your application and a class? Or would you recommend pushing all database work into its own class and only having the connection established there?
Here is what I have in in app.rb
require 'lib/myclass'
configure :production do
MysqlDB = Sequel.connect('mysql://user:password#host:port/db_name')
end
I want to access it in lib/myclass.rb
class Myclass
def self.find_by_domain_and_stub(domain, stub)
# want to do a query here
end
end
I've tried several things but nothing that seems to work well enough to even include as an example.

Assuming you're not doing any threading, just set up the connection as a global var.
require 'lib/myclass'
before do
$MysqlDB = Sequel.connect('mysql://user:password#host:port/db_name')
end
class Myclass
def self.find_by_domain_and_stub(domain, stub)
# use $MysqlDB here
end
end
Might be wise to check the connection timeout settings, or explicitly disconnect from the server. If you're handling a lot of requests, you could run out of connections before they time out.

Tim Rosenblatt's answer will reconnect on every request. It's better to do the following in your Sinatra app:
require 'sequel'
DB = Sequel.connect('mysql://user:password#host:port/db_name')
require 'lib/myclass'
It's better to use a constant than a global variable in this case.

Related

How to establish_connection with more than one database in parallel in Rails?

Context
I'm building a SaaS where users can create their own websites (like Wix or SquareSpace).
That's what happens behind scenes:
My app has its main database which stores users
When a user creates his website, an external database is created to store its data
SQL file runs in this external database to set default settings
Other users shall create their websites simultaneously
Approach
To create a new database and establish connection I do the following:
ActiveRecord::Base.connection.execute("CREATE DATABASE #{name}")
ActiveRecord::Base.establish_connection(<dynamic db data>)
Then I execute sql code in the db by doing:
sql = File.read(sql_file.sql)
statements = sql.split(/;$/)
statements.pop
ActiveRecord::Base.transaction do
statements.each do |statement|
connection.execute(statement)
end
end
Then I reestablish connection with main db:
ActiveRecord::Base.establish_connection :production
Problem
Establishing connection to dynamic database makes application's main database inacessible for a while:
User A is creating a website (establishes dynamic database connection)
User B tries to access his user area (which requires application's main db data)
Application throws an error because it tries to retrieve data of app-main-db (which connection is not established at the moment)
How can I handle many users creating their websites simultaneously without databases conflict?
In other words, how can I establish_connection with more than one database in parallel?
NOTE:
It is not the same as connecting to multiple databases through database.yml. The goal here is to connect and disconnect to dynamic created databases by multiple users simultaneously.
This gem may help. However,you may need to rename some of your models to use the external database namespace instead of ApplicationRecord
https://github.com/ankane/multiverse
I admit that this doesn't answer the core of your initial question but IMO this probably needs to be done via a separate operation, say a pure SQL script triggered somehow via a queue.
You could have your rails app drop a "create message" onto a queue and have a separate service that monitors the queue that does the create operations, and then pass a message with info back to the queue. The rails application monitors the queue for these and then does something with the information.
The larger issue is decoupling your operations. This will help you down the road with things like maintenance, scaling, etc.
FWIW here is a really cool website I found recently describing a lot of popular queuing services.
Probably not the best approach but it can be achieved by calling an external script that creates the database, in a separated ruby file:
Create create_database.rb file in lib folder
Put db creation script inside this file
ActiveRecord::Base.connection.execute("CREATE DATABASE #{name}")
ActiveRecord::Base.establish_connection(<dynamic db data>)
Execute with Rails Runner
rails runner lib/create_database.rb
or with system, if you want to call it from controller
system("rails runner lib/create_database.rb")
This way you can create and access multiple databases without stopping your main database.
Passing arguments
You can pass arguments to your script with ARGV:
rails runner lib/create_database.rb db_name
And catch it inside the script with ARGV[0]:
name = ARGV[0]
puts name
> db_name

Using the rails gem database_cleaner when rSpec testing and using activerecord-sqlserver-adapter

I am using the activerecord-sqlserver-adapter gem to connect to a 2008 SQL Server Database within my rails application. Please note that I do connect to multiple db's throughout my application using 'establish_connection'. I use rSpec to test my application and I want to remove the entries from the DB that rSpec creates. I googled around and found that the database_cleaner gem would be the answer to my problem so I included the Gem and setup my railsappname/spec/spec_helper.rb as so:
require 'database_cleaner'
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
FactoryGirl.reload
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
I also updated the following line to be false
config.use_transactional_fixtures = false
To my rails_helper.rb RSpec.configure block as that is where that line was originally.
I have also tried changing my strategy to :truncation and that has not made a difference. The data is still being inserted in to the DB and not being deleted after the test has completed. Is this a limitation to the activerecord-sqlserver-adapter and are there workarounds for this? Is this issue being caused by the fact I connect to multiple db's within my app? I have googled around for a while and have not seen anyone else experiencing this same issue so any advise is much appreciated!
Thanks for the heads up, but your stack overflow question is really not about SQL Server or the adapter. It is really a by product of how your software is put together. I'm gonna bet it all has something to do with how you establish connections too. Check out this comment on the adapter.
https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/173#issuecomment-4427121
Basically you want to make sure you only have one model that champions a connection so you can manage it. Most gems only know about ActiveRecord::Base. So if you have an app that has many databases, please make sure you do all the things that entails. It is tricky. So much so I even wrote a blog article about it. You should read it :)
http://technology.customink.com/blog/2015/06/22/rails-multi-database-best-practices-roundup/
Lastly... this is purely subjective... but I am a big fan of Rails transactional fixtures and not running your test with an empty database. My advice:
Use Rails defaults.
Layer a small amount of MiniTest on top. Use my minitest-spec-rails gem :)
Use factory girl... but leverage them in a way for dev/test synergies. See https://github.com/metaskills/named_seeds
Again, purely subjective, but I hope that last bit helps. Cheers.

A callback for ActiveRecord database connections?

Is there any way to hook into ActiveRecord connection creation? I want to run some code whenever a connection has just been created.
I feel like it might be a way to set a MySQL variable on the connection, since "variables" in database.yml doesn't seem to work for me. (How to turn off MySQL strict mode in Rails)
The ConnectionAdapter defines two callbacks :checkout (connect) and :checkin (disconnect). You can use it for specific adapter as
ActiveRecord::ConnectionAdapters::MysqlAdapter.set_callback :checkout, :after do
raw_connection.set_your_variables ...
end
Or you can use ActiveRecord::Base.connection.class for whatever adapter is currently declared in database.yml
Also, if you need to configure your model after connection has been made and column information was retrieved, you can redefine load_schema! class method in model.
See: https://github.com/rails/rails/pull/31681#issuecomment-357113030
Just to add to the accepted solution, if you need to fire a database query from within the callback, you can do so in the following way:
ActiveRecord::ConnectionAdapters::AbstractAdapter.set_callback :checkout, :before, ->(conn) {
res = conn.execute('SELECT COUNT(*) from table_name')
puts "res is #{res.each { |tuple| puts tuple.inspect}}"
}
Note: since callback is defined on ActiveRecord::ConnectionAdapters::AbstractAdapter, this should be executed before checking out connection for ANY database type
As #Envek pointed out in comment to the accepted answer, you should not really use ActiveRecord models since you risk running into endless recursion. Also (also noted by #Envek) keep in mind that the callback will be fired on each connection checkout with pooled connections, not just once.
Finally, the chances are, you want to use this from Rails. If so, you can place this code in a file under config/initializers folder. Then it would be run on application startup. Useful for cases where you want to add logic to how database connection is established, especially if precise action depends on results of a specific query.

how to check where mysql dababase exists from rails env?

My app, must work with multiple db's for many reasons. I work on custom model for db connection establishing, where I need to establish new db connection, based on arg's, .
So, for example, before establishing new connection using:
ActiveRecord::Base.establish_connection new_config
Where new_config is customized clone, of database.yml
I want to check whether new DB exists before any db connections establishing. So I need a function db_exists? db_name that will return boolean value, based on whether db exists or not.
It seems a bit illogical to be able to check whether or not a database exists without a connection to that database, but that may just be me.
I recommend you consider using a rescue block to attempt the connection and then handle the appropriate exceptions.
The exceptions you can receive from that attempt are discussed here.
Just ran across a very good discussion on using rescue here.
We use Rails apps to manage databases themselves, so have to do stuff like this all the time. It's perfectly reasonable to want to know about databases besides the current Rails data store. It's a lot like checking for the existence of a file that's not part of the current Rails app.
Here's a solution that may be useful in certain situations (this for MySQL):
def db_exists?(db_name)
ActiveRecord::Base.connection.execute("SHOW DATABASES LIKE '#{db_name}'").size > 0
end
You have to have SOME database connection but it certainly does not have to be for the database you are querying for existence. (Note this implementation is NOT protected against sql injection... it requires you only pass in clean, valid db_name.)

Making Rails use ActiveRecord for only some requests

I have a Rails application in which the vast majority of requests do not use ActiveRecord in any way. It would be nice if the rest of the application worked seamlessly when MySQL was unavailable. However, it seems that on each request, ActiveRecord::Base.verify_active_connections! is called. This means that every endpoint breaks, when really just the ones that use ActiveRecord need to break. How can I configure things so that the actions that do not require ActiveRecord will work fine in the absence of MySQL?
Override it!
class ActiveRecord::Base
def self.verify_active_connections!
begin
super
rescue
puts "Do something"
end
end
end
Probably not the best idea to just do a blanket begin/rescue but you get the point. For more information check this question out: "MySQL server has gone away" with Ruby on Rails