Ruby Design Pattern Question - Classes/Modules Inheritance - ruby

Right now I have:
module A
class B
def initialize
#y = 'foo'
end
end
end
module A
class C < B
def initialize
#z = 'buzz'
end
end
end
How can I have it so when I instantiate C #y is still set equal to 'foo'? Do I have to repeat that in the initialize under C? I am a following a bad pattern? Should #y be a class variable or just a constant under the module? Any help would be appreciated!

class A::C < B
def initialize( x, y )
super # With no parens or arguments, this passes along whatever arguments
# were passed to this initialize; your initialize signature must
# therefore match that of the parent class
#z = 'buzz'
end
end
Or, as #EnabrenTane pointed out, you can explicitly pass along whatever arguments you know the super class will be expecting.
For more on inheritance, see the section on Inheritance and Messages in the old-but-free online version of the Pickaxe book.

You need the super keyword. It calls your parents definition of the same method.
I added params just in case. Note, to pass params B#initialize will have to take optional params as well.
module A
class C < B
def initialize(params = nil)
super(params) # calls B#initialize passing params
#z = 'buzz'
end
end
end

Related

Invoking 'initialize' of several included modules in Ruby

If I include a module into a class, which has initialize defined, I can call it using super:
module M
def initialize(x)
#m = x
end
end
class MyClass
def initialize
super(3)
end
def val
#m
end
end
MyClass.new.val
# => 3
But how do I code this, if I have several modules, and maybe also a parent class?
class Parent
def initialize(x)
#p = x
end
end
module M
def initialize(x)
#m = x
end
end
module N
def initialize(x)
#n = x
end
end
class MyClass < Parent
include M
include N
def initialize
# ???? How to initialize here?
end
def val
[#m,#n,#p]
end
end
I guess that super(100) within MyClass::initialize would set the variable #n, because N is the "most recent" ancestor, but how do I call the initialize methods in M and Parent?
Take a look at this blog post (http://stdout.koraktor.de/blog/2010/10/13/ruby-calling-super-constructors-from-multiple-included-modules/). It explains how do you use the initialize from different included modules.

How to create an object in Ruby without using new

It's possible to create a Complex number in Ruby using
c = Complex.new(1,2)
but, it can be shortened to
c = Complex(1,2)
Is it possible to achieve the same functionality without having to define a function outside the class, like in the example below?
class Bits
def initialize(bits)
#bits = bits
end
end
def Bits(list) # I would like to define this function inside the class
Bits.new list
end
b = Bits([0,1])
I think Ruby should allow at least one of the proposed constructors below
class Bits
def initialize(bits)
#bits = bits
end
def self.Bits(list) # version 1
new list
end
def Bits(list) # version 2
new list
end
def Bits.Bits(list) # version 3
new list
end
end
Have this snippet:
def make_light_constructor(klass)
eval("def #{klass}(*args) #{klass}.new(*args) end")
end
Now you can do this:
class Test
make_light_constructor(Test)
def initialize(x,y)
print x + y
end
end
t = Test(5,3)
Yes, I know you're still defining a function outside a class - but it is only one function, and now any class you want can make use of its implementation rather than making one function per class.
c = Complex(1,2)
is actually calling a method on Kernel
Basically you can't - the () operator cannot be overriden in Ruby (Complex class is written in C).
You could achieve something similar using []:
class Bits
def self.[](list)
Bits.new list
end
end
Which would allow something like:
b = Bits[[1,2]]
If you pack your classes into some module you can use 2 methods:
self.included - called when you include Mod
self.extend - called when you extend Mod
I have created very basic method using self.included.
Cons: It is hard to write. You can say it is complex; It may not contain all features.
Pros: It looks exactly like Complex(2,3) (it uses () instead of [] as in https://stackoverflow.com/a/24351316/2597260 answer); You create just initialize, self.included create the rest.
module M1
# some random classes
class A; end
class B
def initialize list
#list = list
end
attr_accessor :list
end
class C
def initialize var1
#var1 = var1
end
attr_accessor :var1
end
Answer = 42
# called on `include module_name`
def self.included mod
# classes are constants (in normal cases)
constants.each do |cons|
class_eval do
# I don't like hard-coded `::M1`
klass = ::M1.const_get cons
if klass.class==Class
define_method cons do |*args, &block|
klass.new *args, &block
end
end
end
end
end
end
include M1
p A()
b = B([1,2,3])
p b.list
c = C 42
p c.var1
puts Answer()
# NoMethodError: undefined method `Answer' for main:Object
# thats good, because Answer is not a class!
Here's another hack that you could (but shouldn't) use, inspired by this blog post:
def method_missing(sym, *args, **kwargs, &blk)
Object.const_get(sym).new(*args, **kwargs, &blk)
end
This simply expects any unknown method name to be the name of a class and calls :new on the class.
With rudimentary error handling:
alias sys_method_missing method_missing
def method_missing(sym, *args, **kwargs, &blk)
cls = Object.const_get(sym) if Object.constants.include? sym
if cls.is_a?(Class) then cls.new(*args, **kwargs, &blk)
else sys_method_missing(sym, *args, **kwargs, &blk) end
end
If an unknown method name is the name of a class, this calls :new on the class. Otherwise, it delegates the call to the original implementation of method_missing().
Usage:
class Foo
end
foo = Foo()
p foo
Result:
#<Foo:0x00007f8fe0877180>

How to call parent module constructors?

I want to call the constructors of two modules I have included in a class, but I don't know how to do it.
module GameObject
attr_reader :x, :y
def initialize(x, y)
#x, #y = x, y
end
end
module Attackable
attr_reader :health, :damage
def initialize(health, damage)
#health, #damage = health, damage
end
end
class SuperMario
include GameObject
include Attackable
def initialize(x, y, health, damage)
.. how to call the above constructors?
end
end
How do I call the constructor of Attackable and GameObject?
As the way you can call the module's methods explicitly in the class initializer as follows:
def initialize(x, y, health, damage)
m = GameObject.instance_method( :initialize )
m.bind(self).call( x, y )
m = Attackable.instance_method( :initialize )
m.bind(self).call( health, damage)
end
I imagine you come from the C++ world, where multiple inheritance is allowed... In short, Ruby only offers single inheritance, and doesn't allow to nitpick the parent method that you're calling. This makes what you're trying to achieve unweildy, to put it mildly. Modules aren't going to offer you any good workarounds -- even after accounting for prepend or self.included and the rest of the metaprogramming toolbox -- except in the specific set of cases where they have no name collisions.
What you could do is rename the initialize method so its logic in a function specific to its module, and call that. Aside: it's usually a code smell to define an initialize method in a module to begin with.
I learned something new today:
module B
def cat(a) puts "in B, a = #{a}" end
end
module C
def cat(a) puts "in C, a = #{a}"; super a+1; end
end
class A
include B
include C
def cat(a) puts "in A, a = #{a}"; super a+1; end
end
A.new.cat(2)
# => in A, a = 2
# => in C, a = 3
# => in B, a = 4
Your question is not restricted to initialize, which is why I illustrated this behavior with a generic method. I had expected the includes would just mix-in the other two cat methods, so the result would be the same as:
class D
def cat(a) puts "in 1st, a = #{a}" end
def cat(a) puts "in 2nd, a = #{a}"; super a+1; end
def cat(a) puts "in 3rd, a = #{a}"; super a+1; end
end
D.new.cat(2)
# => in 3rd, a = 2
# => module.rb:24:in `cat': super: no superclass method `cat' for
# #<D:0x007faadb99acb8> (NoMethodError) from module.rb:27:in `<main>'
I was puzzled by #sawa's comment about "super" and found I was wrong. (I hope not everyone reading this will be thinking, "so what else is new?".)

Lazy loading from subclass in Ruby

If I make #x lazily loaded in the parent class A it can be called and initialized just fine, but if I try to call it from A's subclass B, then it won't call #x's initialization method and returns nil. Why is that?
class A
def x
#x ||= 'x'
end
end
puts A.new.x # 'x'
class B < A
def use_x
puts #x.inspect # nil
end
end
Use x instead of directly accessing the instance variable #a.
class B < A
def use_x
puts x.inspect
end
end
Because the method x is not called within use_x. Whether it is A or B is irrelevant. puts B.new.x would give the same result as it would with puts A.new.x.

How to pass a method to instance_eval?

I want to call instance_eval on this class:
class A
attr_reader :att
end
passing this method b:
class B
def b(*args)
att
end
end
but this is happening:
a = A.new
bb = B.new
a.instance_eval(&bb.method(:b)) # NameError: undefined local variable or method `att' for #<B:0x007fb39ad0d568>
When b is a block it works, but b as a method isn't working. How can I make it work?
It's not clear exactly what you goal is. You can easily share methods between classes by defining them in a module and including the module in each class
module ABCommon
def a
'a'
end
end
class A
include ABCommon
end
Anything = Hash
class B < Anything
include ABCommon
def b(*args)
a
end
def run
puts b
end
end
This answer does not use a real method as asked, but I didn't need to return a Proc or change A. This is a DSL, def_b should have a meaningful name to the domain, like configure, and it is more likely to be defined in a module or base class.
class B
class << self
def def_b(&block)
(#b_blocks ||= []) << block
end
def run
return if #b_blocks.nil?
a = A.new
#b_blocks.each { |block| a.instance_eval(&block) }
end
end
def_b do
a
end
end
And it accepts multiple definitions. It could be made accept only a single definition like this:
class B
class << self
def def_b(&block)
raise "b defined twice!" unless #b_block.nil?
#b_block = block
end
def run
A.new.instance_eval(&#b_block) unless #b_block.nil?
end
end
def_b do
a
end
end

Resources