Override master db selection for splitting read write queries in rails - ruby-on-rails

To distribute load and improve the database performance, I am trying to split read-write queries with some custom modification. I am using makara(https://github.com/instacart/makara) gem for the same in my Rails application. The same is working fine for splitting read-write queries to master and slave. I need to modify the master db selection by checking the replication lag with some custom condition.
I have defined a custom proxy class(as mentioned in documentation) in config/initializers directory but same does not seem to work.
class MyAwesomeSqlProxy < ::Makara::Proxy
hijack_method :select, :ping
send_to_all :connect, :reconnect, :disconnect, :clear_cache
def connection_for(config)
::Sql::Client.new(config)
end
def needs_master?(method_name, args)
return false if args.empty?
sql = args.first
sql !~ /^select/i
end
end
When I make any changes in needs_master? method, same is not reflecting while running the query. I am not sure if I am defining it at the wrong place or this class needs to be loaded somewhere explicitly.

Related

Rails 6: How to load global variables only once?

My app uses global variables like logos, app name, etc retrieved from the database and shown on different controllers and views. I put it in ApplicationController to be available to all, but I find that the individual controllers repeat the same query sometimes.
class ApplicationController < ActionController::Base
$image = Setting.find_by_name('image').value
$city = Setting.find_by_name('city').value
$currency = Setting.find_by_name('currency').value
end
Is there a way to make the same variables available to all controllers (and users) with just a one-time query with the variables saved on memory, such as when the app starts up?
You could attempt to use initializers.
Rail will load all files in config/initializers/ folder when the server starts up. This can work as a place to initialize application-wide variables. We could create a file inventory.rb file in the initializers directory:
at config/initializers/inventory.rb
module Inventory
class Count
Orders = Order.all
end
end
Inventory::Count::Orders
# => "[your orders will show here]"
These will only be loaded once when the server is started or restarted. As such this works well if the values you need won't change. If they will change I don't think there is a good way to avoid running multiple queries.
Whats about caching? Rails is using SQL Caching and you can use Low Level Caching. See the guides (1.7 and 1.8): https://guides.rubyonrails.org/caching_with_rails.html#low-level-caching

How to load the .yml file into Mongoid.load! inline?

I'm having odd issues on Heroku with paths. I have a rails subfolder named "scrapes" where I keep a number of Watir scrapes I'd like to schedule.
Is there a way to load the information in the YML (for production I imagine) inline instead of going looking for a file?
In direct answer to your question, you can create the connection yourself. I have no yet tracked down the helper .load! however Mongoid.Client is the class that generates a connection from the yml. It appears that a connection is made for each session with just this:
Mongo::Client.new(
configuration[:hosts],
options(configuration).merge(database: configuration[:database])
)
Where configuration is the session from the yml. This is found in factory.rb for client.
The Options method being called above is:
def options(configuration)
config = configuration.dup
options = config.delete(:options) || {}
options.reject{ |k, v| k == :hosts }.to_hash.symbolize_keys!
end
Alternative:
You can use the Mongoid configure which accepts a block describing the config.
Mongoid.configure do |config|
config.connect_to("mongoid_test")
end

Store a list of .rb files inside model

colleagues! I have Document model. Document should be processed by one of the parsers (in my project they are called 'importers' and stored inside 'lib/importers' folder). The question is about what is the best way to implement entity Importer inside models layer? (for instance associate document with importer).
First idea is to create importers table, but then I will have 2 independent places where importer names will be saved (database and file system). Bad cases:
Case 1: I've aded a new importer, but forgot to add it to importers table = I can't associate document with this impoter
Case 2: Importer was renamed and we forgot to rename it inside database = error
I decided to define
def Document.importers
#importers ||= Dir.entries("#{Rails.root}/lib/importers/")
.select { |name| !File.directory?(name) && name != 'base_importer.rb'}
.map { |name| name.gsub(/\.rb$/, '') }
end
for f.association inout and add importer string attribute to the document model. So I can get importer class in following way -- 'importer.classify.constantize'. It works, but it looks creepy
Can you advice better solution for this situation? I will appreciate to hear any ideas ;)
I would model them in the database, and then have some system in place to update the database if the files change. This happens all the time with asset management systems: you have some physical files, and some data, and yes, if they go out of sync you've got problems. So, you put systems in place to try to keep them in sync. You can have a rake task to update the database based on the physical files for example, then the protocol is that you need to run the rake task after changing the files, and if you don't then you screwed up and the problems are your fault. Developers should be able to work within these sorts of rules.
Ultimately, Rails is about object-relational data, so work with that if you want to use rails. If you try to go down some route of building a load of instances on the fly every time, based on the contents of the folder, then you will just end up with a very complicated and inefficient system.
Solvation made during brain storm in my local ruby chat:
I haven't mentioned that all importers are inherited from the base_importer which is also placed in lib/importers. We decided to add 'inheritors' array to it and store all inheritors there using hook 'inherited' provided by the ruby core -> http://ruby-doc.org/core-2.2.0/Class.html#method-i-inherited.
class BaseImporter
#inheritors = []
def self.inheritors
#inheritors
end
def self.inherited(subclass)
#inheritors << subclass.name
end
# ...
end
We expected it to work well, but we forgot, that all classes are eager loaded in Rails. So BaseImporter.inheritors will return [] to you on the fresh system start.:
anton#anton:~/Projects/project$ rails c
Running via Spring preloader in process 16832
Loading development environment (Rails 4.2.5)
B2.2.3 :001 > BaseImporter.inheritors
=> []
To force loading of all importers I made an initializer:
# config/initializers/importers.rb
# Preload importers
require 'base_importer'
Dir.glob("#{Rails.root}/lib/importers/*.rb").each { |file| require file }
BaseImporter should be loaded first, because if any importer will be loaded before BaseImporter it will not fire 'inherited' hook:
anton#anton:~/Projects/project$ rails c
Running via Spring preloader in process 16846
Loading development environment (Rails 4.2.5)
2.2.3 :001 > require "xxx_importer"
=> true
2.2.3 :002 > require "base_importer"
=> true
2.2.3 :003 > require "yyy_importer"
=> true
2.2.3 :004 > BaseImporter.inheritors
=> ["XxxImporter"]
Feel free to post your feedback on this solution. I will be glad to hear it

