Forum: Ruby on Rails Accessing private member by subclassing

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
31fdda5f38fc6df4e193cfb7445ddc5a?d=identicon&s=25 Richard (Guest)
on 2007-02-12 00:29
(Received via mailing list)
Hi All,

SCENARIO: I took an example from Ruby Cookbook which had a private
method 'secret'.
'secret' returned the value of @secret initialized to a random value.

I created a subclass whose initializer called super and accessed
secret to save its returned-value in a instance variable, which I then
displayed.  It matched the value I dumped when the parent class was
initialized.  Success!

However, I tried a alternative approach, which failed --- which is my
reason for posting this.

I created a public method 'spy' in the parent class and returned the
value @secret,  just like the private method 'secret' did.  I then
invoked spy from a subclass instance, but it returned nil.

QUESTION:  Why does subclass.spy return nil?

Code follows.

Thanks in Advance,
Richard

=========== SecretNumber.rb ====================
# Source: Ruby Cookbook, p. 371-373

class SecretNum
  def initialize
    @secret=rand(20)
    puts "@secret <= #{@secret}"
  end

  def hint
    puts "The number is #{"not " if secret<10}greater than 10"
  end

  private
  def secret
    @secret
  end

  public
  def spy
    @secret
  end
  attr_reader :spy
end

=========== SecretNumber_AccessInSubclass.rb ===================
# SecretNumber_AccessInSubclass.rb

require 'SecretNumber.rb'

class NotSoSecretNum < SecretNum
  def initialize
    super
    @theNum = secret
  end

  attr_reader :theNum

end

s = NotSoSecretNum.new
puts "theNum reveals \"secret\" = " + s.theNum.to_s
puts "secret reveals \"secret\" = " + "\"s.secret\" not accessible"
puts "spy reveals \"secret\" = " + s.spy.inspect.to_s

========= Results ==============
@secret <= 3
theNum reveals "secret" = 3
secret reveals "secret" = "s.secret" not accessible
spy reveals "secret" = nil
D0cd6b10e01bacb976b3b815a9c660bc?d=identicon&s=25 Alex Wayne (squeegy)
on 2007-02-12 01:14
Richard wrote:
>   def spy
>     @secret
>   end
>   attr_reader :spy
>
> puts "spy reveals \"secret\" = " + s.spy.inspect.to_s
> #=> nil

The problem looks like its with attr_reader.  attr_reader creates that
accesses an instance variable.  The instance variable and the method
have the same name.  So basically, what you are doing is this:

  #the method you wrote
  def spy
    @secret
  end

  #what attr_reader does for you
  def spy
    @spy
  end

Now you have 2 methods named "spy".  When the second method called "spy"
is declared, the first one is overriden, it's gone.  And since @spy has
not been set to anything it just returns nil.

So get rid of:

  attr_reader :spy

and it should work fine.
31fdda5f38fc6df4e193cfb7445ddc5a?d=identicon&s=25 Richard (Guest)
on 2007-02-12 14:35
(Received via mailing list)
Hi Alex,

Fantastic reply!

You not only told me what action to take to get my intended result,
but more importantly WHY my code failed.  I never really gave much
thought to what attr_reader REALLY meant.

Thank you very much.

Best wishes,
Richard

On Feb 11, 7:14 pm, Alex Wayne <rails-mailing-l...@andreas-s.net>
31fdda5f38fc6df4e193cfb7445ddc5a?d=identicon&s=25 Richard (Guest)
on 2007-02-12 15:06
(Received via mailing list)
Hi again Alex,

After thinking further about my mistaken use of attr_reader, I
wondered why I had never learned the details about it before.  So I
perused Hal Fulton's "The Ruby Way, Second Edition" and found, on page
391, precisely the explanation you gave me.  Clearly,  I have not done
my homework :-)

I mention this primarily for the benefit of anybody else who might
read this thread.

Best wishes,
Richard

