Ruby: DRY class methods calling Singleton instance methods [duplicate] - ruby

This question already has answers here:
Calling a method of a Ruby Singleton without the reference of 'instance'
(5 answers)
Closed 7 years ago.
I have the a Singleton class ExchangeRegistry which keeps all the Exchange objects.
Instead of needing to call:
ExchangeRegistry.instance.exchanges
I want to be able to use:
ExchangeRegistry.exchanges
This works, but I'm not happy with the repetition:
require 'singleton'
# Ensure an Exchange is only created once
class ExchangeRegistry
include Singleton
# Class Methods ###### Here be duplication and dragons
def self.exchanges
instance.exchanges
end
def self.get(exchange)
instance.get(exchange)
end
# Instance Methods
attr_reader :exchanges
def initialize
#exchanges = {} # Stores every Exchange created
end
def get(exchange)
#exchanges[Exchange.to_sym exchange] ||= Exchange.create(exchange)
end
end
I'm not happy with the duplication in the class methods.
I've tried using Forwardable and SimpleDelegator but can't seem to get this to DRY out. (Most examples out there are not for class methods but for instance methods)

The forwardable module will do this. Since you are forwarding class methods, you have to open up the eigenclass and define the forwarding there:
require 'forwardable'
require 'singleton'
class Foo
include Singleton
class << self
extend Forwardable
def_delegators :instance, :foo, :bar
end
def foo
'foo'
end
def bar
'bar'
end
end
p Foo.foo # => "foo"
p Foo.bar # => "bar"

