How to load the .yml file into Mongoid.load! inline? - ruby-on-rails

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

Related

Pull some configuration variables from the DB to set dynamically in Rails environment?

Is it possible to set some configuration variables in production.rb dynamically using values from the database?
I'm building a multi-tenant app and each tenant has some environment specific information that needs to be set dynamically.
For example:
// production.rb
config.action_mailer.default_url_options = { :host => current_tenant.domain }
The current_tenant is a helper method is defined in ApplicationHelper so can't be accessed in production.rb
Here is current_tenant:
// application_helper.rb
def current_tenant
#current_tenant ||= Tenant.find_by(domain: Apartment::Tenant.current)
end
If not, is it possible to dynamically create secrets.yml using DB data? Since then I would be able to use ENV in production.rb
Maybe you can try this instead:
Create a helper method for your emails:
def build_tenant_url(route_name, *args)
opts = args.extract_options!
opts.merge!(host: #tenant ? #tenant.domain : your_domain)
#Here you can merge other options if you need to pass a token etc
Rails.application.routes_url_helpers.send(:"#{route_name}_url", *args, opts)
end
In your mailer, define the #tenant
Then in your email view use it
= link_to 'Edit', build_tenant_url(:edit_tenant, #tenant)
This setup runs when the app starts up, so if you want an option to be driven by some variable, that variable will need to be available to the Ruby code that runs at startup. There's various ways this could be done
the script (or whatever) which starts your rails server passes through a parameter
a value is written into a text file by some other process and read out again by the startup script
same again but with an environment variable
For example, if you were giving each tenant (group of users) their own subdomain on your app, then the subdomain name could be used as a key for the startup code to read the database and pull out some details for that tenant. This subdomain would then be passed through to the app when you spin it up.
EDIT - having said all this, i'm not sure if it's necessary for you to change the startup config at all. Can't you just look up the appropriate domain name whenever you send an email, rather than changing the default set in the config?

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

Rails mountable engine: how should apps set configuration variables?

I have a mountable engine called Blog that apps can use.
What's the best way to allow apps that use the engine to set a configuration variable like site_name (so that the engine can display it in the design)?
Update:
I've seen some gems create a 'config/initializers/gem_name.rb' file. Is there any specification on how to:
Create a file like that on the engine's side
Copy it into the app's side
How to access those set variables on the engine's side?
I tried creating Blog.site_name = "My Site" in the app's config/initializers/blog.rb file but get an Undefined method error.
Figured out an even better solution that also allows you to set default values (incase the app using the engine doesn't specify a config)...
Create config variables in your app's /config/initializers/blog.rb like this:
Blog.setup do |config|
config.site_name = "My Site Name"
end
In your engine's /lib/blog/engine.rb set default values like this:
module Blog
self.mattr_accessor :site_name
self.site_name = "Site Name"
# add default values of more config vars here
# this function maps the vars from your app into your engine
def self.setup(&block)
yield self
end
end
Now you can simply access the config variables in your engine like this:
Blog.site_name
Much cleaner.
After a lot of testing and looking into existing gems, here is what works in Rails 4:
Considering your engine's name is Blog:
In your engine's /lib/blog/engine.rb put in this:
module Blog
def self.setup(&block)
#config ||= Blog::Engine::Configuration.new
yield #config if block
#config
end
def self.config
Rails.application.config
end
end
In your app, create a file called /config/initalizers/blog.rb and set config vars like this:
Blog.setup do |config|
config.testingvar = "asdfasdf"
end
Then you can access these config variables ANYWHERE in your engine like this:
Blog.config.testingvar
Hope this helps someone. There is very little documentation on this right now so it was all trial and error.
I know this is a fairly old post, but in the event someone in the future finds this, I'd like to recommend the Angostura gem for passing dependencies into a Rails engine. To use it, assuming my engine is called 'Blog' and I want to access a variable called 'site_name', the engine's lib/blog.rb looks something like:
require "blog/engine"
require "angostura"
module Blog
include Angostura::Dependencies
dependency :site_name
end
In my main app, in config/initializers/blog.rb, I added
Blog.setup do |config|
config.site_name = "My site name"
end
Now, I can access site_name in my engine by calling Blog.site_name.
I'd like to point out that defaults are also supported, so you could do something like dependency site_name: 'Default site name' in lib/blog.rb. Furthermore, you can pass in whole classes as dependencies by sending it stringified classnames, like config.my_class = 'MyClass'.
For posterity, in order to use the gem, I added s.add_dependency "angostura", "0.6.0" in my engine's gemspec, and then ran bundle install.

Passing variables to config/environments/demo.rb from the Rails app

I have been struggling with a problem for the past days in a Ruby on Rails App I'm currently working on. I have different countries and for each country we use different Amazon S3 buckets. Amazon S3 key credentials are stored as constants in config/environments/environment_name.rb(ex:demo.rb) There is no way for me to determine which country we are operating from the config file. I can determine which country we are operating from the controllers,models,views,etc but not from the config file. Is there a Ruby meta programming or some other kind of magic that I'm not aware of so that I want to say if we are working on UK as a country in the app, use UK's bucket credentials or Germany as a country, use Germany's bucket credentials? I can't think of a way to pass parameters to environment files from the app itself. Thank you very much in advance for all your helps.
Rather than actually pass the configuration details to whichever S3 client you're using at launch, you should probably select the relevant credentials for each request. Your config file can define them all in a hash like so:
# config/s3.rb
S3_BUCKETS => {
:us => 'our-files-us',
:gb => 'our-files-gb',
:tz => 'special-case'
}
Then you can select the credentials on request like so (in maybe your AppController):
bucket_name = S3_BUCKETS[I18n.locale]
# pass this info to your S3 client
Make sense?
Write a little middleware if you want to keep the knowledge of the per-country configuration out of the main application.
A middleware is extremely simple. A do-nothing middleware looks like this:
class DoesNothing
def initialize(app, *args)
#app = app
end
def call(env)
#app.call(env)
end
end
Rack powers applications through chaining a series of middlewares together... each one is given a reference to #app, which is the next link in the chain, and it must invoke #call on that application. The one at the end of the chain runs the app.
So in your case, you can do some additional configuration in here.
class PerCountryConfiguration
def initialize(app)
#app = app
end
def call(env)
case env["COUNTRY"]
when "AU"
Rails.application.config.s3_buckets = { ... }
when "US"
Rails.application.config.s3_buckets = { ... }
... etc
end
#app.call(env)
end
end
There are several ways to use the middleware, but since it depends on access to the Rails environment, you'll want to do it from inside Rails. Put it in your application.rb:
config.middleware.use PerCountryConfiguration
If you want to pass additional arguments to the constructor of your middleware, just list them after the class name:
config.middleware.use PerCountryConfiguration, :some_argument
You can also mount the middleware from inside of ApplicationController, which means all of the initializers and everything will have already been executed, so it may be too far along the chain.

What is the best way to keep web app config?

If I have some config for web admin to set e.g. number of post per page, some enum showing choice. How should I keep this settings in db ? Should I serialize it and save as blob.
Thanks,
I using rails and I want it to dynamically change this setting through web interface, so I think environment.rb would not fit this situation. So I should have a extra table with two tuples as name, value ?
Most languages/frameworks have a config file of sorts. such as the web.config in ASP or the environment.rb files in RoR. You could use one of these.
Or failing that have a key value pair table in your database.
If you're wanting to do this dynamically through the website I would definitely go for the key value pair table.
For the dynamic config values, you should create a model called Configuration with keys and values. I generally have multiple value columns (for number, string, and date) and then call the appropriate method for the configuration.
For "enums" you should create lookup tables with foreign key relationships to where they attach. For example if you have a Post model and you want an enumeration of Category, you should make the Post belong_to :category and Category has_many :posts.
Use a YAML file. YAML is way simpler than XML.
Make a file called "config.yml" in "config" directory. And load the file using YAML::load(). You can make a setting for each environment by naming the first level as environment (e.g., production, development, test).
See this episode of RailsCasts for details.
If you are using asp.net you can use the Web.Config file.
See Asp .net Web.config Configuration File
You could to create a single table in your database to store key-value pairs.
This is what I use. Got the idea from elsewhere, but the implementation is mine. Pulled from a production project of mine:
class AppConfig
# Loads a YAML configuration file from RAILS_ROOT/config/. The default file
# it looks for is 'application.yml', although if this doesn't match your
# application, you can pass in an alternative value as an argument
# to AppConfig.load.
# After the file has been loaded, any inline ERB is evaluated and unserialized
# into a hash. For each key-value pair in the hash, class getter and setter methods
# are defined i.e., AppConfig.key => "value"
# This allows you to store your application configuration information e.g., API keys and
# authentication credentials in a convenient manner, external to your application source
#
# application.yml example
#
# :defaults: &defaults
# :app_name: Platform
# :app_domain: dev.example.com
# :admin_email: admin#example.com
# :development:
# <<: *defaults
# :test:
# <<: *defaults
# :production:
# <<: *defaults
# :app_domain: example.com
#
# For example will result in AppConfig.app_domain => "dev.example.com"
# when Rails.env == "development"
#
class << self
def load(file='application.yml')
configuration_file = File.join Rails.root, 'config', file
File.open(configuration_file) do |configuration|
configuration = ERB.new(configuration.read).result
configuration = YAML.load(configuration)[Rails.env.to_sym]
configuration.each do |key, value|
cattr_accessor key
send "#{key}=", value
end
end if File.exists? configuration_file
end
end
end
AppConfig.load
Create config/initializers/app_config.rb and paste the above code into it. I'm going to make this into a gem. I figure other people will find it useful.
EDIT: Just saw you wish to edit the config as the app runs via a web based interface. You could do this with this method as both getter and setter methods are defined for each attribute.
in your controller:
def update
params[:configuration].each { |k,v| AppConfig.send "#{k}=", v }
…
end
I don't find a model is the right solution here. Forget about the DB overheard, the idea of being able to instantiate something that controls app configuration doesn't make sense. What's more how you implement it? An instance for each tuple?! It should be a singleton class.

Resources