Ruby - extending method with super using splat - ruby

In "Comprehensive Ruby programming course" e-book I have a case when the child class method extends parents method. I am not completely aware how it works:
class Parent
def initialize(foo:, bar:)
#foo = foo
#bar = bar
end
end
class Child < Parent
def initialize(buzz:,**args)
super(**args)
#buzz = buzz
end
end
I cant completely understand why we use splat here - **args.
In here def initialize(buzz:,**args) we are just telling initialize to take unknown number of key-value arguments, right? But what exactly this means super(**args). To tell method to take those key-value arguments from the superclass method? Why not just like this:
class Child < Parent
def initialize(buzz:)
super
#buzz = buzz
end
end
After all, super tells to extend method with whatever there is in the parent, so why these splat args needed?

**args in the parameter list simply means "get all extra keyword arguments and put them in a hash, called args".
Conversely, **args when calling a method does the opposite - "get this hash called args and pass keyword arguments with the corresponding names and values from that hash".
super without arguments will try to pass all arguments that the child method received. Hence if you have extra that the parent didn't expect, you will get an ArgumentError.
In your example, the parent expects only foo: and bar:, while the child also has buzz:.

Related

difference between calling super and calling super()

What is the difference between calling super and calling super()? Which is the best one if the arguments passed to the child method don’t match what the parent is expecting.
When you call super with no arguments, Ruby sends a message to the parent of the current object, asking it to invoke a method with the same name as where you called super from, along with the arguments that were passed to that method.
On the other hand, when called with super(), it sends no arguments to the parent.
If the arguments you have don't match what the parent is expecting, then I would say you would want to use super(), or explicitly list parameters in the functional call to match a valid parent constructor.
Dictates arguments that are sent up the object ancestor chain
super - sends all arguments passed to the function to parent
super() - no arguments
super equals to super(*args), which brings all args to the inherited method
Use super() when you just want to call the method inherited from Parent without passing args
super example:
class Parent
def say(message)
p message
end
end
class Child < Parent
def say(message)
super
end
end
Child.new.say('Hello world!') # => "Hello world!"
super() examples:
class Parent
def say
p "I'm the parent"
end
end
class Child < Parent
def say(message)
super
end
end
Child.new.say('Hello!') # => ArgumentError (wrong number of arguments (given 1, expected 0))
class Parent
def say
p "I'm the parent"
end
end
class Child < Parent
def say(message)
super()
end
end
Child.new.say('Hi!') # => "I'm the parent"

Binding method to instance

Is there a way to bind an existing method to an existing instance of an object if both the method and the instance are passed as symbols into a method that does that if the instance is not a symbol?
For example:
def some_method
#do something
end
some_instance = Klass.new(something)
def method_that_binds(:some_method, to: :some_instance)
#how do I do that?
end
Your requirements are a little unusual, but it is possible to do this mostly as you say:
class Person; end
harry = Person.new
barry = Person.new
def test
puts 'It works!'
end
define_method :method_that_binds do |a_method, to|
eval(to[:to].to_s).singleton_class.send(:define_method, a_method, &Object.new.method(a_method))
end
method_that_binds :test, to: :harry
harry.test
# It works! will be sent to STDOUT
barry.test
# undefined method 'test'
This doesn't actually use a named parameter, but accepts a hash with a to key, but you can see you can call it in the way you want. It also assumes that the methods you are defining are defined globally on Object.
The API you want doesn't easily work, because you have to know from which scope you want to access the local variable. It's not quite clear to me why you want to pass the name of the local variable instead of passing the content of the local variable … after all, the local variable is present at the call site.
Anyway, if you pass in the scope in addition to the name, this can be accomplished rather easily:
def some_method(*args)
puts args
puts "I can access some_instance's ivar: ##private_instance_var"
end
class Foo; def initialize; #private_instance_var = :foo end end
some_instance = Foo.new
def method_that_binds(meth, to:, within:, with: [])
self.class.instance_method(meth).bind(within.local_variable_get(to)).(*with)
end
method_that_binds(:some_method, to: :some_instance, within: binding, with: ['arg1', 'arg2'])
# arg1
# arg2
# I can access some_instance's ivar: foo
As you can see, I also added a way to pass arguments to the method. Without that extension, it becomes even simpler:
def method_that_binds(meth, to:, within:)
self.class.instance_method(meth).bind(within.local_variable_get(to)).()
end
But you have to pass the scope (Binding) into the method.
If you'd like to add a method just to some_instance i.e. it's not available on other instances of Klass then this can be done using define_singleton_method (documentation here.)
some_instance.define_singleton_method(:some_method, method(:some_method))
Here the first use of the symbol :some_method is the name you'd like the method to have on some_instance and the second use as a parameter to method is creating a Method object from your existing method.
If you'd like to use the same name as the existing method you could wrap this in your own method like:
def add_method(obj, name)
obj.define_singleton_method(name, method(name))
end
Let's say we have a class A with a method a and a local variable c.
class A
def a; 10 end
end
c = '5'
And we want to add the method A#a to c.
This is how it can be done
c.singleton_class.send :define_method, :b, &A.new.method(:a)
p c.b # => 10
Explanations.
One way to add a method to an object instance and not to its class is to define it in its singleton class (which every ruby object has).
We can get the c's singleton class by calling the corresponding method c.signleton_class.
Next we need to dynamically define a method in its class and this can usually be accomplished by using the define_method which takes a method name as its first argument (in our case :b) and a block. Now, converting the method into a block might look a bit tricky but the idea is relatively simple: we first transform the method into a Method instance by calling the Object#method and then by putting the & before A.new.method(:a) we tell the interpreter to call the to_proc method on our object (as our returned object is an instance of the Method, the Method#to_proc will be called) and after that the returned proc will be translated into a block that the define_method expects as its second argument.