The accepted answer is clever, but seems needlessly complex (not to mention to performance penalty of method_missing.
The usual way to solve this is to just assign the instance to a constant.
class ExchangeRegistrySingleton
include Singleton
# ...
end
ExchangeRegistry = ExchangeRegistrySingleton.instance

You can take advantage of method_missing hook and delegate the method calls to instance.
require 'singleton'
class ExchangeRegistry
include Singleton
# Missing methods will be delegated to `instance` if an implementation is available.
# Else `NoMethodError` will be raised via call to `super`
def self.method_missing method_name, *args
if instance.respond_to? method_name
puts "** Defining new method: '#{method_name}'"
(class << self; self; end).instance_eval do
define_method(method_name) do |*args|
instance.send(method_name, *args)
end
end
instance.send(method_name, *args)
else
super
end
end
attr_reader :exchanges
def initialize
#exchanges = {} # Stores every Exchange created
end
def get(exchange)
#exchanges[Exchange.to_sym exchange] ||= Exchange.create(exchange)
end
end
# By default, there is no class method - `exchanges`
p ExchangeRegistry.singleton_methods.grep(/exchanges/)
#=> []
p ExchangeRegistry.exchanges
#=> ** Defining new method: 'exchanges'
#=> {}
# After first call to `exchanges`, a new class method is now available
# Future calls will not hit `method_missing` again.
p ExchangeRegistry.singleton_methods.grep(/exchanges/)
#=> [:exchanges]
p ExchangeRegistry.exchanges
#=> {}
Another answer of this question indicates that there is performance penalty for handling method_missing. Hence, I have updated the answer to define the class method when first time a method_missing is reported. The update is based on article: Dynamically create class methods in Ruby

Related

How to call any instance method in ruby object without instantiating it?

I am creating a helper module to initialize the object before calling its methods
module Initialized
extend ActiveSupport::Concern
class_methods do
def run(*args)
new(*args).run
end
def call(*args)
new(*args).call
end
def execute(*args)
new(*args).create
end
end
end
So instead of defining run, call, and execute in my helper module I need to receive any method name and check if it exists on the main class after initializing it, then call the requested instance method if exists in the main class or raise an error if not
I would say my targeted code would be something like this
module Initialized
extend ActiveSupport::Concern
class_methods do
def _(*args, methodname)
new(*args).try(:send, "#{methodname}") || raise 'Method not exist'
end
end
end
Sample usage would be
class MyClass
include Initialized
def initialize(param1)
#param1 = param1
end
def call
puts "call '#{#param1}'"
end
end
then calling
MyClass.call('method param')
I found these links but couldn't find my answer yet:
meta-dynamic-generic-programming-in-ruby
ruby-module-that-delegates-methods-to-an-object
template-methods-in-ruby
Despite the fact method_missing would do the job, I'd probably avoid it in this case in favor of a more explicit delegation. Simple and dirty example:
module InstanceDelegator
def delegate_to_instance(*methods)
methods.each do |method_name|
define_singleton_method method_name do |*args|
new(*args).public_send(method_name)
end
end
end
end
class Foo
extend InstanceDelegator
delegate_to_instance :bar # <- define explicitly which instance methods
# should be mirrored by the class ones
def bar
puts "bar is called"
end
def baz
puts "baz is called"
end
end
# and then
Foo.bar # => bar is called
Foo.baz # NoMethodError ...
# reopening works too
class Foo
delegate_to_instance :baz
end
Foo.baz # baz is called
Pros:
you don't need to redefine method_missing (less magic -> less pain when you debug the code)
you control precisely which instance methods to be wrapped with the class level "shorthand" (fewer chances to delegate something you don't want to - more robust code)
(minor) no need to raise NoMethodError explicitly - you can fully rely on the core dispatching as it is...
I found another solution instead of using a module,
I can use the class method self.method_missing
def self.method_missing(method_name, *args, &block)
obj = new(*args)
raise NoMethodError, "undefined method `#{method_name}' for #{self}:Class" unless obj.respond_to?(method_name)
obj.send(method_name, &block)
end
But the limitation is that I have to copy it into every class whenever I need to use this feature

Calling original method from a redefined one

My question is based on an answer to the topic “redefining a single ruby method on a single instance with a lambda”.
How can I redefine a method and from within the new method call the original definition? Other instances of some_object's class should not become affected.
def some_object.some_method
# call original `some_object.some_method` here
do_something_else
end
If some_object.some_method is not a singleton method, then you can just call super in your redefined method.
def some_object.some_method
super
do_something_else
end
If some_object.some_method is a singleton method, then
Ruby >= 2.0.0
You can define that method in a module
module SomeModule
def some_method
super
do_something_else
end
end
And then prepend it to the singleton class of the object
some_object.singleton_class.prepend(SomeModule)
Ruby < 2.0.0
You have to make an alias then redefine, since there is no Module#prepend.
class << some_object # open the singleton class of some_object
alias some_method_original some_method
def some_method
some_method_original
do_something_else
end
end
class Klass
def greeting
puts "hiya"
end
def add_personal_greeting(str)
define_singleton_method(:greeting) do
super
puts str
end
end
end
Bob gets a handle and tries the standard greeting.
bob = Klass.new
#=> #<Klass:0x007fa66b084ad0>
bob.greeting
# hiya
He finds that too impersonal so he decides to add "I'm Bob" after the greeting. He does that by defining a method greeting on his singleton class that calls Klass's instance method greeting and then adds another line to the greeting.
bob.add_personal_greeting("I'm Bob")
He tries it.
bob.greeting
# hiya
# I'm Bob
Much better. Note
bob.singleton_class.superclass
#=> Klass
Meanwhile, Lucy tries the standard greeting.
lucy = Klass.new
#=> #<Klass:0x007fa66a9ed050>
lucy.greeting
# hiya
She's not wild about it, but it will do.
Bob decides to change his greeting.
bob.add_personal_greeting("I'm Robert")
and tries it.
bob.greeting
# hiya
# I'm Robert

Ruby Singleton avoid using instance member

I like Ruby's singleton but I would like to make usage of it better so here is example
require 'singleton'
class Foo
include Singleton
def initialize
# code to setup singleton here
end
def self.get_bar
Foo.instance.get_bar
end
def get_bar
end
def get_nar
end
end
Usage
Foo.instance.get_bar (default) or Foo.get_bar (due to static self.get_bar method I made)
Is there elegant way to make all methods accessible without me having to write static wrapper for each method? Just seems redundant to have to write for each method .instance
UPDATE
Ruby 1.8.7
You could mix this module:
module DelegateToSingleton
def respond_to_missing?(method)
super || instance.respond_to?(method)
end
def method_missing(method, *args)
instance.send(method, *args)
end
end
into your singleton:
class Foo
extend DelegateToSingleton
include Singleton
def foo
'foo'
end
def bar
'bar'
end
end
with these results:
p Foo.foo # => "foo"
p Foo.bar # => "bar"
DelegateToSingleton::method_missing is what makes it work: Whenever Foo receives a method it doesn't know about, it just forwards it to its instance.
DelegateToSingleton::respond_to_missing? is not strictly needed, but having it is good manners whenever playing tricks with method_missing.
For Ruby earlier than 1.9.2: Override respond_to? instead of respond_to_missing?
Just separate the class from the instance:
class Foo
def initialize
end
def get_bar
end
def get_nar
end
end
MyFoo = Foo.new
MyFoo.get_bar

Using a mixin method inside an instance method

Why does this not work:
class Myclass
include HTTParty
def dosomething
base_uri("some_url")
end
end
The base_uri method is a class method of HTTParty. It works fine if I call it from my class, outside of any instance methods, or from a class method, but when try to call it from an instance method I get "NoMethodError: undefined method `base_uri' for #"
Why? Shouldn't there be some way to refer to the HTTParty class from within my instance method so I can call that HTTParty class method?
I could change it to a class method, but then every instance of my class would have the same value for base_uri.
Why doesn't it work? Because that's not how Ruby works. Similarly, this doesn't work:
class Foo
def self.utility_method; ...; end
def inst_method
utility_method # Error! This instance has no method named "utility_method"
end
end
You could work around this by just doing:
class MyClass
include HTTParty
def dosomething
HTTParty.base_uri("some_url")
end
end
Let's look deeper at how method lookup works with modules. First, some code:
module M
def self.m1; end
def m2; end
end
class Foo
include M
end
p Foo.methods - Object.methods #=> []
p Foo.new.methods - Object.methods #=> [:m2]
class Bar
extend M
end
p Bar.methods - Object.methods #=> [:m2]
p Bar.new.methods - Object.methods #=> []
class Jim; end
j = Jim.new
j.extend M
p j.methods - Object.methods #=> [:m2]
As we see, you can use extend to cause an object (a class or instance) to use the 'instance' methods of a module for the object itself (instead of instances), but you cannot cause 'class methods' of the module to be inherited by anything. The closest you can get is this idiom:
module M2
module ClassMethods
def m1; end # Define as an instance method of this sub-module!
end
extend ClassMethods # Make all methods on the submodule also my own
def self.included(k)
k.extend(ClassMethods) # When included in a class, extend that class with
end # my special class methods
def m2; end
end
class Foo
include M2
end
p Foo.methods - Object.methods #=> [:m1]
p Foo.new.methods - Object.methods #=> [:m2]
If the HTTParty module used the above pattern, and so made the base_uri method available on your MyClass, then you could do this:
class MyClass
include HTTParty
def dosomething
self.class.base_uri("some_url")
end
end
...but that's more work than just directly referencing the module owning the method.
Finally, because this might help you, here's a diagram I made some years ago. (It's missing some core objects from Ruby 1.9, like BasicObject, but is otherwise still applicable. Click for a PDF version. Note #3 from the diagram is particularly applicable.)
(source: phrogz.net)
class Myclass
include HTTParty
def dosomething
Myclass.base_uri("some_url")
end
end

Inheriting class methods from modules / mixins in Ruby

It is known that in Ruby, class methods get inherited:
class P
def self.mm; puts 'abc' end
end
class Q < P; end
Q.mm # works
However, it comes as a surprise to me that it does not work with mixins:
module M
def self.mm; puts 'mixin' end
end
class N; include M end
M.mm # works
N.mm # does not work!
I know that #extend method can do this:
module X; def mm; puts 'extender' end end
Y = Class.new.extend X
X.mm # works
But I am writing a mixin (or, rather, would like to write) containing both instance methods and class methods:
module Common
def self.class_method; puts "class method here" end
def instance_method; puts "instance method here" end
end
Now what I would like to do is this:
class A; include Common
# custom part for A
end
class B; include Common
# custom part for B
end
I want A, B inherit both instance and class methods from Common module. But, of course, that does not work. So, isn't there a secret way of making this inheritance work from a single module?
It seems inelegant to me to split this into two different modules, one to include, the other to extend. Another possible solution would be to use a class Common instead of a module. But this is just a workaround. (What if there are two sets of common functionalities Common1 and Common2 and we really need to have mixins?) Is there any deep reason why class method inheritance does not work from mixins?
A common idiom is to use included hook and inject class methods from there.
module Foo
def self.included base
base.send :include, InstanceMethods
base.extend ClassMethods
end
module InstanceMethods
def bar1
'bar1'
end
end
module ClassMethods
def bar2
'bar2'
end
end
end
class Test
include Foo
end
Test.new.bar1 # => "bar1"
Test.bar2 # => "bar2"
Here is the full story, explaining the necessary metaprogramming concepts needed to understand why module inclusion works the way it does in Ruby.
What happens when a module is included?
Including a module into a class adds the module to the ancestors of the class. You can look at the ancestors of any class or module by calling its ancestors method:
module M
def foo; "foo"; end
end
class C
include M
def bar; "bar"; end
end
C.ancestors
#=> [C, M, Object, Kernel, BasicObject]
# ^ look, it's right here!
When you call a method on an instance of C, Ruby will look at every item of this ancestor list in order to find an instance method with the provided name. Since we included M into C, M is now an ancestor of C, so when we call foo on an instance of C, Ruby will find that method in M:
C.new.foo
#=> "foo"
Note that the inclusion does not copy any instance or class methods to the class – it merely adds a "note" to the class that it should also look for instance methods in the included module.
What about the "class" methods in our module?
Because inclusion only changes the way instance methods are dispatched, including a module into a class only makes its instance methods available on that class. The "class" methods and other declarations in the module are not automatically copied to the class:
module M
def instance_method
"foo"
end
def self.class_method
"bar"
end
end
class C
include M
end
M.class_method
#=> "bar"
C.new.instance_method
#=> "foo"
C.class_method
#=> NoMethodError: undefined method `class_method' for C:Class
How does Ruby implement class methods?
In Ruby, classes and modules are plain objects – they are instances of the class Class and Module. This means that you can dynamically create new classes, assign them to variables, etc.:
klass = Class.new do
def foo
"foo"
end
end
#=> #<Class:0x2b613d0>
klass.new.foo
#=> "foo"
Also in Ruby, you have the possibility of defining so-called singleton methods on objects. These methods get added as new instance methods to the special, hidden singleton class of the object:
obj = Object.new
# define singleton method
def obj.foo
"foo"
end
# here is our singleton method, on the singleton class of `obj`:
obj.singleton_class.instance_methods(false)
#=> [:foo]
But aren't classes and modules just plain objects as well? In fact they are! Does that mean that they can have singleton methods too? Yes, it does! And this is how class methods are born:
class Abc
end
# define singleton method
def Abc.foo
"foo"
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
Or, the more common way of defining a class method is to use self within the class definition block, which refers to the class object being created:
class Abc
def self.foo
"foo"
end
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
How do I include the class methods in a module?
As we just established, class methods are really just instance methods on the singleton class of the class object. Does this mean that we can just include a module into the singleton class to add a bunch of class methods? Yes, it does!
module M
def new_instance_method; "hi"; end
module ClassMethods
def new_class_method; "hello"; end
end
end
class HostKlass
include M
self.singleton_class.include M::ClassMethods
end
HostKlass.new_class_method
#=> "hello"
This self.singleton_class.include M::ClassMethods line does not look very nice, so Ruby added Object#extend, which does the same – i.e. includes a module into the singleton class of the object:
class HostKlass
include M
extend M::ClassMethods
end
HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
# ^ there it is!
Moving the extend call into the module
This previous example is not well-structured code, for two reasons:
We now have to call both include and extend in the HostClass definition to get our module included properly. This can get very cumbersome if you have to include lots of similar modules.
HostClass directly references M::ClassMethods, which is an implementation detail of the module M that HostClass should not need to know or care about.
So how about this: when we call include on the first line, we somehow notify the module that it has been included, and also give it our class object, so that it can call extend itself. This way, it's the module's job to add the class methods if it wants to.
This is exactly what the special self.included method is for. Ruby automatically calls this method whenever the module is included into another class (or module), and passes in the host class object as the first argument:
module M
def new_instance_method; "hi"; end
def self.included(base) # `base` is `HostClass` in our case
base.extend ClassMethods
end
module ClassMethods
def new_class_method; "hello"; end
end
end
class HostKlass
include M
def self.existing_class_method; "cool"; end
end
HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
# ^ still there!
Of course, adding class methods is not the only thing we can do in self.included. We have the class object, so we can call any other (class) method on it:
def self.included(base) # `base` is `HostClass` in our case
base.existing_class_method
#=> "cool"
end
As Sergio mentioned in comments, for guys who are already in Rails (or don’t mind depending on Active Support), Concern is helpful here:
require 'active_support/concern'
module Common
extend ActiveSupport::Concern
def instance_method
puts "instance method here"
end
class_methods do
def class_method
puts "class method here"
end
end
end
class A
include Common
end
You can have your cake and eat it too by doing this:
module M
def self.included(base)
base.class_eval do # do anything you would do at class level
def self.doit #class method
##fred = "Flintstone"
"class method doit called"
end # class method define
def doit(str) #instance method
##common_var = "all instances"
#instance_var = str
"instance method doit called"
end
def get_them
[##common_var,#instance_var,##fred]
end
end # class_eval
end # included
end # module
class F; end
F.include M
F.doit # >> "class method doit called"
a = F.new
b = F.new
a.doit("Yo") # "instance method doit called"
b.doit("Ho") # "instance method doit called"
a.get_them # >> ["all instances", "Yo", "Flintstone"]
b.get_them # >> ["all instances", "Ho", "Flintstone"]
If you intend to add instance, and class variables, you will end up pulling out your hair as you will run into a bunch of broken code unless you do it this way.

Resources