I have a simple module with a basic configuration pattern and API connect method. I am configuring this module in initializer.
services/tasks_manager.rb:
module TasksManager
class << self
attr_reader :client
end
def self.configuration
#configuration ||= Configuration.new
end
def self.configure
yield configuration
end
def self.connect
#client ||= SecretAPI::Client.new do |config|
config.url = configuration.url
config.token = configuration.token
config.username = configuration.username
config.password = configuration.password
end
self
end
#.
#.
# other stuff
#.
#.
class Configuration
attr_accessor :url
attr_accessor :username
attr_accessor :token
attr_accessor :password
end
end
config/initializers/tasks_manger.rb
TasksManager.configure do |config|
config.url = "a..."
config.username = "b..."
config.password = "c..."
config.token = "d..."
end
When I start rails app all is working fine I can use TasksManager by different objects and it is using configuration that has been set up in initializer. But...
When I make a small change to the services/tasks_manager.rb file, like commenting something out or adding new method to it. I am required to restart the rails app. TasksManager.configuration is empty at this stage. It looks like making a changes to the file forces creation of the new module and initializer is not loaded.
It might be a normal behaviour but it took me a while to figure it out and I was thinking that maybe someone will be able to explain it to me.
I am using rails 4.2 with spring(is it why?).
you can put your initialization-code into a ActionDispatch::Callbacks.to_prepare {} block. that will evaluate it whenever rails reloads classes.
Related
Rails 6.1.4.1
I'm setting values from an initializer file and when I request a page, I find the object_id of the configuration changed (and the values lost) unless I do require 'my_library' in the initializer file. I don't understand why?
app/lib/feature_flags.rb:
class FeatureFlags
class Configuration
include ActiveSupport::Configurable
config_accessor(:my_key) { false }
end
class << self
def configuration
#configuration ||= Configuration.new
end
def configure
yield configuration
end
def enabled?(feature)
puts "#{__FILE__} FeatureFlags.configuration.object_id = #{FeatureFlags.configuration.object_id}"
configuration[feature]
end
end
end
config/initializers/feature_flags.rb:
# require 'feature_flag' # If I uncomment this line, the problem is solved
puts "#{__FILE__} FeatureFlags.configuration.object_id = #{FeatureFlags.configuration.object_id}"
FeatureFlags.configure do |config|
config.my_key = true
end
Output:
1. Run the rails server:
config/initializers/feature_flags.rb FeatureFlags.configuration.object_id = 14720
2. Request some page:
app/lib/feature_flags.rb FeatureFlags.configuration.object_id = 22880
My questions are:
Why do I need to require 'feature_flags' in the initializer for the object_id not to change? I thought Zeitwerk was taking care of this.
Is that how I'm supposed to do (to do it right)?
Thanks for your help!
I found my answer here: https://edgeguides.rubyonrails.org/autoloading_and_reloading_constants.html#use-case-1-during-boot-load-reloadable-code
Why is it not working: because FeatureFlags is a reloadable class, it is replaced by a new object uppon request.
How am I supposed to do it right: wrap my initialization code in a to_prepare block:
Rails.application.config.to_prepare do
FeatureFlags.configure do |config|
config.my_key = true
end
end
In Rails, it's common to see a pattern like this (in config/environments/development.rb for example):
Rails.application.configure do
config.some_option = some_value
end
I was intrigued by this idiom since I was recently researching how these configure blocks work and ended up discovering this very similar pattern, where the configure method initializes an (generally memoized) instance of a configuration Class (that has the accessors for the configuration options) and yield that instance object to the block. Something like this:
module Clearance
class << self
attr_accessor :configuration
end
def self.configure
self.configuration ||= Configuration.new
yield(configuration)
end
class Configuration
attr_accessor :mailer_sender
def initialize
#mailer_sender = 'donotreply#example.com'
end
end
end
That's why Clearance.configure {|config| config.mailer_sender = 'something'} works, because it's yielding that instance of Configuration class to the block variable config.
But the way Rails does it, there's no variable being passed to that block. There's no Rails.application.configure do |config| so the block can change the configuration object accessors. I thought config would be undefined inside that block, but it isn't.
Tried looking at rails source code and I suspect it has to do with the configurable module, but I couldn't 100% understand the code I found there.
It seems the Configurable module isn't used by the actual Rails railtie.
https://github.com/rails/rails/blob/5ccdd0bb6d1262a670645ddf3a9e334be4545dac/railties/lib/rails/railtie.rb#L170
I don't claim to understand it's usage fully, but apparently the delegate method used in Railtie class
delegate :config, to: :instance
adds a config attribute pointing to an instance of Rails::Application::Configuration - the one which we later access through Rails.application.config.
https://apidock.com/rails/Module/delegate
It uses instance_eval (or class_eval which does essentially the same) to evaluate the block in the context of a specified object. Within the block, that object becomes the implicit receiver (i.e. self).
For example, using your code:
module Clearance
class << self
attr_accessor :configuration
end
def self.configure(&block)
self.configuration ||= Configuration.new
configuration.instance_eval(&block)
end
class Configuration
attr_accessor :mailer_sender
def initialize
#mailer_sender = 'donotreply#example.com'
end
end
end
Clearance.configure { self.mailer_sender = 'something' }
Clearance.configuration.mailer_sender #=> "something"
In a Rails4 project I am working on,
inside the development.rb file (and in the others enviroments),
inside the configure block:
Rails.application.configure do
...
config.foo = 'foo'
end
I can use whatever setter I want, like config.foo=
(I suppose some dynamic code under the hood)
what's the purpouse of that and how does it work ? I did't not find any documentation about it.
Thanks for any help to understant it.
The following code in rails/railties/lib/rails/application/configuration.rb file on the rails code base sets what the valid config options are:
attr_accessor :allow_concurrency, :asset_host, :assets, :autoflush_log,
:cache_classes, :cache_store, :consider_all_requests_local, :console,
:eager_load, :exceptions_app, :file_watcher, :filter_parameters,
:force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags,
:railties_order, :relative_url_root, :secret_key_base, :secret_token :serve_static_files, :ssl_options, :static_cache_control, :session_options,
:time_zone, :reload_classes_only_on_change,
:beginning_of_week, :filter_redirect, :x
attr_writer :log_level
attr_reader :encoding
Rails uses any of the settings mentioned above, and simply ignores the rest.
The dynamic code under the hood is also in the same file
def method_missing(method, *args)
if method =~ /=$/
#configurations[$`.to_sym] = args.first
else
#configurations.fetch(method) {
#configurations[method] = ActiveSupport::OrderedOptions.new
}
end
end
i am trying to create my first rails plugin, and i want it to be configurable, that is to say, i want to be able to set a variable in the environment.rb file or something.
UPDATE: i'm trying to do something like what's done here: http://soakedandsoaped.com/articles/read/exception-notifier-ruby-on-rails-plugin. i have tried mimicking their code, but i can't get it working.
i have the plugin working with the value hard-coded, but everything i have tried so far for making it configurable hasn't worked.
Here's some of the code:
#vendor/plugin/markup/lib/markup_helper.rb
module MarkupHelper
def stylesheet_cache_link_tag(*sources)
cache = assests_cache_dir ? assests_cache_dir : ""
options = sources.extract_options!.stringify_keys
cached_name = options.delete("cached_name")
stylesheet_link_tag(sources, :cache=> File.join(cache, cached_name))
end
def javascript_cache_include_tag(*sources)
cache = assests_cache_dir ? assests_cache_dir : ""
options = sources.extract_options!.stringify_keys
cached_name = options.delete("cached_name")
javascript_include_tag(sources, :cache=> File.join(cache, cached_name))
end
end
#something like the following in config/environment.rb or probably config/environments/production.rb
MarkupConfig.assests_cache_dir = "cache"
i want assests_cache_dir to default to "cache" but be able to set in an environment config file. i have googled a long time on this, and can't find anything discussing this. How can i accomplish this?
module MarkupHelper
mattr_accessor :assets_cache_dir
self.assets_cache_dir = "cache"
def assets_cache_dir
MarkupHelper.assets_cache_dir
end
end
Then in environment.rb (or development.rb/test.rb/production.rb if you want different values for each environment):
MarkupHelper.assets_cache_dir = "my-value"
Although the approach used by tomafro is quite easy to use, another approach is to use a database.yml-style configuration file that can be split according to environments:
module MyPlugin
class Configuration
# == Constants ==========================================================
CONFIG_FILES = [
"#{RAILS_ROOT}/config/myplugin.yml",
"#{RAILS_ROOT}/config/myplugin.yaml"
].freeze
DEFAULT_CONFIGURATION = {
:url => DEFAULT_HOSTNAME
}.freeze
# == Class Methods ======================================================
# :nodoc:
def self.config_file_found
CONFIG_FILES.find do |path|
File.exist?(path)
end
end
# Returns the default path to the configuration file
def self.default_path
config_file_found or CONFIG_FILES.first
end
# == Instance Methods ===================================================
# Creates a new MyPlugin::Configuration instance by reading from the
# configuration file.
# +env+ The Rails environment to load
def initialize(env)
config_file = self.class.config_file_found
#env_config = DEFAULT_CONFIGURATION
if (#config = (config_file and YAML.load(File.open(config_file))))
[ #config['defaults'], #config[env] ].each do |options|
if (options)
#env_config = #env_config.merge(options.symbolize_keys)
end
end
end
end
# Will return +true+ if a configuration file was found and loaded, or
# +false+ otherwise.
def exists?
#env_config != DEFAULT_CONFIGURATION
end
# Returns a particular configuration option.
def [](key)
#env_config[key.to_sym]
end
end
def self.config
#config ||= Configuration.new(Rails.env)
end
end
You would use this as:
settting = MyPlugin.config[:param_name]
You can also write utility methods to fetch particular values, or use OpenStruct instead of a configuration Hash. This is posted merely as an example of another design pattern.
In rails I want to log some information in a different log file and not the standard development.log or production.log. I want to do this logging from a model class.
You can create a Logger object yourself from inside any model. Just pass the file name to the constructor and use the object like the usual Rails logger:
class User < ActiveRecord::Base
def my_logger
##my_logger ||= Logger.new("#{Rails.root}/log/my.log")
end
def before_save
my_logger.info("Creating user with name #{self.name}")
end
end
Here I used a class attribute to memoize the logger. This way it won't be created for every single User object that gets created, but you aren't required to do that. Remember also that you can inject the my_logger method directly into the ActiveRecord::Base class (or into some superclass of your own if you don't like to monkey patch too much) to share the code between your app's models.
Update
I made a gem based on the solution below, called multi_logger. Just do this in the initializer:
MultiLogger.add_logger('post')
and call
Rails.logger.post.error('hi')
# or call logger.post.error('hi') if it is accessible.
and you are done.
If you want to code it yourself, see below:
A more complete solution would be to place the following in your lib/ or config/initializers/ directory.
The benefit is that you can setup formatter to prefix timestamps or severity to the logs automatically. This is accessible from anywhere in Rails, and looks neater by using the singleton pattern.
# Custom Post logger
require 'singleton'
class PostLogger < Logger
include Singleton
def initialize
super(Rails.root.join('log/post_error.log'))
self.formatter = formatter()
self
end
# Optional, but good for prefixing timestamps automatically
def formatter
Proc.new{|severity, time, progname, msg|
formatted_severity = sprintf("%-5s",severity.to_s)
formatted_time = time.strftime("%Y-%m-%d %H:%M:%S")
"[#{formatted_severity} #{formatted_time} #{$$}] #{msg.to_s.strip}\n"
}
end
class << self
delegate :error, :debug, :fatal, :info, :warn, :add, :log, :to => :instance
end
end
PostLogger.error('hi')
# [ERROR 2012-09-12 10:40:15] hi
A decent option that works for me is to just add a fairly plain class to your app/models folder such as app/models/my_log.rb
class MyLog
def self.debug(message=nil)
#my_log ||= Logger.new("#{Rails.root}/log/my.log")
#my_log.debug(message) unless message.nil?
end
end
then in your controller, or really almost anywhere that you could reference a model's class from within your rails app, i.e. anywhere you could do Post.create(:title => "Hello world", :contents => "Lorum ipsum"); or something similar you can log to your custom file like this
MyLog.debug "Hello world"
Define a logger class in (say) app/models/special_log.rb:
class SpecialLog
LogFile = Rails.root.join('log', 'special.log')
class << self
cattr_accessor :logger
delegate :debug, :info, :warn, :error, :fatal, :to => :logger
end
end
initialize the logger in (say) config/initializers/special_log.rb:
SpecialLog.logger = Logger.new(SpecialLog::LogFile)
SpecialLog.logger.level = 'debug' # could be debug, info, warn, error or fatal
Anywhere in your app, you can log with:
SpecialLog.debug("something went wrong")
# or
SpecialLog.info("life is good")
Here is my custom logger:
class DebugLog
def self.debug(message=nil)
return unless Rails.env.development? and message.present?
#logger ||= Logger.new(File.join(Rails.root, 'log', 'debug.log'))
#logger.debug(message)
end
end
class Article < ActiveRecord::Base
LOGFILE = File.join(RAILS_ROOT, '/log/', "article_#{RAILS_ENV}.log")
def validate
log "was validated!"
end
def log(*args)
args.size == 1 ? (message = args; severity = :info) : (severity, message = args)
Article.logger severity, "Article##{self.id}: #{message}"
end
def self.logger(severity = nil, message = nil)
#article_logger ||= Article.open_log
if !severity.nil? && !message.nil? && #article_logger.respond_to?(severity)
#article_logger.send severity, "[#{Time.now.to_s(:db)}] [#{severity.to_s.capitalize}] #{message}\n"
end
message or #article_logger
end
def self.open_log
ActiveSupport::BufferedLogger.new(LOGFILE)
end
end
I would suggest using Log4r gem for custom logging. Quoting description from its page:
Log4r is a comprehensive and flexible logging library written in Ruby for use
in Ruby programs. It features a hierarchical logging system of any number of
levels, custom level names, logger inheritance, multiple output destinations
per log event, execution tracing, custom formatting, thread safteyness, XML
and YAML configuration, and more.
class Post < ActiveRecord::Base
def initialize(attributes)
super(attributes)
#logger = Logger.new("#{Rails.root}/log/post.log")
end
def logger
#logger
end
def some_method
logger.info('Test 1')
end
end
ps = Post.new
ps.some_method
ps.logger.info('Test 2')
Post.new.logger.info('Test 3')
The Logging framework, with its deceptively simple name, has the sophistication you crave!
Follow the very short instructions of logging-rails to get started filtering out noise, getting alerts, and choosing output in a fine-grained and high-level way.
Pat yourself on the back when you are done. Log-rolling, daily. Worth it for that alone.