0

I'd like to alias an instance variable, so the alias can be used in the subclass (or in the class itself, for that matter). Here is a simplified demonstration of what I'd like to achieve

class A

    attr_reader :bar
    alias :foo :bar

    def initialize
        @bar = 'hello'
    end

end

class B < A

    def try_alias
        puts @foo
    end

end

B.new.try_alias #=> "hello"

But it doesn't work, @foo is nil. Is there a way to achieve it somehow? Or do I have to change/set @foo whenever I change @bar to mimic this behaviour?

EDIT: It seems the way I am trying to do the alias would work if called

foo 

instead of

@foo

in the try_alias method, which is not surprising. Trying to use

alias :@foo :bar 

doesn't work either.

Václav Pruner
  • 310
  • 1
  • 11
  • 3
    `alias :foo :bar` creates an alias for the method `bar`, not for the instance variable `@bar`. Instance variables cannot be aliased. – Stefan Apr 19 '18 at 13:14
  • Please explain a use case where you would need to store 2 identical instance variables because as I understand your question right now `@foo` should be a direct reference to `@bar` e.g. `@foo.equal?(@bar) #=> true`. Currently in your case changing `puts @foo` (instance variable) to `puts foo` (method call) will result in the desired behavior although I would recommend `alias_method` for inheritance over `alias` – engineersmnky Apr 19 '18 at 13:19
  • @engineersmnky it is a matter of backward compatibility. I have a class with instance variable bar which has to be renamed to foo, so everyone who uses this class as a superclass wouldn't have to rename all of its usages. – Václav Pruner Apr 19 '18 at 13:26
  • This does not seem logical to me since you cannot alias an instance variable and this change would break compatibility this should be a major release and the CHANGE LOG should notate the change explicitly so that others can upgrade their code accordingly – engineersmnky Apr 19 '18 at 13:33
  • Additionally this "do I have to change/set @foo whenever I change @bar to mimic this behaviour?" is also not possible as it would require the ability to overwrite assignment `=` which you cannot do – engineersmnky Apr 19 '18 at 13:46
  • @engineersmnky what I meant by that is: having two instance variables with the same value assigned to them (no aliasing, only mimicking it). – Václav Pruner Apr 20 '18 at 06:53

2 Answers2

0

attr_reader :bar creates a "getter" method for @bar, saving you the trouble of doing it yourself:

def bar
  @bar
end

(See Module#attr_reader). To create an alias for the method bar you write

alias :foo :bar

which is effectively the same as creating a second getter for @bar named foo:

def foo
  @bar
end

Therefore puts foo reduces to puts @bar.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • 1
    Yes, I understand this. But it doesn't solve what I wanted to do (which apparently isn't possible at all, according to other answers). I wanted to explicitly use @foo (aliased) and not just foo. – Václav Pruner Apr 20 '18 at 06:58
0

You should explicitly use @foo in the original, aliased setter; which will add just one line to your code.

  • Notice that, as you made A superclass, generally you would want to validate before setting some variables, such as @bar. This the reason why the validation function was added to the code below (just to reflect this idea).

Also, as attr s declarations will be translated to methods, methods are the ones that master the final behaviour and, therefore, in general, it is better to use alias_method over alias (please, see this widely accepted answer).

I would dare say this is the simplest way to go:

module Aside
  class A
    attr_reader :bar
    # as mentioned, preferable to use alias_method
    alias_method :foo, :bar
    alias_method :foo=, :bar=

    def initialize
      # use setter method to keep @bar and @foo in sync
      self.bar = 'hello'
    end
    def bar=(value)
      @bar = nil
      @bar = value if some_validation?(value)
      @foo = @bar
    end

    private
    def some_validation?(value)
      value.is_a?(String)
    end
  end

  class B < A
    def try_alias
      puts @foo
    end
  end
end

Aside::B.new.try_alias #=> "hello"

Please, observe that the initialize of the class A, uses the setter method, rather than the direct assignment (as in your example), to initialize @bar.

This is because after aliasing this way, to keep in sync @bar and @foo, you should avoid direct variable assignment everywhere other than the bar setter itself (aside note: that is good practice anyway, because you centralize all the validation of @foo to one single point).

In other words, when you want to use this approach:

  • do not use direct assignment : @bar = value or @foo = value, and
  • use setter methods instead : self.bar = value or self.foo = value (as shown in the class A initializer).
rellampec
  • 698
  • 6
  • 22