So... First, I should make my goal clear. My goal is to have an environment defined constant filled with attributes from a yaml file that my app can reference.
The simplest example I can give is something like this:
#
initalizers/config.rb
CONFIG = YAML.load_file("#{Rails.root}/config/config.yml")[RAILS_ENV]
#
config.yml:
production:
host:
foo.com
development:
host:
localhost
port:
3000
#
config/environments/development.rb
Foo::Application.configure do
#...
config.action_mailer.default_url_options = { :host => CONFIG[:host], :port => CONFIG[:port] }
config.after_initialize do
Rails.application.routes.default_url_options = { :host => CONFIG[:host], :port => CONFIG[:port] }
end
end
My problems are:
The constant "CONFIG" does not exist prior to the after_initialize block (obviously because CONFIG is set as part of the initialize process), so I need to move this into the after_initialize block, but I cannot reference "config.action_mailer" inside the after_initialize block because "config" does not exist in that scope... Which is really confusing to me. Shouldn't "config" be accessible inside the block since it exists outside of it?
And as a side question, I am really really really confused how this config.x business works. The block is not yielding any variables so, how is "config" even valid in the context of Foo::Application.configure ?
I would think for it to work at all that it should be:
Foo::Application.configure do |config|
But that's not the case, so I really am curious how this works..
If you define your configuration loader inside of config/application.rb instead of an initializer you should be able to pre-empt the call to configure and have everything set up in time.
Sometimes it's best to make a sub-class that handles configuration files. A very basic example is this:
class Foo::Configuration
def initialize
# Read in contents of config file
end
def method_missing(name)
#config[name.to_s]
end
end
Then in your application you define a method like this:
class Foo::Application
def self.config
#config ||= Foo::Configuration.new
end
end
This will auto-load as required. It also avoids having a messy constant that shows up in the context of every single object.
Related
Can any one tell me the best practice for initializing config variables and read that variables in gems?
Have tried with the following steps:
This code is written in gem
config = YAML.load_file("#{RAILS_ROOT}/config/config.yml")
#key = config["config"]["key"]
server = config["config"]["server"]
and created yml file in config/config.yml in rails application.
Thanks in advance,
Jagdish
I did it once like following:
module YourGem
class YourClass
#config = { :username => "foo", :password => "bar" } # or #config = SomeHelperClass.default_config if the config is more complex
#valid_config_keys = #config.keys
# Configure through hash
def self.configure(opts = {})
opts.each { |k,v| #config[k.to_sym] = v if #valid_config_keys.include? k.to_sym }
end
# Configure through yaml file
def self.configure_with(path_to_yaml_file)
begin
config = YAML::load(IO.read(path_to_yaml_file))
rescue => e
raise "YAML configuration file couldn't be found: #{e}"
end
configure(config)
end
end
end
And in your Rails Application, where you require your gem, you can add an initializer and configure like following:
config/initializers/your_initializer.rb
YourGem::YourClass.configure_with(path_to_the_yml_config_file)
This solution provides a default config, and the possibility to add a own yaml file to change the default values.
I've found my favourite way to set config variables in rails is using the figaro gem. Figaro basically makes use of the ENV['x'] method that is available throughout rails. It stores all your config variables inside a common application.yml file and makes all the constants accessible via the ENV variable.
The bonus is that this translates 1 to 1 with the way Heroku does things as well.
I have a Sidekiq worker that is intended to perform social actions (e.g.: like pages on Facebook). Part of this requires knowing the URL for the object being liked.
Fortunately, Rails 3 makes it easy to access app-specific routes by including Rails.application.routes.url_helpers in whatever class or module needs access to the path/url helper method.
The problem I'm running into is that my default url/port are not accessible from within my Sidekiq worker despite various attempts to define them in my development.rb or production.rb.
class Facebook::LikeRecipeWorker
include Sidekiq::Worker
include Rails.application.routes.url_helpers
sidekiq_options queue: :facebook
def perform(recipe_id, user_id)
recipe = Recipe.find(recipe_id)
user = User.find(user_id)
if user.facebook_token
api = Koala::Facebook::API.new(user.facebook_token)
api.put_connections 'me', 'my_namespace:like', object: recipe_url(recipe)
end
end
end
When the recipe_url method is access, an ArgumentError is raised with the message:
ArgumentError: Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true
I know that I can specify default_url_options for ActionController or ActionMailer in the environment-specific config files, e.g.:
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
config.action_controller.default_url_options = { host: 'localhost', port: 3000 }
However, these (rightfully) appear to have no influence on my Sidekiq worker classes. How should I go about defining default_url_options for these classes?
What we did was this:
In your config file(s) (e.g. config/application.rb, config/production.db, etc.), have a line that sets the default:
routes.default_url_options = { host: 'www.example.org' }
I found a potential solution to this, though it feels like a little bit of a hack. I'm definitely open to better answers.
First, in my environment files (e.g.: config/environments/development.rb), I specify the default_url_options for my controllers:
config.action_controller.default_url_options = { host: 'localhost', port: 3000 }
Then in my worker class, I define a default_url_options instance method there:
class Facebook::LikeRecipeWorker
# ...
private
def default_url_options
ActionController::Base.default_url_options
end
end
I have several models that need to call a method that takes a particular hash as input. Where should I declare this hash to keep things DRY, and how can I then use it in multiple models?
The hash will be the same for all uses. Specifically, it's just a hash of connection details that I want to pass to a call to establish_connection. I realize that that method takes a URL, though I'm still on rails 3.1, and that method was not made available until a little later.
Instead of repeating the following in five models, I just want to call something like establish_connection(legacy_db_connection_hash).
establish_connection(
:adapter => 'mysql',
:host => ENV['LEGACY_DATABASE_HOST'],
:username => ENV['LEGACY_DATABASE_USERNAME'],
:password => ENV['LEGACY_DATABASE_PASSWORD'],
:database => ENV['LEGACY_DATABASE_NAME']
)
I was thinking that perhaps there's a way to do it via /lib, but I'm really not sure how to go about it.
Thank you for your time!
You can also declare the configuration in your application.rb file
module Yourapp
class Application < Rails::Application
config.db_connection_config = {
.....
}
end
end
And you can use it Yourapp::Application.config.db_connection_config
You can modify them in separate environment files as well, if need.
And may be instead of using establish_connection each_time you can put in a separate module in the lib module, like its suggested in the previous answer.
It should go into a module.
For example, declare a module Foo in the file lib/foo.rb (and don't forget self. in front of the method name):
module Foo
# returns a hash of connection settings
def self.settings
{
adapter: 'mysql2',
database: ENV['LEGACY_DATABASE_NAME'],
username: ENV['LEGACY_DATABASE_USERNAME'],
password: ENV['LEGACY_DATABASE_PASSWORD'],
host: ENV['LEGACY_DATABASE_HOST']
}
end
end
Foo.settings will be available when you include Foo in your models.
Also, make sure to add the lib directory to config.autoload_paths in config/application.rb, so that lib/foo.rb is read and its contained Foo module is loaded:
config.autoload_paths += %W(#{config.root}/lib)
Here's how I do it:
config/initializers/profile_store.rb
config = YAML.load_file('config/profile_store.yml')[Rails.env]
Rails.application.config.profile_store = config.symbolize_keys
config/profile_store.yml
development:
host: localhost
prefix: dev-profile
test:
host: localhost
prefix: test-profile
You access it later with Rails.application.config.profile_store and in development you'll get
{:host => 'localhost', :prefix => 'dev-profile'}
I have some modules inside the lib folder in rails i.e.:
/lib/myapp/lib/**
I am working on them in development, however each time I have to restart server. I have been through a number of different questions on SO but most of them are for not for rails 3.1
I currently have an initializer that does this;
if Rails.env == "development"
lib_reloader = ActiveSupport::FileUpdateChecker.new(Dir["lib/**/*"], true) do
Rails.application.reload_routes! # or do something better here
end
ActionDispatch::Callbacks.to_prepare do
lib_reloader.execute_if_updated
end
end
if Rails.env == "development"
lib_reloader = ActiveSupport::FileUpdateChecker.new(Dir["lib/myapp/lib/*"], true) do
Rails.application.reload_routes! # or do something better here
end
ActionDispatch::Callbacks.to_prepare do
lib_reloader.execute_if_updated
end
end
Is there a generic way to do this? Its very time consuming having to restart the server every single time!
Get rid of the initializer and in your application.rb file put following line:
config.autoload_paths += Dir["#{config.root}/lib/**/"]
One thing to watch out is that your module and class names should follow the naming convention for autoreload to work. for example if you have file lib/myapp/cool.rb, then your constant for class/module declaration in cool.rb should look like this:
Myapp::Cool
If you have file lib/myapp/lib/cool.rb and you want it to use Cool as class/module name instead of Myapp::Lib::Cool then your autoload should look like this:
config.autoload_paths += Dir["#{config.root}/lib/myapp/lib/**/"]
As long as you are running in devmode, rails will automatically reload all classes/modules that are in autoload path and follow naming conventions.
Add to application_controller.rb or your base controller:
before_filter :dev_reload if Rails.env.eql? 'development'
def dev_reload
# add lib files here
["rest_client.rb"].each do |lib_file|
ActiveSupport::Dependencies.load_file lib_file
end
end
Worked for me.
I would like to do something like this
config.default_host = 'www.subdomain.example.com'
in some of my configuration files, so that object_url helpers (ActionView::Helpers::UrlHelper) produce links beginning with http://www.subdomain.example.com
I have tried to search the docs but I did not find anything except ActionMailer docs and http://api.rubyonrails.org/classes/Rails/Configuration.html which is not useful for me, because I do not know in which pat to look. Is there a place which describes the whole structure of Rails::Initializer.config?
asset_host doesn't work for urls
You need to override default_url_options in your ApplicationController (at least in Rails 3)
http://edgeguides.rubyonrails.org/action_controller_overview.html#default-url-options
class ApplicationController < ActionController::Base
def default_url_options
if Rails.env.production?
{:host => "myproduction.com"}
else
{}
end
end
end
Define the default host in your environment config:
# config/environments/staging.rb
MyApp::Application.configure do
# ...
Rails.application.routes.default_url_options[:host] = 'preview.mydomain.com'
# ...
end
Then you can create a URL anywhere in your app:
Rails.application.routes.url_helpers.widgets_url()
Or include the URL helpers in your class:
class MyLib
include Rails.application.routes.url_helpers
def make_a_url
widgets_url
end
end
If you don't define the default host, you will need to pass it as an option:
widgets_url host: (Rails.env.staging? ? 'preview.mydomain.com' : 'www.mydomain.com')
It's also useful to specify things like the protocol:
widgets_url protocol: 'https'
Another way is to set it like this
# config/production.rb
config.action_controller.default_url_options = { host: 'myproduction.com' }
You can easily set :host or/and :only_path parameter for every url_helper.
yours_url(params, :host => "http://example.com", :only_path => Rails.env.test?)
This way you are not setting global default_url_options in your environments, unless you want that.
In Rails 6.1 (at least), application-wide default_url_options can be set as follows:
# config/environments/development.rb
Rails.application.default_url_options = { host: 'localhost', port: 3000 }
Rails.application.configure do
# ...
end
See: https://github.com/rails/rails/issues/29992#issuecomment-761892658
As far as I know, the *_url helpers use the server's configured host name. So for example if my Apache installation is accepting requests for this Rails app at http://www.myapp.com/ then Rails will use that address. That's why the *_url methods in a development environment point to http://localhost:3000 by default.
The asset host suggested in the previous answer will only affect the image_tag, stylesheet_link_tag and javascript_link_tag helpers.
NSD's solution is how I do it, but I had to add a block to make it work with https:
config.action_controller.asset_host = Proc.new { |source, request|
(request ? request.protocol : 'http://') + "www.subdomain.example.com"
}
There's this, but I'm not terribly sure if they're the helpers you're referring to:
ActionController::Base.asset_host = "assets.example.com"
http://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagHelper.html