I have a class of Publish:
class Publish
def initialize(app_id, secret_key)
#app_id = app_id
#secret_key = secret_key
end
def publish(source_file, target_link)
# ...
end
end
and I want a global instance variable of Publish, so I do some thing in initializer:
Publish.class_eval do
class_attribute :instance
end
Publish.instance = Publish.new(Settings.app_id, Settings.secret_key)
So I could retrieve the instance in everywhere:
Publish.instance.publish(source_file, target_link)
However, If I change the code of Publish, It will throw a error undefined method 'instance' of Publish because of auto-reload.
Put the instance creation/assignment inside a to_prepare block. That way it'll be created just once in production, but any time the application is reloaded in development mode.
Rails.application.config.to_prepare do
Publish.instance = Publish.new(Settings.app_id, Settings.secret_key)
end
(I'd move the class_attribute into the class definition -- but you could put that in the to_prepare too if you like.)
Related
So far I always used "puts" to add custom logging infos to my code. But now it is kind of a pain. When I run rspec for exemple I'm not interested in all the verbose that I added with puts. So I installed the "logging and logging-rails gem" because its installation is real fast and satisfying.
It works well when I call logger from models and controller, but not when I'm using logger inside libraries. I get that error : NameError - undefined local variable or method `logger' for CustomLib:Class.
The easiest thing I succeeded is to call 'Rails.logger' instead of just 'logger'. But in that way in my logfile, the class referring to that line will be 'Rails' but I want 'CustomLib'. For models and controller the right classname is displayed without any intervention from myself.
# config/environnement/test.rb
# Set the logging destination(s)
config.log_to = %w[stdout]
config.log_level = :info
# Show the logging configuration on STDOUT
config.show_log_configuration = false
# lib/custom_lib.rb
class CustomLib
def initialize
Rails.logger.info 'foo'
end
end
When I will use or test my customlib class I'll get :
[2019-06-21T16:26:41] INFO Rails : foo
Instead I would like to see :
[2019-06-21T16:26:41] INFO CustomLib : foo
I'm a bit lost in all that log management in rails, I have no idea what to try next to reach that goal...
Edit
When I put a byebug just before the "logger.info 'foo' " line and enter into it via 'step', I got two different results if whether I'm in a model/controller or a custom lib.
# In custom lib, step enters this file "gems/logging-2.2.2/lib/logging/logger.rb"
# And Rails.logger returns an object like this one beloow
Logging::Logger:0x000055a2182f8f40
#name="Rails",
#parent=#<Logging::RootLogger:0x000055a2182e7ee8
#name="root",
#level=1>,
# In model/controller, step enters this file "gems/logging-rails-0.6.0/lib/logging/rails/mixin.rb"
# And Rails.logger returns an object like this one beloow
Logging::Logger:0x0000557aed75d7b8
#name="Controller",
#parent=#<Logging::RootLogger:0x0000557aedfcf630
#name="root",
#level=0>,
At the end I found a better way, I just need to include Logging.globally on top of the module where I want that behavior:
# lib/custom_lib.rb
class CustomLib
include Logging.globally
def initialize
logger.info 'foo'
end
end
The Rails in the log line is the progname of the logger. You could create a new logger that sets the progname to 'CustomLib'with (say) Rails.logger.clone.tap {|l| l.progname = 'CustomLib' }, but that isn't really the purpose of progname, which is to specify the name of the program, not the name of a class.
You could instead include the class name in the log line:
Rails.logger.info "[#{self.class}] - some message"
Or, for a bit more effort, you could define your own formatter. Here's one with a convenience method for wrapping an existing logger:
class ClassNameFormatter
def self.wrap_logger(klass, logger)
logger.clone.tap { |l| l.formatter = new(klass, logger.formatter) }
end
def initialize(klass, formatter=nil)
#klass = klass
#formatter = formatter ||= Logger::Formatter.new
end
def call(severity, timestamp, progname, msg)
#formatter.call severity, timestamp, progname, "[#{#klass.name}] - #{msg}"
end
end
To use it:
class CustomLib
def initialize
#logger = ClassNameFormatter.wrap_logger self.class, Rails.logger
end
def call
#logger.info 'test log please ignore'
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"
Suppose I need to parse some configuration to instanciate some Service Singletons (that could be used with or without Rails).
A sample code example:
#services/my_service.rb
module MyService
#config = nil
def self.load_config(config)
#config = config
end
When using with Rail (or Capistrano, SInatra, etc.) I would use an initializer to boot up the service
#initializers/svc.rb
MyService.load_config(Rails.application.secrets.my_service.credentials)
But when used specifically with Rails, on every rails console restart!, this #config variable is cleared which is a problem...
Are there
after-reload! hooks that I could use to re-run the initializer ?
other types of variables that would be preserved during a restart!
that I could use here ?
You could define config method as:
def config
#config ||= Rails.application.secrets.my_service.credentials
end
And call this method instead of #config, so when the config variable is unset, it will be set again, otherwise it will return the value.
Seeing that people are still reading this, here is implementation I ended up with
# config/initializers/0_service_activation.rb
# Activation done at the end of file
module ServiceActivation
def self.with_reload
ActiveSupport::Reloader.to_prepare do
yield
end
end
module Slack
def self.service
::SlackConnector
end
def self.should_be_activated?
Utility.production? ||
Utility.staging? ||
(
Utility.development? &&
ENV['ENABLE_SLACK'] == 'true'
)
end
def self.activate
slack = service
slack.webhook = Rails.application.secrets.slack&.dig(:slack_webhooks)
Rails.application.secrets&.dig(:slack, :intercept_channel).try do |channel|
slack.intercept_channel = channel if channel.present?
end
slack.activate
slack
end
end
[
ServiceActivation::Intercom,
ServiceActivation::Slack,
ServiceActivation::Google
] .each do |activator|
ServiceActivation.with_reload do
activator.activate if activator.should_be_activated?
activator.service.status_report
end
end
I am not showing my connector class SlackConnector, but basically you can probably guess the interface from the way it is called. You need to set the webhook url, and do other stuff. The implementation is decoupled, so it's possible to use the same SlackConnector in Rails and in Capistrano for deployment, so it's basically in the lib/ folder
I'm using RSpec for testing my classes on Rails.
I'm wondering what is a good way to test methods calling private methods.
For example I have this class:
Class Config
def configuration(overrides)
#config.merge(overrides)
end
private
def read_config_from_yml
#config ||= YAML.load()...
end
end
To test the configuration method, we need to somehow mock the read_config_from_yml method. I know it's not good to simply mock the private method read_config_from_yml or the instance variable #config because that would be messing with the internals of the object.
What I can think of on top of my head:
make read_config_from_yml public
add setter method for config (to avoid mocking the instance variable)
Are these hacks? Any other ideas?
One idea would be to actually create a copy of the YAML file in the test. You could take a snippet of the file that you're using in your production code, write it to the expected file location and delete it upon test completion.
before do
File.open(file_path_here, 'w+') do |f|
f << <<-eof
config:
setting1: 'string'
setting2: 0
eof
end
end
after do
File.delete(file_path_here)
end
it 'does the thing' do
...
end
This would avoid any stubbing and allow you to keep your method private.
I'm trying to create a module that will be included in many different classes. It needs to record the caller's path to the class file
so I can reference the path in later code. This code tries to add a method to the calling class, but fails because it just returns the current value of ##x.
# /home/eric/FindMe.rb
class FindMe
include GladeGUI
end
# /home/eric/GladeGUI.rb
module GladeGUI
def self.included(obj)
##x, = caller[0].partition(":") # this works ##x = "/home/eric/FindMe.rb"
obj.class_eval do
def my_class_file_path
return ????? # I want to return "/home/eric/FindMe.rb"
end
end
end
end
The GladeGUI module will be "included" in many different classes, so I can't just add code to the calling class. I need a way to make ##x compile into a constant value, so the method stored in the class looks like this:
def my_class_file_path
return "/home/eric/FindMe.rb"
end
How do I convert a variable to a constant in code?
Thanks.
It seems like you don't actually need it to be a "constant" - you just need some way to make the method return the correct value all the time and not allow other code to come along and change the value (with the current ##x solution, someone can just modify ##x and it will break)
The solution is to store the data in a local variable instead of a class or instance variable, and then access that local variable via a closure.
No other code will have scope to 'see' the local variable and thus it cannot be changed.
But then the problem becomes that when you use def inside a class_eval, the scope of the caller isn't captured, so the code can't see your local variable. You can use define_method instead
Here's an example
# /home/eric/GladeGUI.rb
module GladeGUI
def self.included(obj)
caller_file_path = caller[0].split(":").first
obj.class_eval do
define_method :my_class_file_path do
return caller_file_path
end
end
end
end
# /home/eric/FindMe.rb
class FindMe
include GladeGUI
end
puts FindMe.new.my_class_file_path # prints the correct path
But - what if you want my_class_file_path to be a class method rather than an instance method - use define_singleton_method instead:
module GladeGUI
def self.included(obj)
caller_file_path = caller[0].split(":").first
obj.class_eval do
define_singleton_method :my_class_file_path do
return caller_file_path
end
end
end
end
...
puts FindMe.my_class_file_path
Interesting side note: This is how you can fake "private variables" in javascript :-)