How the instance_eval work in ruby with dsl - ruby-on-rails

i am learning to implement dsl in ruby but get confused with the uses of instance_eval.
my question is how the config variable get accessed in this code without calling the config method, and if the config method is called where it get called.
class Rails
def self.configure (&block)
instance_eval(&block)
end
def self.config
#config I|= {}
end
end
Rails.yes
Rails.configure do
config["feature"] = true
end

It is because when ruby see the config it either find the variable with config or method with config and here we haven't defined the variable with config but there's a method with name config so it called the config method.

Related

The best way to set global instance in rails?

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.)

How does Rails.application.configure block works without providing a variable name

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"

Rails reload! resets class variables, need to rerun some initializers

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

RSpec - Testing methods that call private methods that should be mocked

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.

Override method (monkey patch?) on rails core

On this file:
...gems/actionpack-2.3.18/lib/action_controller/rescue.rb
Exists a method called local_request? inside module Rescue, that is contained in module ActionController, so its like this:
module ActionController
...
module Rescue
...
protected
...
# True if the request came from localhost, 127.0.0.1. Override this
# method if you wish to redefine the meaning of a local request to
# include remote IP addresses or other criteria.
def local_request?
....
I want to override that method in order to handle all requests as non-local, I tried with this (what I think is a monkey patch, not sure if the term is wrong)
module ActionController
module Rescue
protected
def local_request? #:doc:
false
end
end
end
It seems to work (that method return false), but when making a request I receive an error:
undefined method `call_with_exception' for ApplicationController:Class
That method exists in
module ActionController > module Rescue > module ClassMethods
1) If i'm overriding only one method why that got undefined? im deleting the other methods/modules inside the one i'm modifying?
2) How to and whats the right way to do this?
In rails 2.3.x Rails uses ruby's autoload method to load the modules that make up ActionController (see here: the file defining each module is only loaded when the constant is first accessed.
Because you are defining a constant of the same name, autoload will never get triggered (since as far as ruby is concerned it doesn't need to), so the rails provided code never gets loaded. You could instead do
ActionController::Rescue.module_eval do
def local_request?
false
end
end
Although as pointed out in the comments you can just define this on your controller - no need to do it this way.

Resources