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.
Related
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
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
I am new to Rails and come from a ColdFusion background, where we would store global / site-wide variables in the 'application' scope. This persists the variable across any view or controller. Does Rails 4 have an equivalent functionality for this type of thing?
The site-wide variable won't typically change often so it doesn't need protecting in any way.
For example, in my situation, I want to store the website's domain name. One for testing and one for live environments. Localhost for development and xxxxxx.com for production.
Any tips or pointers would help. I have Googled this extensively and solutions seem to be far too complicated to achieve what seems to be such a trivial task. What's the best elegant solution for Rails 4?
The simplest, basic and default way is to use the Rails.application.config store.
Rails.application.config.my_config = 'foo'
You can assign a config in your environment:
# application.rb
module MyApp
class Application < Rails::Application
config.my_config = 'foo'
end
end
and read it with
Rails.application.config.my_config
# => 'foo'
This approach works well for very simple applications, but if you want something more advanced there are several gems available.
I'm currently using SimpleConfig. The main advantages are:
per-environment configuration. You can configure default configurations for the application, then override defaults with environment specific configurations
local.rb file for custom overrides
capistrano-like configuration style
it works nicely with the dotenv gem, very useful to avoid storing sensitive credentials in your repo.
This sounds like a perfect example for configuration values stored in config/environments/production.rb and config/environments/development.rb. Just store any value there:
config.my_special_value = 'val'
And access it in your application like this:
Rails.application.config.my_special_value
Always the value of your environment is active.
If you just want to have a „global“ value, store it in your application controller. All your view controllers are derived from your app controller, so you can save any value there as an instance or class variable:
class ApplicationController < ActionController::Base
MY_CONSTANT_VALUE = "foo"
end
class MyViewController < ApplicationController
def index
raise MY_CONSTANT_VALUE.inspect
end
end
You also could implement an helper:
# app/helpers/application_helper.rb
module ApplicationHelper
FOO = "bar"
end
# app/controllers/foo_controller.rb
class FooController < ApplicationController
def index
raise FOO
end
end
I can recommend good method to store variable. I use this on production
Passwords can be stored easier to .env file
like this
#Root dir create file ".env"
PASSWORD=123456
and load password
#Somewhere in app
ENV['PASSWORD'] #=> 123456
it works I hope will help you
You can use gem figaro
write your variables in config/application.yml
HELLO: world
development:
HELLO: developers
production:
HELLO: users
Then you can fetch
ENV["HELLO"]
In rails there is gem named as
gem 'dotenv-rails'
By using it we can assign the variables to system level and used in application.
By using simple steps
First create a simple filed in system level at any place with named extension .env
//in application.rb
require 'dotenv'
Dotenv.load('path-of-your-file.env')
And restart your application
Source
Please got the link for the desscription of dot env gem
Say I have a yml file for my rails configuration...
settings.yml
defaults: &defaults
interceptor_email: robot#wearemanalive.com
development:
<<: *defaults
test:
<<: *defaults
production:
<<: *defaults
and I want to have another yml file that is NOT included in version control that each developer maintains locally...
user_settings.yml
development:
interceptor_email: userfoo#domain.com
How can I merge these keys? I am processing my yml files with esb, so that is also an option. Just having trouble figuring out how to do it. I have it setup so keys fallback to the defaults if a key is missing for my environments.
Can't you read the two yml files separately?
settings = YAML.load(path_to_settings)[RAILS_ENV].symbolize_keys
user_settings = YAML.load(path_to_user_settings)[RAILS_ENV].symbolize_keys
settings.merge!(user_settings)
Now you should have the hash value of the settings, then you can merge the hash if you want. If the second hash has the same key as the first hash, the first one will be overwritten.
This is how I do it (disclaimer, I just wrote it, so it doesn't have unit tests yet etc...I'll update this as I improve on it):
require 'yaml'
# read config files (currently only yaml supported), merge user config files over
# defaults and make the parsed data available to the rest of your application.
#
module YourNamespace class Config
attr_reader :files, :get
# Accepts a string filename or an array of string filenames to parse.
# If an array is supplied, values from later files will override values
# of earlier files with the same name.
# Will choke if YAML.load_file returns false (invalid or empty file)
#
def initialize( files )
#files = files.respond_to?( 'map' ) ? files : [ files ]
#get = #files \
\
.map { | file | YAML.load_file file } \
.reduce( {}, :merge! )
;
end
end end
You can call it like this:
config = YourNamespace::Config.new 'config.yml'
# or have the second one override the first
#
config = YourNamespace::Config.new [ 'config-defaults.yml', 'config.yml' ]
And if you want to go fancy, there's a lot of room for improvement here. Ideally make ´Config´ an interface that does not deal with files, and implement in YamlConfig, IniConfig, CliConfig, DbConfig, CookieConfig. That way if you decide one day the that that new config format super seeding yaml is so cool, you can easily change it without breaking anything. And you can have the command line configuration options easily override the ones coming from the configuration files. And you can reuse the config module for any ruby project regardless of where the config values come from. Or maybe just stop inventing hot water. A quick browse makes me think there's some pretty hot water over there...
Next write some documentation, unit tests, input validation, error handling and create some fancy read/write accessors for the config values. Maybe you'd like to be able to ask for a config value like this instead of writing arrays and hashes all the time:
config.get 'app.component.section.setting'
# or this if you want to keep them separate:
#
config.get( 'app', 'component', 'section', 'setting' )
I'm trying to add two extra config options to my application.rb so I can read them out in controllers.
# Extra
config.twitter.key = 'foo'
config.twitter.secret = 'bar'
I am trying to access them using three suggested methods:
self.config.twitter.key # Should be extended through ApplicationController Base
config.twitter.key # Inherited but with different syntax
CONFIG['twitter']['key'] #some massive magical array that apparently exists somewhere
They all give me different kinds of error when I pass them through the "debug" method, E.g:
debug self.config.twitter.key # undefined method `key' for nil:NilClass
So, whats going on?
I believe you've got a slightly incorrect idea behind what your expectations for the config/application.rb is providing you. The ActiveRecord::Base and ActiveController::Base eigenclasses use the Rails::Application::Configuration class that is configured in config/application.rb. The attributes aren't available in classes that descend from either of the Base classes, nor their eigenclasses. This is why you are running into errors in ApplicationController.
There are generally two ways to make configuration initializations in a Rails app. The first way is to create a configuration module and then load values into it via initializer:
First, create a Twiter Config module:
#lib/twitter_config.rb
module TwitterConfig
def self.config
##config ||= {}
end
def self.config=(hash)
##config = hash
end
end
Create a YAML config file:
# config/twitter.yaml
development: &base
key: "foo"
secret: "bar"
test:
<<: *base
key: "foo2"
production:
<<: *base
secret: "barbar"
Alternatively, if you don't intend to add config/twitter.yaml to your SCM, you can just skip this and set the key and secret via environment variables. This would be the suggested solution for an application with a public SCM repository deploying on Heroku.
Then load and set the value via an initializer:
#config/initializers/01_twitter.rb
require 'twitter_config'
TwitterConfig.config = YAML.load_file("config/config.yml")[Rails.env].symbolize_keys
It's generally a best practice to number your initializer files as Rails will load them in order according to their filename. If you are initializing a datastore and that is critical for other steps, then it needs the lowest number. Alternatively, if you are using environment variables, this would be the init file:
#config/initializers/01_twitter.rb
require 'twitter_config'
TwitterConfig.config[:key] = ENV['twitter_config_key']
TwitterConfig.config[:secret] = ENV['twitter_config_secret']
Throughout the Rails application, you now have access to the config values with TwitterConfig.config[:key] & TwitterConfig.config[:secret]. You can include the module as well, just watch out for conflicts.
You can also just load the values as a global constant. It feels a bit ugly to me though:
#config/application.rb
TWITTER_CONFIG = YAML.load_file("config/twitter.yaml")[Rails.env]
I've tried this and seems to be working, you can use ::Rails.application.config.
For example I'm using it to get the correct time_zone set in the application like this:
Rails.application.config.time_zone
I found it thanks to the less-rails code: https://github.com/metaskills/less-rails/blob/master/lib/less/rails/helpers.rb
So you can declare this in your application.rb or in any enviroment file:
config.twitter_key = 'foo'
And then read it like so anywhere in your code:
Rails.application.config.twitter_key
You might want to consider using a yaml file approach.
In application.rb
CONFIG = YAML.load_file("config/config.yml")[Rails.env]
In config/config.yml
development: &base_config
twitter_key = "foo"
twitter_secret = "bar"
test:
<<: *base_config
twitter_key = "foo2"
production:
<<: *base_config
twitter_secret = "barbar"
Same usage as before with definable attributes on an environment level with overloading.
CONFIG['twitter_key']
A small update to the widely accepted answer here : Accessing config from application.rb in Controller (Rails 3)
The methods inside the module TwitterConfig should be class methods (or module methods if you prefer it that way). They can't be instance methods.
Sorry to put this in an answer, but I could not find a way to comment on that answer.
Just put a file in config/initializers/ like app_config.rb If you use ENV constant you can later on easily deploy to Heroku setting the values with the heroku config:add twitter_key=mypublickey command.
Something like this:
## config/initializers/app_config.rb
unless Rails.env.production?
ENV['twitter_key'] = 'foo'
ENV['twitter_secret'] = 'bar'
end
You keep your production keys out of revision control and don't need to dribble with YAML-files.