1
class A
  def initialize(var)
    @var = var
  end
end

class B < A
  def initialize
  end

  def test
    puts @var
  end
end

a = A.new('hello')

b = B.new
b.test

This code doesn't work, because b inherits from A but not a.

In Ruby (or Rails), how do I get this kind of behavior? Inheritance is useful for sharing methods, can it also share data?


UPDATE

This question is a bit confusing, so I will try to clarify.

I want instances of B to inherit not only from the class A, but also from an instance of A. That way, I could do something like this:

a1 = A.new('one')
a2 = A.new('two')
b1 = a1.B.new # not ruby syntax, but this is why I'm asking the question
b2 = a2.B.new
b3 = a2.B.new

b3.test
#=> 'two'
b2.test
#=> 'two'
b1.test
#=> 'one'
10
  • I'm trying to achieve an object relationship where I can access a parent's data as well as methods Commented Sep 23, 2019 at 10:52
  • 1
    After your edition: It does not work not because instance variables are somehow "not shared" (whatever it means), but because you overwrote method initialize in A class so now it doesn't set @var instance variable. What are you trying to achieve? What do you mean by trying to achieve 'parent's data'? Instance variables only live on instances, not on classes (well they do, but it's a different case then). Commented Sep 23, 2019 at 10:52
  • What should happen when the variable @var is updated in the context of b? Should a.test change then too? Commented Sep 23, 2019 at 10:53
  • 1
    It's unclear why you want to do this--this isn't how OOP works in Ruby (and most other OOP languages). It sounds like you want a mix of "classical" and prototypal inheritance. While there a variety of hacks it might be better to fit the problem to the language. Commented Sep 23, 2019 at 11:23
  • 1
    @Mirror318 That doesn't really explain why you want to do it like this. So far it sounds like simple composition is more what you want, or a factory off of A. Commented Sep 23, 2019 at 18:07

5 Answers 5

1

One quick way to achieve your goal would be to use the little known SimpleDelegator class:

class A
  attr_reader :var

  def initialize(var)
    @var = var
  end
end

require 'delegate'

class B < SimpleDelegator
  def test
    puts var
  end
end

a1 = A.new('one')
a2 = A.new('two')
b1 = B.new(a1)
b1 = B.new(a1)
b2 = B.new(a2)
b3 = B.new(a2)

b3.test
#=> 'two'
b2.test
#=> 'two'
b1.test
#=> 'one'

This blog article ("5 ways of forwarding your work to some other object in Ruby") might be of interest to you.

Sign up to request clarification or add additional context in comments.

1 Comment

This is great. One problem is that it will only delegate public methods from the parent class (so it can't do e.g. private memoization methods). Do you know if there's a way around this?
1

You might want to consider a class variable then:

class A
  def initialize(var)
    @@var = var
  end
end

class B < A
  def initialize
  end

  def test
    puts @@var
  end
end

a = A.new('hello')

b = B.new
b.test
#=> 'hello'

2 Comments

This is in the right direction, but I'm looking for a variable scoped to instances of A. So if there's a1 = A.new('one') and a2 = A.new('two'), then all children of a1 would see @var == 'one' and all children of a2 would see @var == 'two'
Classes inherit from classes. But a1 and a2 are instances. You cannot inherit a class from an instance.
1

It's not possible using 'inheritance' as you described, because there's no such thing as inheritance from an instance in Ruby world. But, having said that, you can do some hacking to actually get what (I guess) you need, using delegate (you can do it, because, as you wrote, you're using Rails):

class A
  attr_reader :var # here, you set method to get `@var` value
  # ...
end

class B < A
  delegate :var, to: :a # here, you're setting delegator

  attr_reader :a # It's used by line above

  def initialize(a)
    @a = a
  end

  def test
    puts var
  end
end
a1 = A.new('one')
a2 = A.new('two')
b1 = B.new(a1)
b2 = B.new(a2)
b3 = B.new(a2)
b1.test
# one
b2.test
# two
b3.test
# two

Keep in mind though it's a solution written from scratch, as there's no 'syntatic' way to do this, as there's no inheritance from instances in Ruby world.

Comments

0

Your code raises error:

b = B.new
ArgumentError: wrong number of arguments (given 0, expected 1)
from (pry):14:in `initialize'

B inherits the constructor and requires an argument in the intitialization.

See below:

b = B.new("hello")
# => #<B:0x0000555b8b8c20d0 @var="hello">
b.test
# hello
# => nil

As you can see, the ivar is inherited.

EDIT

In this case, you'd better use composition than inheritance:

class A
  def initialize(var)
    @var = var
  end
  attr_accessor :var
end

class B < A
  def initialize(a)
    @a = a
  end

  def test
    puts var
  end

  def var
    a.var
  end

  def var=(var)
    a.var=(var)
  end
end

# or with delegation
class C < A
  def initialize(a)
    @a = a
  end
  delegate :var, :var=, to: :a

  def test
    puts var
  end
end

2 Comments

Right, I just updated the code example. Bugs aside, the question is can I have an object that is a child of an instance of it's parent class, accessing data?
Why do you want to do that? Do you want to all B instances do inherit data from some specific A instance?
0

One highly unrecommended way to achieve this would be to move the inheritance chain into a dynamic module, eg.

class InheritableState < Module
  def initialize(key)
    var_name = "@@#{key}_state".to_sym

    define_method :state do 
      return self.class.class_variable_get(var_name) if self.class.class_variable_defined?(var_name)
      self.class.class_variable_set(var_name, nil)
    end

    define_method :state= do |val|
      self.class.class_variable_set(var_name, val)
    end
  end
end

class A
end

> a, b, c = A.new, A.new, A.new
 => [#<A:0x00007fb691944338>, #<A:0x00007fb691944310>, #<A:0x00007fb6919442e8>]
> a.extend(InheritableState.new("abc")); b.extend(InheritableState.new("abc")); c.extend(InheritableState.new("def"));
 => #<A:0x00007fb6919442e8>
> [a.state, b.state, c.state]
 => [nil, nil, nil]
> a.state = "abc"
 => "abc"
> [a.state, b.state, c.state]
 => ["abc", "abc", nil]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.