On Feb 11, 7:14 pm, Alex Wayne <rails-mailing-l...@andreas-s.net>
31fdda5f38fc6df4e193cfb7445ddc5a?d=identicon&s=25 Richard (Guest)
on 2007-02-12 16:00
(Received via mailing list)
Hi Alex,

Just one more question, if I may:

Hal Fulton say "attr_reader theNum" creates theNum [an instance
method, right?] and @theNum [an instance variable, right?].

However,  the initialize method creates and initializes @theNum.

So, which of the following occurs?

(1) the initialize method creates/initializes @theNum first and
attr_reader acknowledges @theNum's existence and restricts its
creation to theNum method; or

(2) the initialize method gets scanned but not executed, so
attr_reader does both creations and then the initialize method
initializes the existing @theNum instance variable.

The closest thing I've found on this issue is "compile" in Pickaxe 2nd
Edition, which applied to Regexp's, not compiling/interpreting Ruby
source code.

Thanks in advance for any documentation you can point me to or any
ideas you have on this question.

Yours truly,
Richard




On Feb 11, 7:14 pm, Alex Wayne <rails-mailing-l...@andreas-s.net>
D0cd6b10e01bacb976b3b815a9c660bc?d=identicon&s=25 Alex Wayne (squeegy)
on 2007-02-12 18:32
Richard wrote:
> Hi Alex,
>
> Just one more question, if I may:
>
> Hal Fulton say "attr_reader theNum" creates theNum [an instance
> method, right?] and @theNum [an instance variable, right?].

I assume you mean:

  attr_reader :theNum

> However,  the initialize method creates and initializes @theNum.
>
> So, which of the following occurs?
>
> (1) the initialize method creates/initializes @theNum first and
> attr_reader acknowledges @theNum's existence and restricts its
> creation to theNum method; or

attr_reader reads only, not creates.

> (2) the initialize method gets scanned but not executed, so
> attr_reader does both creations and then the initialize method
> initializes the existing @theNum instance variable.

You're getting hung up on who creates what and has access to who and
it's much simpler than that.  Take this class:

  class Foo
    attr_reader :bar

    def initialize(value)
      @bar = value
    end
  end

  foo = Foo.new('abc')
  foo.bar              #=> "abc"

When you call "Foo.new('abc')" you are invoking the initialize method,
this sets the @bar variable.  When you call "bar" you just retrieve
whatever the valid of @bar is at that moment.

And attr_reader gives you an instance method, meaning it's only on
instances of class Foo created with: Foo.new('abc').  And in this
example you cannot instantiate the Foo class without running the
initialize method that sets up @bar.

So the "bar" method doesn't acknowledge or restrict anything.  It simply
fetches whats in @bar whether it's there or not.  As a result it doesn't
care what else is going in your class at all.  you can have a class like
this and it will work fine, except always return nil.

  class Bar
    attr_reader :foo
  end

  bar = Bar.new
  bar.foo        #=> nil

The final thing to keep in mind is that instance variable like @bar are
the only kind of variable that will never give you undefined or
uninitialized errors.  So if it's not initialized it will just give you
nil, unlike class of or local variables that will raise an exception:


