Ruby: Raise error in Module if class method not found - ruby

I would like to put some code in module that throws an error if certain method is not defined.
This module relies on the external definition of this method, since this method's implementation is different for all classes. This code would help developers know early that they forgot to implement the method rather than when they tried to use features of the module.
module MyModule
def self.included(klass)
raise "MyModule: please `def my_method` on #{klass}" unless klass.respond_to?(:my_method)
end
end
I can easily raise an error in a module's included definition if a method is not defined, however since most modules are included at the top of a file, it's likely that my required method is defined in the class, but not before my module is included.
class MyClass
include MyModule
def self.my_method
# ...
end
end
This would still raise an error :(
Is it possible to raise an error only if the method truly is not defined in the class definition? Almost need a class.onload callback of sorts. If not, any other ideas for how to mitigate the possibilities that a programmer might include our module without defining this needed method?

Sounds like you want to make use of method_missing and define_method.
If you do use method_missing don't forget to:
call super for unhandled cases.
also implement a respond_to? method
look at this question, plus this and that.
Update:
It sounds the goal is to do static method checking like Java or c++ does. This is not really meaningful in ruby :-(
Since in ruby:
Each instance of an object has its own eigenclass. A given object may have the necessary methods mixed in at runtime. So just because Foo does not have a method at class load time is meaningless.
Frameworks like RoR hooks method_missing and dynamically create methods needed for the database query methods, so the method may exist (or not) when it is needed.
With regards to "class on load": A class definition is really executed. Try this:
class Foo
p "Hi"
end
You will see "Hi" the first and only the first time Foo is used. This is how things like devise hook into do their magic.
class User < ActiveRecord::Base
# **CALL 'devise' method**
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
# **CALL attr_accessible method**
attr_accessible :email, :password, :password_confirmation
end
So maybe by private convention have developers add a check_class method call to the bottom of the classes in question?
I understand the intent but it seems like fighting the way ruby is designed to work.
As a mostly Java person I appreciate the frustration. Let me guess: repeated cases of code getting pushed to production that had missing methods? :-P
Update2:
wrt onload In ruby barring use of frozen a class get new methods defined all the time. ( Or an instance can get new methods defined just for that instance. ) so checking for a method's nonexistence is only a snapshot check and not as definitive a check as a static language brings to the table. This is ruby's very own Halting problem

How about declaring a method with that name, which just raises an error, to make sure the user redefines the method?
module MyModule
def my_method
raise "Please implement me"
end
end
class MyClass
include MyModule
def my_method
# do something
end
end

Assuming your program requires all files when started and does not use any autoload and the like, you could use something like the following right after everything is required, but before the program actually starts:
classes_to_check = Object.constants.find_all do |const|
klass = Object.const_get(c)
klass.ancestors.include?(MyModule) if klass.kind_of?(Module)
end
classes_to_check.each do |klass|
raise "MyModule: please `def my_method` on #{klass}" \
unless klass.respond_to?(:my_method)
end
However, I personally always use Dogbert's solution.

Related

ActiveRecord: override attribute writers by using a class method

I don't know how to correctly phrase the title, I think the best way to explain this issue is just with code samples.
My goal
I want to define a meta method like this (in Rails 5):
class Post < ApplicationRecord
override_this_attribute_writer :some_attribute
end
The override_this_attribute_writer follows a common pattern, it overrides the original writer by doing some filtering on top of it. I find this way of overriding very convenient and clear.
First approach
module MyCommonModule
extend ActiveSupport::Concern
module ClassMethods
def override_this_attribute_writer(attribute_name)
alias_method :"#{attribute_name}_old=", :"#{attribute_name}="
define_method :"#{attribute_name}=" do |a_value|
# Do my stuff
send(:"#{attribute_name}_old=", a_value)
end
end
end
When doing this, I was getting an exception at the call of alias_method, because, apparently, the method I was trying to copy didn't exist (yet).
Second approach
module MyCommonModule
extend ActiveSupport::Concern
module ClassMethods
def override_this_attribute_writer(attribute_name)
define_method :"#{attribute_name}=" do |a_value|
# Do my stuff
send(:write_attribute, attribute_name, a_value)
end
end
end
I was expecting this not to work: if, when running the meta method, ActiveRecord hasn't created the attribute writer yet, this means that it will do it later and override the method that I just defined.
But surprisingly it worked! So I put my hands inside ActiveRecord (5.1.5) to find out more.
Dig into ActiveRecord 5.1.5
I wanted to ensure that what I did was safe and it wasn't just working by accident: I looked into the definition of method writer, and put binding.pry around the method.
This is the result of the experiment:
For attributes that I did not override,
This line is called
Then the method is defined inside this module eval call
Finally, the newly created writer method is correctly called when performing object.attribute=
For attributes that I DID override,
My own method is defined before anything else (when the ActiveRecord writers aren't there yet
Then ActiveRecord calls the same line that handles writer creation, as in the previous example
The method gets (apparently) correctly created by ActiveRecord, since it passes again by this point
But now, surprisingly, when calling object.attribute= my own method is still called in place of the ActiveRecord one
So, this is what I don't understand: if ActiveRecord seems to be overriding my method but it doesn't, what prevents it from doing it?
My questions
What in the end I need to know is whether the fix I have done is actually a good practice (and robust) or it's at risk and it might break if in the future we do upgrades.
If you think that my fix is dangerous, would you be able to suggest a different way to achieve the same goal?
Calling super is even more idiomatic:
module MyCommonModule
extend ActiveSupport::Concern
module ClassMethods
def override_this_attribute_writer(attribute_name)
define_method :"#{attribute_name}=" do |value|
# do some stuff
super value
end
end
end
end

Ruby hook for method defined?

I've been googling around for this and haven't been able to find an answer, which makes me think the answer is no, but I figured I'd ask here in case anyone knows for sure.
Does Ruby have a hook for when methods are defined (ie on a module or class)?
If not, is anyone familiar enough with the implementation of the main object to know how exactly it copies methods to Object when they're defined at the top level?
Really curious about this. Thanks for any info :)
It does. Module#method_added https://ruby-doc.org/core-2.2.2/Module.html#method-i-method_added
module Thing
def self.method_added(method_name)
puts "Thing added #{method_name}"
end
def self.a_class_method; end
def do_something; end
end
class Person
def self.method_added(method_name)
puts "I added #{method_name}"
end
attr_accessor :name
end
Thing
Person.new
# Thing added do_something
# I added name
# I added name=
If not, is anyone familiar enough with the implementation of the main object to know how exactly it copies methods to Object when they're defined at the top level?
It doesn't "copy methods". The language specification simply says that methods defined at the top-level become methods of Object. This is exactly the same mechanism as the one that says that methods defined inside class Foo become methods of class Foo. The language spec says it, therefore the implementors implement it that way. main doesn't need to do anything.
If you want to get real technical, then this is about the default definee, which is the implicit scope in which methods get defined when you don't explicitly specify the definee (as in def foo.bar; end). Usually, the default definee is the self of the closest lexically enclosing class or module definition body, and when there is no lexically enclosing class or module definition, it is Object. But some reflective methods, such as instance_eval or class_eval etc. may or may not change it.

Is overriding a module's method a good convention?

I've got some kind of template patterned module with few method defined (default behaviour) and some which look like method below:
def tax
1.2
end
def do_something!
raise "Please implement it in your class"
end
I've read that in most cases I should use modules over inheritance because of inheritance capabilities (single inheritance) and when I don't need super() at all.
But I feel a bit guilty to override all raise "..." methods and also some default (like tax method), because it is module.
What do you think?
When I need to override methods should I rather use inheritance or including modules?
The rule I usually follow is: when the method has to be defined in the class including the module (e.g. module acts as an interface) i always do:
def method_that_needs_to_be_defined
raise NoMethodError
end
It's good practice, prevents unexpected calls to yet undefined method.
Example:
module Speaker
def speak
raise NoMethodError
end
end
class Bird < Animal
include Speaker
def speak
'chirp'
end
end

How can I mock super in ruby using rspec?

I am extending an existing library by creating a child class which extends to the library class.
In the child class, I was able to test most of functionality in initialize method, but was not able to mock super call. The child class looks like something like below.
class Child < SomeLibrary
def initialize(arg)
validate_arg(arg)
do_something
super(arg)
end
def validate_arg(arg)
# do the validation
end
def do_something
#setup = true
end
end
How can I write rspec test (with mocha) such that I can mock super call? Note that I am testing functionality of initialize method in the Child class. Do I have to create separate code path which does not call super when it is provided with extra argument?
You can't mock super, and you shouldn't. When you mock something, you are verifying that a particular message is received, and super is not a message -- it's a keyword.
Instead, figure out what behavior of this class will change if the super call is missing, and write an example that exercises and verifies that behavior.
As #myron suggested you probably want to test the behavior happening in super.
But if you really want to do this, you could do:
expect_any_instance_of(A).to receive(:instance_method).and_call_original
Assuming
class B < A
def instance_method
super
end
end
class A
def instance_method
#
end
end
Disclaimer expect_any_instance_of are a mark of weak test (see):
This feature is sometimes useful when working with legacy code, though
in general we discourage its use for a number of reasons:
The rspec-mocks API is designed for individual object instances, but
this feature operates on entire classes of objects. As a result there
are some semantically confusing edge cases. For example, in
expect_any_instance_of(Widget).to receive(:name).twice it isn't clear
whether a specific instance is expected to receive name twice, or if
two receives total are expected. (It's the former.)
Using this feature is often a design smell. It may be that your test is trying to do too much or that the object under test is too
complex.
It is the most complicated feature of rspec-mocks, and has historically received the most bug reports. (None of the core team
actively use it, which doesn't help.)
A good way to test this is to set an expectation of some action taken by the superclass - example :
class Some::Thing < Some
def instance_method
super
end
end
and the super class:
class Some
def instance_method
another_method
end
def self.another_method # not private!
'does a thing'
end
end
now test :
describe '#instance_method' do
it 'appropriately triggers the super class method' do
sawm = Some::Thing.new
expect(sawm).to receive(:another_method)
sawm.instance_method
end
end
All This Determines Is That Super Was Called On the Superclass
This pattern's usefulness is dependent on how you structure your tests/what expectations you have of the child/derivative class' mutation by way of the super method being applied.
Also - pay close attention to class and instance methods, you will need to adjust allows and expects accordingly
YMMV
A bit late to this party, but what you can also do is forego using the super keyword and instead do
class Parent
def m(*args)
end
end
class Child < Parent
alias super_m m
def m(*args)
super_m(*args)
end
end
That way your super method is accessible like any other method and can e.g. be stubbed like any other method. The main downside is that you have to explicitly pass arguments to the call to the super method.

Test modules with Test::Unit

I encountered a problem when trying to test a module with Test::Unit. What I used to do is this:
my_module.rb:
class MyModule
def my_func
5 # return some value
end
end
test_my_module.rb:
require 'test/unit'
require 'my_module'
class TestMyModule < Unit::Test::TestCase
include MyModule
def test_my_func
assert_equal(5, my_func) # test the output value given the input params
end
end
Now the problem is, if my_module declares an initialize method, it gets included in the test class and this causes a bunch of problems since Test::Unit seems to override/generate an initialize method. So I'm wondering what is the best way to test a module?
I'm also wondering wether my module should become a class at this point since the initialize method is made for initializing the state of something. Opinions?
Thanks in advance !
Including an initialize method in a module feels very wrong to me, so I'd rethink that at the very least.
To answer your question about testing this as a module more directly, though, I would create a new, empty class, include your module in it, create an instance of that class, and then test against that instance:
class TestClass
include MyModule
end
class TestMyModule < Unit::Test::TestCase
def setup
#instance = TestClass.new
end
def test_my_func
assert_equal(5, #instance.my_func) # test the output value given the input params
end
end
Yeah, your initialize should definitely suggest that you're going towards a class. A module in ruby often feels like an interface in other languages, as long as you implement some basic things when you include the module you'll get a lot for free.
Enumerable is a great example, as long as you define [] and each when you include Enumerable you suddenly get pop, push, etc.
So my gut feeling about testing modules is that you should probably be testing classes that include the module rather than testing the module itself unless the module is designed to not be included in anything, it's simply a code storage mechanism.

Resources