Splat operator and method overriding

I have a base class with child classes that override a method that takes multiple arguments.
class Parent
def foo *bar
end
end
class Child < Parent
def foo bar, baz
end
end
This works fine. However, suppose there is a method foobar in Parent that calls foo:
def foobar *foo_args
foo foo_args
end
This raises a ArgumentError when called on a Child instance because foo_args is one single array, while Child.new.foo expects two objects. Is there a way around this?
Your question is not clear, but I think this may be what you want:
def foobar *foo_args
foo(*foo_args)
end
Still, Child.new.foo must take exactly two arguments for it to not raise an error.

Why do method definitions return symbols?

When you define a method, it returns a symbol with the same name as the method. Is there a point to this? Or is it just there as validation that you created it?
Like so:
def something
...
end
# => :something
IRb always displays the result of calling inspect on the value of the last expression that was evaluated. It doesn't matter whether that expression is a literal expression, a conditional expression, a message send, a class definition expression or a method definition expression.
Everything returns a value in Ruby, i.e. everything is an expression, there is no such thing as a statement in Ruby.
In the past, the return value of a method definition expression was undefined. Most Ruby implementations simply returned nil from a method definition expression, but Rubinius for example returned the CompiledMethod object for the method that was defined.
With Ruby 2.1, the return value of a method definition expression was standardized to be the Symbol corresponding to the method's name. This allows you to use the method definition expression as an argument in methods that expect the name of a method as an argument.
Some examples:
# Before Ruby 2.0:
def foo; end
private :foo
# After Ruby 2.0:
private def foo; end # similar for `protected`, `public`, `module_function`
# Before Ruby 2.0:
def map; end
alias_method :collect, :map
# After Ruby 2.0:
alias_method :collect, def map; end
On a personal note, I would have preferred a method definition expression to evaluate to an UnboundMethod object corresponding to that method, and methods like public, private, protected, alias_method, module_function etc. should be amended to accept UnboundMethods in addition to Symbols and Strings.
The person who proposed this had in mind a usage like this:
private def foo
...
end
protected def bar
...
end
Methods such as public, private, protected take symbols as arguments. The point was to make use of this syntax.
All method defs return symbols in Ruby >=2.1 (not just the ones in IRB).
For example:
class Foo
p def bar; end
end
# => prints :bar
Why is this interesting?
You may have noticed that there are many methods, particularly class-level methods, that take the symbolized name of another method as an argument. You may be familiar with before_filter in Rails controllers. Since method defs return symbols, you could potentially do this:
class MyController < ApplicationController
before_filter def my_filter
# do stuff
end
end
IRB respects the ruby standard “the result of last executed statement is returned from method.” Imagine the code:
def a
def b
# do stuff
end
end
What is the result of execution this code? It follows:
a
# => :b
a.class
# => Symbol < Object
That said, IRB executes the method definition and returns/prints out it’s result. Which is, apparently, a Symbol instance.

Copy all methods from one class to another on the fly

I have two classes and I want to copy all of the methods from one class to another. Some methods will have no arguments, some will have arguments, and some will have hashes as arguments. And I never know in advance which ones will. So I created this code, until I figured out that it didn't take into account arguments. Is there any way to get a list of methods from a Class, and then clone them exactly to another class?
def partial(cls)
cls.instance_methods(false).each do |method_name|
define_method(method_name) do
cls.new.method(method_name.to_sym).call
end
end
end
The methods are created on the fly using define_method in the first class, so I can't just use an include. The code above has cls being passed in, then it finds all of the instance methods that are actually written in that Class, not ones it inherits, and then creates a new method with the same name. When that method is called, it actually calls the other Class with its method of the same name. This works wonderfully, unless I have args. I had condition check to see if it had arguments, and then had it call a method with arguments, but it did not handle hashes very well. It made the hash as an array for an argument, which is not what I wanted.
I was wondering if there was a simple way to literally say "Hey you know this method, whatever it is, literally make the same thing for this other Class."
you could also try DelegateClass:
class NamedArray < DelegateClass(Array)
def initialize n
#name = n
super(Array.new)
end
def sayName
"My name is #{#name}"
end
end
You could try SimpleDelegator: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/delegate/rdoc/SimpleDelegator.html
If all the methods are identical, why not just define them in a common module which you include in both classes? You mention not using include because the methods are dynamically defined, but that doesn't mean they won't be found when you mixin the module:
module Foo
def self.make_an_example_method(name)
define_method(name) do |*args|
puts "I am #{name} called with (#{args.inspect})"
end
end
end
class A
include Foo
end
class B
include Foo
end
Foo.make_an_example_method(:example)
Foo.make_an_example_method(:dynamic)
A.new.example # => I am example called with ([])
B.new.dynamic(1,2,3) # => I am dynamic called with ([1, 2, 3])

Resources