How do you check that monkey patching has been done to a specific class in Ruby? If that is possible, is it also possible to get the previous implementation(s) of the attribute that's been patched?
There are the hooks method_added and method_undefined. Garry Dolley has written an Immutable module that prevents monkey patching.
I found this blog posting that touches on how to use method_added to track monkey patching. It's not too hard to extend it to track the methods that were patched.
http://hedonismbot.wordpress.com/2008/11/27/monkey-business-2/:
By using open classes, we can re-define method_added for instances of Class and do some custom stuff every time a method is defined for any class. In this example, we’re re-defining method_added so that it tracks where the method was last defined.
#!/usr/bin/env ruby
class Class
##method_history = {}
def self.method_history
return ##method_history
end
def method_added(method_name)
puts "#{method_name} added to #{self}"
##method_history[self] ||= {}
##method_history[self][method_name] = caller
end
def method_defined_in(method_name)
return ##method_history[self][method_name]
end
end
Related
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
I came across this solution for a Proxy class in the Ruby koans:
class Proxy
attr_accessor :messages
def initialize(target_object)
#object = target_object
#messages = []
end
def method_missing(method_name, *args, &block)
#messages << method_name
#object.send(method_name, *args, &block)
end
end
I can create an object from this proxy class by passing another class as an argument. For instance, the following code will result in "Do something", without having to type thing.method_missing(:do_thing):
class Thing
def do_thing
puts "Doing something."
end
end
thing = Proxy.new(Thing.new)
thing.do_thing
Why is the code in method_missing executed even without having to call said method?
There are methods that are called implicitly (i.e., called even when you don't write it in the code) when a certain event happens or a certain method is called. I call these methods hooks, borrowing the terminology of e-lisp. As far as I know, Ruby has the following hooks:
Ruby hooks
at_exit
set_trace_func
initialize
method_missing
singleton_method_added
singleton_method_removed
singleton_method_undefined
respond_to_missing?
extended
included
method_added
method_removed
method_undefined
const_missing
inherited
initialize_copy
initialize_clone
initialize_dup
prepend
append_features
extend_features
prepend_features
And method_missing is one of them. For this particular one, it is automatically called when Ruby cannot find a defined method. Or in other words, method_missing is the most default method that is called with the least priority, for any method call.
method_missing is one of the amazing aspects of metaprogramming in ruby. With proper use of this method, you can gracefully handle exceptions and whatnot. In your case it is called because the method you are calling on the object doesn't exist obviously.
But one should be careful of its use too. While you are at it do look at responds_to method too.
An example regarding ActiveRecord will make you understand better. When we write:
User.find_by_email_and_age('me#example.com', 20)
There isn't actually a method by that name. This call goes to the method_missing and then this fancyfindmethod is broken down into pieces and you are served what you asked for. I hope that helps.
This is essentially a snippet from Ruby Metaprogramming 2. In the section they gloss over this example but there isn't really an explanation.
module MyRefinement
refine MyClass do
def my_method
"refined"
end
end
end
class MyClass
def my_method
"original"
end
def another_method
my_method
end
end
using MyRefinement
obj = MyClass.new
puts obj.my_method #=> "refined"
puts obj.another_method #=> "original"
Why doesn't the refinement apply when you call my_method from another method?
It avoids "leaky" refinements, e.g., the refinement applies specifically to the method you refine.
http://yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice/
Very near the bottom this functionality is explained; nutshell:
[the] refinement should not leak [...]. If it did, it would mean that any call into any method could leak a refinement into that method, which is the opposite of the purpose of the feature.
refine keyword use to Refinements of the class locally. It's mean we can monkey patch any method by refinement of the class.
In your case, the process of refine/redfined/monkey patch only active when the method get call directly. Also Refinements are lexical in scope. When control is transferred outside the scope the refinement is deactivated.
To get better insight, read the scope part of refinements from here: Refinements
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.
When it comes to run time introspection and dynamic code generation I don't think ruby has any rivals except possibly for some lisp dialects. The other day I was doing some code exercise to explore ruby's dynamic facilities and I started to wonder about ways of adding methods to existing objects. Here are 3 ways I could think of:
obj = Object.new
# add a method directly
def obj.new_method
...
end
# add a method indirectly with the singleton class
class << obj
def new_method
...
end
end
# add a method by opening up the class
obj.class.class_eval do
def new_method
...
end
end
This is just the tip of the iceberg because I still haven't explored various combinations of instance_eval, module_eval and define_method. Is there an online/offline resource where I can find out more about such dynamic tricks?
Ruby Metaprogramming seems to be a good resource. (And, linked from there, The Book of Ruby.)
If obj has a superclass, you can add methods to obj from the superclass using define_method (API) as you mentioned. If you ever look at the Rails source code, you'll notice that they do this quite a bit.
Also while this isn't exactly what you're asking for, you can easily give the impression of creating an almost infinite number of methods dynamically by using method_missing:
def method_missing(name, *args)
string_name = name.to_s
return super unless string_name =~ /^expected_\w+/
# otherwise do something as if you have a method called expected_name
end
Adding that to your class will allow it to respond to any method call which looks like
#instance.expected_something
I like the book Metaprogramming Ruby which is published by the publishers of the pickaxe book.