I am trying to test a single method in ruby. It is in a separate file so basically:
a.rb:
def my_method
...
end
in my a_spec.rb
require 'minitest/autorun'
Object.instance_eval do
load("path_to/a.rb")
def hello_world
...
end
end
When I try to run my test, it says that my_method is a private method while I can actually call Object.hello_world outright. What gives?
Also, is there an easier way to test plain ruby methods(no classes or modules) with minitest?
Doing the load above doesn't add the methods of a.rb as singleton methods to Object. Rather, it adds the methods to the global namespace. (The fact that you are doing the load inside the block where self refers to the Object class is irrelevant.)
With you above code, you should be able to call *my_method* directly in your tests:
class MyTest < MiniTest::Unit::TestCase
def test_my_method
assert my_method
end
def test_hello_world
assert Object.hello_world
end
end
Related
I have a simple file called helper.rb that looks like this:
module MyHelper
def initialize_helper
puts "Initialized"
end
initialize_helper()
end
And another simple file like this:
require_relative 'helper.rb'
include MyHelper
puts "Done"
But when I run this second file, it results in this error:
helper.rb:6:in `<module:MyHelper>': undefined method `initialize_helper' for MyHelper:Module (NoMethodError)
Why can't Ruby find this initializeHelper method defined directly above where I'm calling it???
Try
def self.initialize_helper
puts "Initialized"
end
Without the self., you're declaring an instance method intended to be called on objects, not the module itself. So, for instance, your original code is intended to be used like
module MyHelper
def initialize_helper
puts "Initialized"
end
end
class Foo
include MyHelper
end
Foo.new.initialize_helper
But if you want to call it on the module, you need to have self. in front of it to make it a method on the module itself.
I want a piece of code to run before any other static methods run, is it possible to do something in the spirit of the following?
class MyClass
def self.initialize
#stuff = 1
end
def self.print_stuff
puts #stuff
end
end
My Ruby version of interest is 2.3.
Every chunk of code in Ruby is an expression. Even a class definition is a series of expressions: method definitions are expressions that have the side-effect of adding the method to the class.
This is how meta-programming methods work. attr_reader is a private method call where the implicit self is the class. So, long story short, you aren't restricted inside a class body, you can put whatever code you want to run in the context of the class:
class MyClass
#stuff = 1
def self.print_stuff
puts #stuff
end
end
There's no such thing as an explicit metaclass initializer. The class itself is "initialized" as it's defined, so it's perfectly valid to do this:
class MyClass
# Code here will be executed as the class itself is defined.
#stuff = 1
def self.print_stuff
puts #stuff
end
end
MyClass.print_stuff
Remember that def itself is a form of method call and defining a class in Ruby involves sending a bunch of messages (method calls) around to the proper context objects, such as the class itself as it's being defined.
Given that I have an abstract class which provides inherited functionality to subclasses:
class Superclass
class_attribute :_configuration_parameter
def self.configuration_parameter config
self._configuration_parameter = config
end
def results
unless #queried
execute
#queried = true
end
#results
end
private
# Execute uses the class instance config
def execute
#rows = DataSource.fetch self.class._configuration_parameter
#results = Results.new #rows, count
post_process
end
def post_process
#results.each do |row|
# mutate results
end
end
end
Which might be used by a subclass like this:
class Subclass < Superclass
configuration_parameter :foo
def subclass_method
end
end
I'm having a hard time writing RSpec to test the inherited and configured functionality without abusing the global namespace:
RSpec.describe Superclass do
let(:config_parameter) { :bar }
let(:test_subclass) do
# this feels like an anti-pattern, but the Class.new block scope
# doesn't contain config_parameter from the Rspec describe
$config_parameter = config_parameter
Class.new(Superclass) do
configuration_parameter $config_parameter
end
end
let(:test_instance) do
test_subclass.new
end
describe 'config parameter' do
it 'sets the class attribute' do
expect(test_subclass._configuration_parameter).to be(config_parameter)
end
end
describe 'execute' do
it 'fetches the data from the right place' do
expect(DataSource).to receive(:fetch).with(config_parameter)
instance.results
end
end
end
The real world superclass I'm mocking here has a few more configuration parameters and several other pieces of functionality which test reasonably well with this pattern.
Am I missing something obviously bad about the class or test design?
Thanks
I'm just going to jump to the most concrete part of your question, about how to avoid using a global variable to pass a local parameter to the dummy class instantiated in your spec.
Here's your spec code:
let(:test_subclass) do
# this feels like an anti-pattern, but the Class.new block scope
# doesn't contain config_parameter from the Rspec describe
$config_parameter = config_parameter
Class.new(Superclass) do
configuration_parameter $config_parameter
end
end
If you take the value returned from Class.new you can call configuration_parameter on that with the local value and avoid the global. Using tap does this with only a minor change to your existing code:
let(:test_subclass) do
Class.new(SuperClass).tap do |klass|
klass.configuration_parameter config_parameter
end
end
As to the more general question of how to test functionality inherited from a superclass, I think the general approach of creating a stub subclass and writing specs for that subclass is fine. I personally would make your _configuration_parameter class attribute private, and rather than testing that the configuration_parameter method actually sets the value, I'd instead focus on checking that the value is different from the superclass value. But I'm not sure that's in the scope of this question.
I have the following module and classes:
module MyModule
def self.included base
base.extend(ClassMethods)
end
module ClassMethods
attr_reader :config
# this method MUST be called by every class which includes MyModule
def configure &block
#config = {}
block.call(#config) if block
end
end
end
class A
include MyModule
configure do |config|
# do sth with the config
end
end
class B
include MyModule
end
Is it possible to check, if the configure method from the module was called? This means A should be fine, but B should throw an error, because it never called configure.
I tried it within the self.included callback, but the configure method gets called afterwards.
Technically, #ndn is right, it could be called after the class has been evaluated. However, it sounds like what you want is to validate that the configure method has been called at some point within the class body definition (this will also allow any modules that have been included, to finish evaluating, so if a module include calls the configure method, it's all good as well).
The closest solution I've come up to address this situation can be found here:
https://github.com/jasonayre/trax_core/blob/master/lib/trax/core/abstract_methods.rb
The above code is an abstract methods implementation for ruby, which technically isn't what you're asking (you are talking about calling the method, abstract methods are about checking that a subclass defined it), but the same trick I used there could be applied.
Basically, I'm using ruby's trace point library to watch for the end of the class definition to hit, at which point it fires an event, I check whether the method was defined, and throw an error if not. So as long as you're calling configure from WITHIN your classes, a similar solution could work for you. Something like (not tested):
module MustConfigure
extend ::ActiveSupport::Concern
module ClassMethods
def inherited(subklass)
super(subklass)
subklass.class_attribute :_configured_was_called
subklass._configured_was_called = false
trace = ::TracePoint.new(:end) do |tracepoint|
if tracepoint.self == subklass #modules also trace end we only care about the class end
trace.disable
raise NotImplementedError.new("Must call configure") unless subklass._configured_was_called
end
end
trace.enable
subklass
end
def configure(&block)
self._configured_was_called = true
#do your thing
end
end
end
class A
include MustConfigure
end
class B < A
configure do
#dowhatever
end
end
class C < B
#will blow up here
end
Or, you could try using the InheritanceHooks module from my library and skip the manual tracepoint handling:
class BaseClass
include::Trax::Core::InheritanceHooks
after_inherited do
raise NotImplementedError unless self._configure_was_called
end
end
Note, although I am using this pattern in production at the moment, and everything works great on MRI, because tracepoint is a library built for debugging, there are some limitations when using jruby. (right now it breaks unless you pass the jruby debug flag) -- I opened an issue awhile back trying to get tracepoint added in without having to enable debug explicitly.
https://github.com/jruby/jruby/issues/3096
Here's an example based on your structure.
It checks at instantiation if configure has been called, and will work automatically with any class on which you prepended MyModule.
It checks at every instantiation if configure has been called, but it is just checking a boolean so it shouldn't have any performance impact.
I looked for a way to undefine a prepended method for a specific class but didn't find anything.
module MyModule
def self.prepended base
base.extend(ClassMethods)
end
module ClassMethods
attr_reader :config
def configured?
#configured
end
def configure &block
#configured = true
#config = {}
block.call(#config) if block
end
end
def initialize(*p)
klass = self.class
if klass.configured? then
super
else
raise "Please run #{klass}.configure before calling #{klass}.new"
end
end
end
class A
prepend MyModule
configure do |config|
config[:a] = true
puts "A has been configured with #{config}"
end
end
class B
prepend MyModule
end
A.new
puts "A has been instantiated"
puts
B.new
puts "B has been instantiated"
# =>
# A has been configured with {:a=>true}
# A has been instantiated
# check_module_class.rb:27:in `initialize': Please run B.configure before calling B.new (RuntimeError)
# from check_module_class.rb:50:in `new'
# from check_module_class.rb:50:in `<main>'
I have an app that includes modules into core Classes for adding client customizations.
I'm finding that class_eval is a good way to override methods in the core Class, but sometimes I would like to avoid re-writing the entire method, and just defer to the original method.
For example, if I have a method called account_balance, it would be nice to do something like this in my module (i.e. the module that gets included into the Class):
module CustomClient
def self.included base
base.class_eval do
def account_balance
send_alert_email if balance < min
super # Then this would just defer the rest of the logic defined in the original class
end
end
end
end
But using class_eval seems to take the super method out of the lookup path.
Does anyone know how to work around this?
Thanks!
I think there are several ways to do what you're wanting to do. One is to open the class and alias the old implementation:
class MyClass
def method1
1
end
end
class MyClass
alias_method :old_method1, :method1
def method1
old_method1 + 1
end
end
MyClass.new.method1
=> 2
This is a form of monkey patching, so probably best to make use of the idiom in moderation. Also, sometimes what is wanted is a separate helper method that holds the common functionality.
EDIT: See Jörg W Mittag's answer for a more comprehensive set of options.
I'm finding that instance_eval is a good way to override methods in the core Class,
You are not overriding. You are overwriting aka monkeypatching.
but sometimes I would like to avoid re-writing the entire method, and just defer to the original method.
You can't defer to the original method. There is no original method. You overwrote it.
But using instance_eval seems to take the super method out of the lookup path.
There is no inheritance in your example. super doesn't even come into play.
See this answer for possible solutions and alternatives: When monkey patching a method, can you call the overridden method from the new implementation?
As you say, alias_method must be used carefully. Given this contrived example :
module CustomClient
...
host.class_eval do
alias :old_account_balance :account_balance
def account_balance ...
old_account_balance
end
...
class CoreClass
def old_account_balance ... defined here or in a superclass or
in another included module
def account_balance
# some new stuff ...
old_account_balance # some old stuff ...
end
include CustomClient
end
you end up with an infinite loop because, after alias, old_account_balance is a copy of account_balance, which now calls itself :
$ ruby -w t4.rb
t4.rb:21: warning: method redefined; discarding old old_account_balance
t4.rb:2: warning: previous definition of old_account_balance was here
[ output of puts removed ]
t4.rb:6: stack level too deep (SystemStackError)
[from the Pickaxe] The problem with this technique [alias_method] is that you’re relying on there not being an existing method called old_xxx. A better alternative is to make use of method objects, which are effectively anonymous.
Having said that, if you own the source code, a simple alias is good enough. But for a more general case, i'll use Jörg's Method Wrapping technique.
class CoreClass
def account_balance
puts 'CoreClass#account_balance, stuff deferred to the original method.'
end
end
module CustomClient
def self.included host
#is_defined_account_balance = host.new.respond_to? :account_balance
puts "is_defined_account_balance=#{#is_defined_account_balance}"
# pass this flag from CustomClient to host :
host.instance_variable_set(:#is_defined_account_balance,
#is_defined_account_balance)
host.class_eval do
old_account_balance = instance_method(:account_balance) if
#is_defined_account_balance
define_method(:account_balance) do |*args|
puts 'CustomClient#account_balance, additional stuff'
# like super :
old_account_balance.bind(self).call(*args) if
self.class.instance_variable_get(:#is_defined_account_balance)
end
end
end
end
class CoreClass
include CustomClient
end
print 'CoreClass.new.account_balance : '
CoreClass.new.account_balance
Output :
$ ruby -w t5.rb
is_defined_account_balance=true
CoreClass.new.account_balance : CustomClient#account_balance, additional stuff
CoreClass#account_balance, stuff deferred to the original method.
Why not a class variable ##is_defined_account_balance ? [from the Pickaxe] The module or class definition containing the include gains access to the constants, class variables, and instance methods of the module it includes.
It would avoid passing it from CustomClient to host and simplify the test :
old_account_balance if ##is_defined_account_balance # = super
But some dislike class variables as much as global variables.
[from the Pickaxe] The method Object#instance_eval lets you set self to be some arbitrary object, evaluates the code in a block with, and then resets self.
module CustomClient
def self.included base
base.instance_eval do
puts "about to def account_balance in #{self}"
def account_balance
super
end
end
end
end
class Client
include CustomClient #=> about to def account_balance in Client
end
As you can see, def account_balance is evaluated in the context of class Client, the host class which includes the module, hence account_balance becomes a singleton method (aka class method) of Client :
print 'Client.singleton_methods : '
p Client.singleton_methods #=> Client.singleton_methods : [:account_balance]
Client.new.account_balance won't work because it's not an instance method.
"I have an app that includes modules into core Classes"
As you don't give much details, I have imagined the following infrastructure :
class SuperClient
def account_balance
puts 'SuperClient#account_balance'
end
end
class Client < SuperClient
include CustomClient
end
Now replace instance_eval by class_eval. [from the Pickaxe] class_eval sets things up as if you were in the body of a class definition, so method definitions will define instance methods.
module CustomClient
...
base.class_eval do
...
print 'Client.new.account_balance : '
Client.new.account_balance
Output :
#=> from include CustomClient :
about to def account_balance in Client #=> as class Client, in the body of Client
Client.singleton_methods : []
Client.new.account_balance : SuperClient#account_balance #=> from super
"But using instance_eval seems to take the super method out of the lookup path."
super has worked. The problem was instance_eval.