How to enable monkey patch for specific method? - ruby-on-rails

I am using a gem, for some reason, one of its method needs to be patched before it can be used by some of my code.
The problem is here, how can I enable this patch just for some of my code, say. for some method inside a class, I need to enable this patch; some I want to disable this patch.
How to do this?
class FromGem
def BlahBlah
#patch here
end
end
class A
def Test1
#need patch
end
def Test2
# don't need patch
end
end

This is what Refinements are for.
Say, we have the following third-party code:
class FromGem
def say_hello
'Hello'
end
end
FromGem.new.say_hello
# => 'Hello'
And we want to extend it to say "Hello World" instead, we'd do something like this:
module ExtendFromGem
def say_hello
super + ' World'
end
end
class FromGem
prepend ExtendFromGem
end
FromGem.new.say_hello
# => 'Hello World'
That's just the standard way of extending behavior, of course, this is still global. If we want to restrict the scope of our monkey-patch, we will need to use Refinements:
module ExtendedFromGem
module ExtendFromGem
def say_hello
super + ' World'
end
end
refine FromGem do
prepend ExtendFromGem
end
end
FromGem.new.say_hello
# => 'Hello'
# We haven't activated our Refinement yet!
using ExtendedFromGem
FromGem.new.say_hello
# => 'Hello World'
# There it is!
Now, what we want to write is this:
class A
def test1
using ExtendedFromGem
FromGem.new.say_hello
end
def test2
FromGem.new.say_hello
end
end
A.new.test1
# => 'Hello World'
A.new.test2
# => 'Hello'
Unfortunately, that doesn't work: Refinements only work in script scope, in which case Refinements are only active after the call to using, or they work in module scope, in which case they are active for the whole module body, even before the call to using, so what we can do, is this (IMO, this is cleaner):
class A
using ExtendedFromGem
def test1
FromGem.new.say_hello
end
end
class A
def test2
FromGem.new.say_hello
end
end
A.new.test1
# => 'Hello World'
A.new.test2
# => 'Hello'
or this:
class A
def test2
FromGem.new.say_hello
end
end
using ExtendedFromGem
class A
def test1
FromGem.new.say_hello
end
end
A.new.test1
# => 'Hello World'
A.new.test2
# => 'Hello'
Et voilà: test1 sees the refinement, test2 doesn't.

If you want to change the behavior just in one place, then you do not need anything to do, just write few lines of code that meets your requirement using or without using the gem's method
If you are gonna need that modified behavior in multiple places then creating a method that implements the changed behavior. In this case; you are gonna use Adapter Design pattern;
Steps:
You create AdaptorClass that Uses the Original class's behavior to provide you the new behavior you desire.
Now, you use the Adaptor's behavior rather than original class's to do your job where required.
Try not to modify the original class; follow Open-Closed Principle
class A
def Test1
# need patch
# you use GemAdapter rather than FromGem
end
def Test2
# don't need patch
# use FromGem class
end
def Test3
# need patch
# you use GemAdapter rather than FromGem
end
end
class GemAdapter
def new_behavior
gem_instance = FromGem.new
# implement your new behavior you desire
end
end

This could also be a solution
class << is used to modify a particular instance; a part of meta-programming
class A
def print
p 'Original method'
end
end
class B
def initialize
#new_instance_of_a = A.new
end
def my_method
modifiable_a = A.new
# modifying the instance of class A i.e. modifiable_a
class << modifiable_a
def print
p 'Monkey patch'
end
end
modifiable_a .print
end
def normal_method
#new_instance_of_a.print
end
end
However, modifying a local_instance does not make more sense. If this modified instance could be used in some more places then using this method is worthy

Related

How to allow initializing a class from a particular class in ruby and nowhere else?

