In the following situation, where include_class is a preference, can I somehow dynamically change the value of include_class and reload Image such that it instead includes the new value of include_class?
module Foo
included do
#test_var = :foo
end
end
module Bar
included do
#test_var = :bar
end
end
Config.include_class = Foo
class Image
include Config.include_class
end
# ... run tests with default configuration for Image, where Image.test_var = :foo
Config.include_class = OtherClass
# ... how can I reload or re-evaluate Image such that Image.test_var = :bar?
Context
I am trying to test whether a configuration option (that is normally set by an initializer) has the correct effects on the application. Because this is part of a test suite the the modules and classes might be loaded before and all configuration changes need to be reset after the test.
I'd suggest to use refinements instead, in this case class will be reset after the test (on any other stuff). I implemented an example without active_concern, but the idea should be clear. This example can be executed with plain ruby as a single script.
# This part is just to keep close to your example
class Config
def self.include_class=(klass)
##include_class = klass
end
def self.include_class
##include_class ||= nil
end
end
# Assuming Bar is a default module and Foo will be used in tests
module Bar
def print_something
puts 'I am Bar'
end
end
module Foo
def print_something
puts 'I am Foo'
end
end
# Setting default module
Config.include_class = Bar
# Defining Image class
class Image
include Config.include_class
end
#Changing config
Config.include_class = Foo
# This is a refinement
module ImagePatch
# It will include current Config.include_class
# Note that methods from Bar that are not present in Foo will not be removed
refine Image do
include Config.include_class
end
end
# Here we will create module where refinement is activated
module TestEnv
# Activating the patch
using ImagePatch
Image.new.print_something #=>I am Foo
end
#Outside of the TestEnv module we enjoy the default Image class
Image.new.print_something #=>I am Bar
Related
I am dynamically including a module into the Baz class in the foobarbaz method.
However, when I execute this in ruby, I get a nil puts. Doesn't the module have access to Foo's instance variables?
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
Baz.send(:include, Bar) # For Ruby < 2.1
# Baz.include(Bar) # For Ruby > 2.1
end
end
class Baz
end
module Bar
def foobar
#current_session
# 'foobar'
end
end
puts Foo.new('current_session').foobarbaz.new.foobar # nil
NOTE, for this, I was using Ruby 2.0.0. The following also does not puts desired result in Ruby 2.1.2.
Here is a meta programming for you :
#!/usr/bin/env ruby
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
session = current_session
Bar.module_eval { #current_session = session }
Baz.send(:include, Bar)
end
end
module_eval says
Evaluates the string or block in the context of mod, except that when a block is given, constant/class variable lookup is not affected....
Thus inside Bar.module_eval { #current_session = session }, #current_session is the instance variable of Bar only and I am setting the value of it to the instance variable value of the class Foo, which is #current_session.
Baz.send(:include, Bar) is helpfull, which returns class/module itself, which is including the other module. include(module, ...) → self.
class Baz
end
Read this post to understand the below stuff.
module Bar
class << self
attr_reader :current_session
end
def foobar
Bar.current_session
end
end
puts Foo.new('current_session').foobarbaz.new.foobar
# >> current_session
Update
As #Christian Fazzin gave a good suggestion :-
If you want Bar module to have write method also, then you have to do 2 changes -
Bar should contain then attr_accesor :current_session, instead of what it has now.
You don't need to use the power of module_eval there, rather use syntactic sugraness of write methods, like put Bar.current_session = current_session inside the method foobarbaz . Remove the lines session = current_session and Bar.module_eval { #current_session = session }.
After creating an instance of Foo (foo, say) and in doing so initializing foo's instance variable #current_session to 'current session', it appears to me that you want foo.foobarbaz to do the following:
cause Baz to include the module Bar
create an instance of Baz (baz, say)
create an instance variable named #current_session for baz and assign it the value of foo's instance variable of the same name
invoke baz.foobar to return the value of baz's instance variable #current_session.
If my understanding is correct, we can perform these four steps with four lines in Foo#foobarbaz:
class Baz
end
module Bar
def foobar
#current_session + ' in Baz'
end
end
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
Baz.include(Bar)
baz = Baz.new
baz.instance_variable_set(:#current_session, self.current_session)
baz.foobar
end
end
foo = Foo.new('current session')
foo.foobarbaz
#=> "current session in Baz"
I've slightly modified what foobarbaz returns to show where it is coming from.
Note that the third line of foobarbaz could be changed to either of the following
baz.instance_variable_set(:#current_session, #current_session)
baz.instance_variable_set(:#current_session,
instance_variable_get(:#current_session))
If the latter of these were used, #current_session's accessor would not be needed.
You'd just need to set instance variable (not class instance variable!) #current_session of class Baz.
With slightest modification of your code without need of additional class/module methods the most straightforward way is to define initialization method that sets the required variable:
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
# define Baz#initialize on-the-fly, alternatively with define_method
Baz.class_eval "def initialize; #current_session = '#{#current_session}';end"
Baz.send(:include, Bar) # For Ruby < 2.1
end
end
class Baz
end
module Bar
def foobar
#current_session
# 'foobar'
end
end
puts Foo.new('current_session').foobarbaz.new.foobar
# current_session
I know, I can overwrite class method from module this way
class Foo
class << self
def some_static_method
puts 'some_static_method'
end
end
end
module BAR
class << Foo
def some_static_method
puts 'another_static_method'
end
end
end
class Foo
include BAR
end
Foo.some_static_method # => 'another_static_method'
Is it possible for an instance method?
You can do the following:
class Foo
def self.some_static_method; puts "Hello from Foo" end
end
module Bar
def self.included(base)
base.instance_eval do
def some_static_method; puts "Hello from Bar" end
end
end
end
class Foo
include Bar
end
Foo.some_static_method
This should work
UPDATE
To override instance method use:
class Foo
def some_instance_method; puts "Hello from Foo" end
end
module Bar
def self.included(base)
base.class_eval do
def some_instance_method; puts "Hello from Bar" end
end
end
end
class Foo
include Bar
end
Foo.new.some_instance_method
Your question is actually not about method overriding. It is about what class is referred to within a class ... construction in a module body.
When you do
module Bar
class << Foo
p self
end
end
# => #<Class:Foo>
the << Foo points to the singleton class of the Foo in the main environment because class << Foo cannot define the singleton class directly of a class Foo that has not been defined in advance. So it looks up for Foo that is already defined, and such class is found in the main environment.
When you do
module Bar
class Foo
p self
end
end
# => Bar::Foo
a new class Bar::Foo is created; the Foo points to this Bar::Foo that is newly created, and it does not point to the Foo in the main environment. In order to point to it, you have to explicitly specify that with ::.
module Bar
class ::Foo
p self
end
end
# => Foo
If you are using Ruby > 2.0.0 then what you can use is Module#prepend. Instead of include you can prepend an module and that way all of the module's methods are overriding any existing class instance methods with the same name. You can see a quick example here.
Prior to Ruby 2, Rails had introduced a similar hack: #alias_method_chain
Here is a nice comparison of the two approaches.
In Ruby, I can do this:
module Foo
end
class Bar
include Foo
end
module Foo
def do_something_instancey
puts "I'm an instance!"
end
end
Then, if I instantiate a Bar object, I can call do_something_instancey on it:
b = Bar.new
b.do_something_instancey
However, if I do this...
module Foo
def self.included(base)
def base.do_something_classy do
puts "I'm a class!"
end
end
end
My understanding is that because I included Foo in Bar before defining that class method, I cannot call Bar.do_something_classy because it never got "attached" to Bar.
I realize that might be slightly inaccurate/not really the right terminology. Regardless, is there a way, in the above example, to attach a class method to Bar from Foo after the module has already been included?
Here's an example for both, class and instance methods:
module Foo
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
end
end
class Bar
include Foo
end
module Foo
def do_something_instancey
puts "I'm an instance!"
end
module ClassMethods
def do_something_classy
puts "I'm a class!"
end
end
end
b = Bar.new
b.do_something_instancey
# => I'm an instance!
Bar.do_something_classy
# => I'm a class!
To add class methods to each class that has (already) included a specific module, you could traverse Ruby's ObjectSpace:
ObjectSpace.each_object(Class) do |klass|
if klass.include? Foo
klass.define_singleton_method(:do_something_classy) do
puts "I'm a class!"
end
end
end
Description of retroactive_module_inclusion gem:
This gem circumvents the "dynamic module include" (aka "double
inclusion") problem, which is the fact that M.module_eval { include N
} does not make the methods of module N available to modules and
classes which had included module M beforehand, only to the ones that
include it thereafter. This behaviour hurts the least surprise
principle, specially because if K is a class, then K.class_eval {
include M } does make all methods of M available to all classes
which had previously inherited it.
I'm trying to override a method from another gem. The code looks something like this:
module DatabaseCleaner
class Base
def orm_strategy(strategy)
# ...
end
end
end
In my gem:
require 'database_cleaner/base'
module DatabaseCleaner
class Base
def orm_strategy(strategy)
# New code
end
end
end
However, it the original is still being used. What am I doing wrong?
You are trying to override an instance method of the Foo::Bar class. You have to redefine the class's method:
module Foo
class Bar
def self.test # self == Bar
# New code
end
end
end
Is there a callback which can be executed when a class is loaded. I am thinking something like this.
register_callback('Foo', :debug_message)
def debug_message
puts "Foo has been loaded"
end
require 'foo'
No, there is not. And there cannot be, for the simple reason that classes in Ruby are open: they are never fully "loaded", you can always add, remove, rename and overwrite methods at any later point in time.
For example, when is the following class "loaded"?
# foo.rb
class Foo
def some_method
end
end
# bar.rb
class Foo
def some_other_method
end
end
# baz.rb
class Foo
def some_method
end
end
require 'foo'
require 'bar'
require 'baz' if rand > 0.5