Where is a good place to initialize an API?

I wanted to use this api: https://github.com/coinbase/coinbase-ruby and the first step is to initialize the API, like this:
coinbase = Coinbase::Client.new(ENV['COINBASE_API_KEY'], ENV['COINBASE_API_SECRET'])
I was wondering what the best place to put this code is, and how would I access it if I put it "there"? I want this variable (coinbase) to be accessible ANYWHERE in the application.
Thanks!
The answer to this question really depends on your use case and your approach. My geral recommendation, however, is to create a Service Object (in the DDD sense) (see the section named "Domain Objects Should Not Know Anything About Infrastructure Underneath" in that link), that handles all communication with the Coinbase API. And then, within this service object, you can simply initialize the Coinbase::Client object once for however many times you call into it. Here's an example:
# app/services/coinbase_service.rb
class CoinbaseService
cattr_reader :coinbase_client, instance_accessor: false do
Coinbase::Client.new(ENV['COINBASE_API_KEY'], ENV['COINBASE_API_SECRET'])
end
def self.do_something
coinbase_client.do_something_in_their_api
end
def self.do_something_else
coinbase_client.do_something_else_in_their_api
end
end
So then you might do, e.g.:
# From MyController#action_1
if CoinbaseService.do_something
# ...
else
# ...
end
Or:
# From MyModel
def do_something
CoinbaseService.do_something_else
end
To get the service object working, you may need to add app/services to your autoload paths in application.rb file. I normally just add this:
# config/application.rb
config.autoload_paths += %W(#{config.root}/app)
I find this Service Object approach to be very beneficial organizationally, more efficient (only 1 invocation of the new Coinbase client needed), easier to test (easy to mock-out calls to Coinbase::Client), and simply joyful :).
One way to go about having a global variable can be done as similar as initializing redis in a Rails application by creating an initializer in config/initializers/coinbase.rb with:
$coinbase = Coinbase::Client.new(ENV['COINBASE_API_KEY'], ENV['COINBASE_API_SECRET'])
Now, you can access $coinbase anywhere in the application!
In the file config/initializers/coinbase.rb
Rails.application.config.after_initialize do
CoinbaseClient = Coinbase::Client.new(
Rails.application.credentials.coinbase[:api_key],
Rails.application.credentials.coinbase[:api_secret])
end
In place of the encrypted credentials, you could also use environment variables: ENV['COINBASE_API_KEY'], ENV['COINBASE_API_SECRET']
The above will make the constant CoinbaseClient available everywhere in your app. It will also ensure all your gems are loaded before the client is initialized.
Note: I am using Rails 6.1.4.4, and Ruby 2.7.5

Customer/Client specific code with Rails

in a project we have several customers/clients for a Rails 3 application, where some of them need specific code although we try to solve as many requirements as possible through configuration of the application.
We want to have all customer code in a single branch but in separate directories. Therefore I am now experimenting with autoload_paths:
config.autoload_paths += Dir[Rails.root.join('customer', 'abc', 'app', 'models')]
This works for new files/classes/models, but it is not possible to modify existing models/classes with this approach. For example I want to add a single method to app/models/test_model.rb from customer/abc/app/models/test_model.rb:
class TestModel
def self.test_me; 123; end
end
Unfortunately this overrides the whole class/model and not only this single method. I know that I can do this in Ruby even for core classes like String:
class String
def to_bla; "bla"; end
end
Is it possible to have this behavior also for rails models or is there any better way to separate customer specific code from the rest of the project? I would prefer a solution where I don't have to insert requires or includes in the files I want to customize. It would also be great when rails automatically reloads files for changes in customer specific files in development mode.

Resources