module A
module B
class Foo
def initialize(args)
#args = args
end
def call
puts 'Inside Foo'
end
end
end
end
module C
class Boo
def initialize(args)
#args = args
end
def call
puts 'Inside Boo'
A::B::Foo.new(#args).call
end
end
end
boo = C::Boo.new(nil).call
# Inside Boo
# Inside Foo
A::B::Foo.new(nil).call
# Inside Foo
How can I avoid A::B::Foo.new(nil).call ?
It should be accessible only from Boo class.
If anybody wants to access Foo class they will be able to access it from Boo.
How can I achieve this ?
Searched Internet but could not find what should be call this concept ?
This is ruby - so there's no such thing as an iron-clad way of making an object "private". (For example, you can access private methods via .send!) But you can at least define a private interface.
However, your interface doesn't actually make much sense from an OOP perspective. Why is A::B::Foo accessible only within C::Boo? If A::B::Foo is private, then it should only be accessible within A::B, and nowhere else.
The key method you're looking for is: private_constant
And you can circumvent a private constant lookup exception by using const_get.
Therefore we can "hack" your current implementation to work as follows:
module A
module B
class Foo
def initialize(args)
#args = args
end
def call
puts 'Inside Foo'
end
end
private_constant :Foo
end
end
module C
class Boo
def initialize(args)
#args = args
end
def call
puts 'Inside Boo'
A::B.const_get(:Foo).new(#args).call
end
end
end
# This works:
C::Boo.new(nil).call
# This errors:
A::B::Foo.new(nil).call

How to refactor code for deprecated alias_method_chain

I'm upgrading my rails application and I'm getting a warning saying alias_method_chain is deprecated. Please, use Module#prepend instead. But I'm not really understanding how to handle this. How can I change the code below?
def read_attribute_with_mapping(attr_name)
read_attribute_without_mapping(ADDRESS_MAPPING[attr_name] || attr_name)
end
alias_method_chain :read_attribute, :mapping
prepend is basically like importing a module, but it ends up "in front" of other code (so the module can call super to run the code it's in front of).
This is a runnable example with something close to your situation.
module MyModule
def read_attribute(attr_name)
super("modified_#{attr_name}")
end
end
class Example
prepend MyModule
def read_attribute(attr_name)
puts "Reading #{attr_name}"
end
end
Example.new.read_attribute(:foo)
# Outputs: Reading modified_foo
I defined read_attribute directly on Example, but it could just as well have been a method inherited from a superclass (such as ActiveRecord::Base).
This is a shorter but more cryptic version that uses an anonymous module:
class Example
prepend(Module.new do
def read_attribute(attr_name)
super("modified_#{attr_name}")
end
end)
def read_attribute(attr_name)
puts "Reading #{attr_name}"
end
end
Example.new.read_attribute(:foo)
# Outputs: Reading modified_foo
UPDATE:
Just for fun and to address a question below, here's how it could be done without having to explicitly make any modules yourself. I don't think I'd choose to do it this way myself, since it obscures a common pattern.
# You'd do this once somewhere, e.g. config/initializers/prepend_block.rb in a Rails app.
class Module
def prepend_block(&block)
prepend Module.new.tap { |m| m.module_eval(&block) }
end
end
# Now you can do:
class Example
prepend_block do
def read_attribute(attr_name)
super("modified_#{attr_name}")
end
end
def read_attribute(attr_name)
puts "Reading #{attr_name}"
end
end
Example.new.read_attribute(:foo)
# Outputs: Reading modified_foo

How to call `super` in a block

I have a block of code. It was:
class User < ActiveRecord::Base
def configuration_with_cache
Rails.cache.fetch("user_#{id}_configuration") do
configuration_without_cache
end
end
alias_method_chain :configuration, :cache
end
I want to remove the notorious alias_method_chain, so I decided to refactor it. Here is my version:
class User < ActiveRecord::Base
def configuration
Rails.cache.fetch("#{id}_agency_configuration") do
super
end
end
end
But it doesn't work. The super enters a new scope. How can I make it work? I got TypeError: can't cast Class, and I misunderstood it.
To start off, calling super in blocks does behave the way you want. Must be your console is in a corrupted state (or something).
class User
def professional?
Rails.cache.fetch("user_professional") do
puts 'running super'
super
end
end
end
User.new.professional?
# >> running super
# => false
User.new.professional?
# => false
Next, this looks like something Module#prepend was made to help with.
module Cacher
def with_rails_cache(method)
mod = Module.new do
define_method method do
cache_key = "my_cache_for_#{method}"
Rails.cache.fetch(cache_key) do
puts "filling the cache"
super()
end
end
end
prepend mod
end
end
class User
extend Cacher
with_rails_cache :professional?
end
User.new.professional?
# >> filling the cache
# => false
User.new.professional?
# => false
you can user Super in block.
please see this, any issues let me know.
Calling it as just 'super' will pass the block.
super(*args, &block)' will as well.

Is there a way to use class method in a module without extend it in rails?

currently I have a module like this:
module MyModule
def A
end
.....
end
and I have a model that I want to use that method A as a class method. However, the thing is I only need that A method. If I extend it, I am gonna extend the other unnecessary class methods into my model. Therefore, is there a way for me to do sth like MyModule.A without rewriting the module like this:
module MyModule
def A
...
end
def self.A
...
end
.....
end
It is kind of repeating myself if I do it that way. I still feel there is a better way to do it in Rails.
Use Module#module_function to make a single function to be a module function:
module M
def m1; puts "m1"; end
def m2; puts "m2"; end
module_function :m2
end
or:
module M
def m1; puts "m1"; end
module_function # from now on all functions are defined as module_functions
def m2; puts "m2"; end
end
M.m1 #⇒ NoMethodError: undefined method `m1' for M:Module
M.m2 #⇒ "m2"
Yes, you can define it as a module_function, then you should be able to access it using module name.
Ex:
module Mod
def my_method
100
end
def self.my_method_1
200
end
module_function :my_method
end
Mod.my_method
# => 100
Mod.my_method_1
# => 200
Note: No need to add the self defined methods in module_function, they are accessible directly. But it's needed for methods defined without self

How to alias a class method within a module?

I am using Ruby v1.9.2 and the Ruby on Rails v3.2.2 gem. I had the following module
module MyModule
extend ActiveSupport::Concern
included do
def self.my_method(arg1, arg2)
...
end
end
end
and I wanted to alias the class method my_method. So, I stated the following (not working) code:
module MyModule
extend ActiveSupport::Concern
included do
def self.my_method(arg1, arg2)
...
end
# Note: the following code doesn't work (it raises "NameError: undefined
# local variable or method `new_name' for #<Class:0x00000101412b00>").
def self.alias_class_method(new_name, old_name)
class << self
alias_method new_name, old_name
end
end
alias_class_method :my_new_method, :my_method
end
end
In other words, I thought to extend the Module class someway in order to add an alias_class_method method available throughout MyModule. However, I would like to make it to work and to be available in all my Ruby on Rails application.
Where I should put the file related to the Ruby core extension of the Module class? Maybe in the Ruby on Rails lib directory?
How should I properly "extend" the Module class in the core extension file?
Is it the right way to proceed? That is, for example, should I "extend" another class (Object, BasicObject, Kernel, ...) rather than Module? or, should I avoid implementing the mentioned core extension at all?
But, more important, is there a Ruby feature that makes what I am trying to accomplish so that I don't have to extend its classes?
You could use define_singleton_method to wrap your old method under a new name, like so:
module MyModule
def alias_class_method(new_name, old_name)
define_singleton_method(new_name) { old_name }
end
end
class MyClass
def my_method
puts "my method"
end
end
MyClass.extend(MyModule)
MyClass.alias_class_method(:my_new_method, :my_method)
MyClass.my_new_method # => "my method"
Answering your comment, you wouldn't have to extend every single class by hand. The define_singleton_method is implemented in the Object class. So you could simply extend the Object class, so every class should have the method available...
Object.extend(MyModule)
Put this in an initializer in your Rails app and you should be good to go...
I found an answer on this website: http://engineering.lonelyplanet.com/2012/12/09/monitoring-our-applications-ruby-methods/
The solution is to use class_eval with a block. That enables using variables from the enclosing scope.
module Alias
def trigger
#trigger = true
end
def method_added(name)
if #trigger
#trigger = false
with_x = "#{name}_with_x"
without_x = "#{name}_without_x"
define_method(with_x) do
"#{send(without_x)} with x"
end
alias_method without_x, name
alias_method name, with_x
end
end
def singleton_method_added(name)
if #trigger
#trigger = false
with_x = "#{name}_with_x"
without_x = "#{name}_without_x"
define_singleton_method(with_x) do
"singleton #{send(without_x)} with x"
end
singleton_class.class_eval do
alias_method without_x, name
alias_method name, with_x
end
end
end
end
class TestAlias
extend Alias
trigger
def self.foo
'foo'
end
trigger
def bar
'bar'
end
end
TestAlias.foo # => 'singleton foo with x'
TestAlias.new.bar # => 'bar with x'
If you don't have singleton_class then you should probably upgrade your version of Ruby. If that's not possible you can do this:
class Object
def singleton_class
class << self
self
end
end
end
The accepted answer was confusing and did not work.
class Module
def alias_class_method(new_name, old_name)
define_singleton_method(new_name, singleton_method(old_name))
end
end
module MyModule
def self.my_method
'my method'
end
end
MyModule.alias_class_method(:my_new_method, :my_method)
MyModule.my_new_method # => "my_method"

Resources