irb(main):001:0> foo
NameError: undefined local variable or method `foo' for main:Object
        from (irb):1
        from :0
irb(main):002:0> @foo
=> nil
irb(main):003:0> @@foo
NameError: uninitialized class variable @@foo in Object
        from (irb):3
        from :0

One last example:

  class Bam
    attr_reader :foo

    def set_foo
      @foo = 'abc123'
    end
  end

  bam = Bam.new
  bam.foo        #=> nil
  bam.set_foo
  bam.foo        #=> "abc123"

I think all that answers your question.
31fdda5f38fc6df4e193cfb7445ddc5a?d=identicon&s=25 Richard (Guest)
on 2007-02-13 01:47
(Received via mailing list)
Hi Alex,

I responded earlier to your last post,  but my response got lost in
cyber-space.  Here is a new response, which hopefully is better
written than the lost one.

> I assume you mean:
>
>    attr_reader :theNum

Yes,  I omitted the colon :-(   You've got eagle-eyes :-)

> I think all that answers your question.

It sure does.  Thanks a lot.  I really like to nail down these kinds
of details.

I put together my list of examples based on the your ideas,  which I
provided below.  They satisfy me that:

1) "attr_reader :foo" is equivalent to "def foo; @foo; end" (as you
said originally)

I think examples 2a and b (and 4, 5 and 6, as well) demonstrate that.

2) "@foo" yields nil when it's undefined.  Occam's razor suggests that
a method returning @foo's value:
a)  returns nil when @foo is undefined (as you also said);  rather
than
b) creates @foo with an initial nil value when @foo is undefined, and
then returns @foo's nil value.

In short,  I signed onto your ideas, and now proclaim them as mine,
too :-).

Best wishes,
Richard

===== My humble examples ====
class FooBar1
end

class FooBar2a
  attr_reader :foo
end

class FooBar2b
  def foo
    @foo
  end
end

class FooBar3
  attr_reader :foo

  def set_foo(parm)
    @foo = parm
  end
end

class FooBar4
  def set_foo(parm)
    @foo = parm
  end

  attr_reader :foo
end

class FooBar5
  def set_foo(parm)
    @foo = parm
  end

  def foo
    @foo
  end
end

class FooBar6
  def set_foo(parm)
    @foo = parm
  end

  def foo; @foo; end
end

fb1 = FooBar1.new
# puts fb1.foo  # ==> undefined method `foo' for #<FooBar1: ...

fb2a = FooBar2a.new
puts "fb2a.foo = " + fb2a.foo.inspect  # ==> nil

fb2b = FooBar2b.new
puts "fb2b.foo = " + fb2b.foo.inspect  # ==> nil

fb3 = FooBar3.new
puts "fb3.foo = " + fb3.foo.inspect  # ==> nil
fb3.set_foo "some-value"
puts "fb3.foo = " +  fb3.foo  # ==> some-value

fb4 = FooBar4.new
fb4.set_foo "another-value"
puts "fb4.foo = " + fb4.foo  # ==> another-value

fb5 = FooBar5.new
fb5.set_foo "a third value"
puts "fb5.foo = "  + fb5.foo  # ==> a third value

fb6 = FooBar6.new
fb6.set_foo "a fourth value"
puts "fb6.foo = "  + fb6.foo  # ==> a fourth value


On Feb 12, 12:32 pm, Alex Wayne <rails-mailing-l...@andreas-s.net>
D0cd6b10e01bacb976b3b815a9c660bc?d=identicon&s=25 Alex Wayne (squeegy)
on 2007-02-13 02:20
Richard wrote:
> In short,  I signed onto your ideas, and now proclaim them as mine,
> too :-).

As long as we are being thorough, one last example that proves nil and
undefined are not the same thing when it comes to instance variables.

  class Foo
    attr_reader :bar

    def set_bar(value)
      @bar = value
    end

    def bar_exists?
      defined? @bar
    end
  end

  foo = Foo.new

  foo.bar            #=> nil
  foo.bar_exists?    #=> nil
  foo.set_bar("abc")

  foo.bar            #=> "abc"
  foo.bar_exists?    #=> "instance-variable"

  foo.set_bar(nil)
  foo.bar            #=> nil
  foo.bar_exists?    #=> "instance-variable"


This way you can tell if variable has been not been initialized, or if
has been initialized and was specifically set to nil.
31fdda5f38fc6df4e193cfb7445ddc5a?d=identicon&s=25 Richard (Guest)
on 2007-02-17 16:30
(Received via mailing list)
Hi Alex,

I got distracted by our snow-storm for a few days.

> This way you can tell if variable has been not been initialized, or if
has been initialized and was specifically set to nil.

That's excellent!!

Thank you very much for educating me in Ruby.

Yours very truly,
Richard

On Feb 12, 8:20 pm, Alex Wayne <rails-mailing-l...@andreas-s.net>
This topic is locked and can not be replied to.