Child constructor arguments from the parent - ruby

How can a parent get the constructor arguments of a child ?
class A
include Parent
def initialize(foo, bar)
#foo = foo
#bar = bar
end
end
class B
include Parent
def initialize(foo)
#foo = foo
end
end
module Parent
def print_args
# here is the code for print args of child, this is not real code
puts Child.args # this is not real code
end
end
The expected behavior would be :
a = A.new('hello', 'world')
a.print_args
=> "hello world"
b = B.new('hello')
b.print_args
=> "hello"
The Parent module should not now the args names

One way is to have the "children" implement a method that returns their arguments:
class A
include Parent
def initialize(foo, bar)
#foo = foo
#bar = bar
end
def args
[#foo, #bar]
end
end
class B
include Parent
def initialize(foo)
#foo = foo
end
def args
[#foo]
end
end
The "parent" can than call that method without having to know its implementation:
module Parent
def print_args
puts args.join(' ')
end
end

If your module is included in many classes and you want to display instance variable values space separated, then you can do as follow,
using only ruby,
def print_args
instance_variables.map { |x| instance_variable_get(x) }.join(' ')
end
using rails,
def print_args
instance_values.values.join(' ')
end

You're asking how to get the "constructor arguments from the parent" and since almost everything is possible in Ruby: if you're really adventurous (read: don't do this), you can override the new method upon including Parent in order to intercept its arguments and define a singleton method on the instance which prints the argument:
module Parent
def self.included(mod)
def mod.new(*args)
super.tap do |instance|
instance.define_singleton_method(:print_args) do
puts args.join(' ')
end
end
end
end
end
Example usage:
class A
include Parent
def initialize(foo, bar)
end
end
A.new('hello', 'world').print_args
# prints "hello world"
The instance doesn't even have to store the arguments in instance variables.

Related

How to work through name collisions in ruby

Two modules Foo and Baa respectively define a method with the same name name, and I did include Foo and include Baa in a particular context.
When I call name, how can I disambiguate whether to call the name method of Foo or Baa?
Only the order of modules inclusion decides which one will get called. Can't have both with the same name - the latter will override the former.
Of course, you can do any tricks, just from the top of my head:
module A
def foo
:foo_from_A
end
end
module B
def foo
:foo_from_B
end
end
class C
def initialize(from)
#from = from
end
def foo
from.instance_method(__method__).bind(self).call
end
private
attr_reader :from
end
C.new(A).foo #=> :a_from_A
C.new(B).foo #=> :a_from_B
But that's no good for real life use cases :)
Technically, there is no name collision because the method foo is redefined.
In the following exemple, A.foo is redefined and is never called
module A
def foo
raise "I'm never called"
end
end
module B
def foo
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_B
If you write A and B module, you can use super to call previous definition of foo. As if it where an inherited method.
module A
def foo
puts :foo_from_A
end
end
module B
def foo
super
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_A
# foo_from_B
There are side effects and I would not use this but this is doing the trick :
module A
def foo
puts :foo_from_A
end
end
module B
def foo
puts :foo_from_B
end
end
class C
def self.include_with_suffix(m, suffix)
m.instance_methods.each do |method_name|
define_method("#{method_name}#{suffix}", m.instance_method(method_name))
end
end
include_with_suffix A, "_from_A"
include_with_suffix B, "_from_B"
end
c= C.new
c.foo_from_A
c.foo_from_B
begin
c.foo
rescue NoMethodError
puts "foo is not defined"
end
# =>
# foo_from_A
# foo_from_B
# foo is not defined
Provided none of the methods of Foo or Baa call name (which seems a reasonable assumption), one can simply create aliases.
module Foo
def name; "Foo#name"; end
end
module Baa
def name; "Baa#name"; end
end
class C
include Foo
alias :foo_name :name
include Baa
alias :baa_name :name
undef_method :name
end
c = C.new
c.foo_name
#=> "Foo#name"
c.baa_name
#=> "Baa#name"
C.instance_methods & [:foo_name, :baa_name, :name]
#=> [:foo_name, :baa_name]
The keyword alias is documented here. One may alternatively use the method #alias_method. See this blog for a comparison of the two.
Module#undef_method is not strictly necessary. It's just to ensure that an exception is raised if name is called.
You should definetely read about method lookups.
Anyway, I would do it this way:
module Foo
def name
:foo
end
end
module Bar
def name
:bar
end
end
class MyClass
include Foo
include Bar
def foo_name
Foo.instance_method(:name).bind(self).call
end
def bar_name
Bar.instance_method(:name).bind(self).call
end
#
# or even like this: obj.name(Foo)
#
def name(mod)
mod.instance_method(:name).bind(self).call
end
end
BTW if you are using Module#instance_method and UnboundMethod#bind you don't really need to include specific module. This code works:
Foo.instance_method(:name).bind('any object (e.g. string)').call

In Ruby, what's the best way to execute a block around a child method?

I have a parent class:
class Base
def my_method
block_method do
# EXECUTE WHATEVER'S IN THE CHILD VERSION OF my_method
# HOW TO DO?
end
end
def block_method
original_foo = 'foo'
foo = 'CUSTOM FOO'
yield
foo = original_foo
end
end
and some child classes--currently five and there will be more:
class ChildA < Base
def my_method
puts 'apple'
puts 'aardvark'
end
end
class ChildE < Base
def my_method
puts 'eel'
puts 'elephant'
end
end
I want to change what a variable refers to just for the duration of each Child's #my_method.
My thought is to do this by wrapping the functionality of each Child's #my_method in a block. But I'd rather do that in the parent class than have to wrap each child class's #my_method in the exact same block.
Any insights on how I can do this?
If there's some opposite of super, I guess that would be one way to accomplish what I want to do.
You could give the idea of what you are calling "some opposite of super" using a module and prepend like so:
module MethodThing
# added to remove binding from class since
# #my_method relies on the existence of #foo and #foo=
def self.prepended(base)
base.attr_reader(:foo) unless base.method_defined?(:foo)
base.attr_writer(:foo) unless base.method_defined?(:foo=)
end
def my_method
puts "Before: #{foo}"
original_foo = foo
self.foo= 'CUSTOM FOO'
begin
super
rescue NoMethodError
warn "(skipped) #{self.class}##{__method__} not defined"
end
self.foo = original_foo
puts "After: #{foo}"
end
end
Prepend the module on inheritance
class Base
def self.inherited(child)
child.prepend(MethodThing)
end
attr_accessor :foo
def initialize
#foo = 12
end
end
class ChildA < Base
def my_method
puts 'apple'
puts "During: #{foo}"
puts 'aardvark'
end
end
class ChildE < Base
end
Output:
ChildA.new.my_method
# Before: 12
# apple
# During: CUSTOM FOO
# aardvark
# After: 12
ChildE.new.my_method
# Before: 12
# (skipped) ChildE#my_method not defined
# After: 12
There are other strange ways to accomplish this with inheritance as well such as
class Base
class << self
attr_accessor :delegate_my_method
def method_added(method_name)
if method_name.to_s == "my_method" && self.name != "Base"
warn "#{self.name}#my_method has been overwritten use delegate_my_method instead"
end
end
end
attr_accessor :foo
def my_method
puts "Before: #{foo}"
original_foo = foo
self.foo= 'CUSTOM FOO'
begin
method(self.class.delegate_my_method.to_s).()
rescue NameError, TypeError
warn "(skipped) #{self.class} method delegation not defined"
end
self.foo = original_foo
puts "After: #{foo}"
end
end
class ChildA < Base
self.delegate_my_method = :delegation_method
def delegation_method
puts 'apple'
puts "During: #{foo}"
puts 'aardvark'
end
end
I could probably keep going with stranger and stranger ways to solve this problem but I think these will get you where you need to go.
One option would be to define a private or nodoc method which the parent class can call and is defined in each of the children.
class Parent
def my_method
block_method do
my_method_behavior
end
end
def block_method
original_foo = #foo
#foo = 'CUSTOM FOO'
yield
#foo = original_foo
end
end
class Child1 < Parent
def my_method_behavior
puts #foo
end
end
class Child2 < Parent
def my_method_behavior
puts #foo
end
end

Pass optional arguments to superclass

module MyModule
class Parent
def initialize(a, b=nil)
begin
#my_var = b[:some_key]
rescue
#my_var = {}
end
end
end
end
module MyModule
class Child < Parent
def initialize(a, b=nil)
super a, b
begin
#my_var = b[:some_key]
rescue
#my_var = {:some_key => 'something more specific'}
end
end
end
end
I have two classes, that I'm basically using begin / rescue / end to set an optional parameter as an instance variable. I KNOW that I'm using exception handling for control flow here, I just don't know another way! How can I set #my_var to either itself (non-nil) or an empty hash?
I've tried...
def initialize(a, b=nil)
b ||= {}
super
end
I guess my main problem is I don't understand how optional args and super work? Does the fact that I'm inheriting within a module change anything?
The default value is used if no parameter is passed, so you can use a different default value for the child and parent (since super explicitly passes the optional parameter).
Actually (as #CarySwoveland has commented) you don't even have to write the parameters to super, since writing super without parameters implicitly passes the original parameters.:
module MyModule
class Parent
def initialize(a, b={})
#my_var = b[:some_key]
end
attr_accessor :my_var
end
end
module MyModule
class Child < Parent
def initialize(a, b={some_key: 'something more specific'})
super
end
end
end
MyModule::Parent.new('a').my_var
# => nil
MyModule::Child.new('a').my_var
# => "something more specific"
In ruby versions of 2.0 onwards, you could use Keyword Arguments to do the same thing more expressively:
module MyModule
class Parent
def initialize(a, some_key: nil)
#my_var = some_key
end
attr_accessor :my_var
end
end
module MyModule
class Child < Parent
def initialize(a, some_key: 'something more specific')
super
end
end
end

Use method in different context, instance_eval adds unwanted argument

I'm trying to call a method of an object foo as if it was an method of object bar. I tried two approaches:
1. unbind and bind - fails because of different classes
class Foo
def initialize
#name = "John"
end
end
class Bar
def out
puts #name
end
end
foo = Foo.new
bar = Bar.new
m = bar.method :out
foo.instance_eval m.unbind.bind(foo)
2. instance_eval on proc made from method
This fails on the fact that instance_eval passes a reciever as an additional argument instead of the real reciever (afaik)
class Foo
def initialize
#name = "John"
end
end
class Bar
def out
puts #name
end
end
foo = Foo.new
bar = Bar.new
m = bar.method :out
proc = m.to_proc
foo.instance_eval &proc
It says: in `out': wrong number of arguments (1 for 0) (ArgumentError) in the stacktrace.
However when I use this instead of the last line it works fine:
foo.instance_eval {
puts #name
}
The problem is that #instance_eval sends to the block a parameter that is the object it self. So you can do it:
# ...
class Bar
def out(foo_object)
[#name, foo_object, self]
end
end
# ...
m = bar.method :out
foo.instance_eval &m # => ["John", #<Foo:0x1c11b10>, #<Bar:0x1bb2470>]
The argument is place where the method is called and self is from here the method is. I don't know how to call the method without parsing this extra argument.

Super keyword in Ruby

What is the super for in this code?
def initialize options = {}, &block
#filter = options.delete(:filter) || 1
super
end
As far as I know it's like calling the function recursively, right?
no... super calls the method of the parent class, if it exists. Also, as #EnabrenTane pointed out, it passes all the arguments to the parent class method as well.
super calls a parent method of the same name, with the same arguments. It's very useful to use for inherited classes.
Here's an example:
class Foo
def baz(str)
p 'parent with ' + str
end
end
class Bar < Foo
def baz(str)
super
p 'child with ' + str
end
end
Bar.new.baz('test') # => 'parent with test' \ 'child with test'
There's no limit to how many times you can call super, so it's possible to use it with multiple inherited classes, like this:
class Foo
def gazonk(str)
p 'parent with ' + str
end
end
class Bar < Foo
def gazonk(str)
super
p 'child with ' + str
end
end
class Baz < Bar
def gazonk(str)
super
p 'grandchild with ' + str
end
end
Baz.new.gazonk('test') # => 'parent with test' \ 'child with test' \ 'grandchild with test'
If there's no parent method of the same name, however, Ruby raises an exception:
class Foo; end
class Bar < Foo
def baz(str)
super
p 'child with ' + str
end
end
Bar.new.baz('test') # => NoMethodError: super: no superclass method ‘baz’
The super keyword can be used to call a method of the same name in the superclass of the class making the call.
It passes all the arguments to parent class method.
super is not same as super()
class Foo
def show
puts "Foo#show"
end
end
class Bar < Foo
def show(text)
super
puts text
end
end
Bar.new.show("Hello Ruby")
ArgumentError: wrong number of arguments (1 for 0)
super(without parentheses) within subclass will call parent method with exactly same arguments that were passed to original method (so super inside Bar#show becomes super("Hello Ruby") and causing error because parent method does not takes any argument)
I know this is late but:
super method calls the parent class method.
for example:
class A
def a
# do stuff for A
end
end
class B < A
def a
# do some stuff specific to B
super
# or use super() if you don't want super to pass on any args that method a might have had
# super/super() can also be called first
# it should be noted that some design patterns call for avoiding this construct
# as it creates a tight coupling between the classes. If you control both
# classes, it's not as big a deal, but if the superclass is outside your control
# it could change, w/o you knowing. This is pretty much composition vs inheritance
end
end
If it is not enough then you can study further from here
Bonus:
module Bar
def self.included base
base.extend ClassMethods
end
module ClassMethods
def bar
"bar in Bar"
end
end
end
class Foo
include Bar
class << self
def bar
super
end
end
end
puts Foo.bar # => "bar in Bar"
Super in a method of a class , say test_method, is used to call another method with same name i.e test_method of a parent class.
The code written above and below the super keyword will be executed normally and the whole bunch of code action of the method of super class will be included at the place of super keyword.

Resources