Search code examples
rubyinstancecase-sensitive

Ruby Object Case-Sensitive in IRB


So I ran into an interesting dilemma regarding object/receiver cases (upper/lower). Convention states lowercase but something seems to go awry. But, it my case, my code only works when the instance begins with a cap? It's driving me nuts!

I'm am using Start Command Prompt with Ruby. IRB 2.3, and also C9 (problem persisted in the IDE as well)

An example:

#rex.rb
class Dog
    def wants(something)
        puts "Rex wants to #{something}."
    end
end

#rex instance is lowercase, per convention and the book's fidelity
rex = Dog.new
rex.wants("play")

This runs in my machine (via ruby rex.rb) and on irb (via require "rex") as well. It runs. But, then in irb:

rex.wants("x") yields a NameError basically saying rex object is undefined.

To remedy this I have to alter the code:

#rex.rb
class Dog
    def wants(something)
        puts "Rex wants to #{something}."
    end
end

#Rex starts with an uppercase R now, not lowercase r - which it should be
Rex = Dog.new
Rex.wants("play")

I run this similarly on my machine (ruby rex.rb) and on irb (require "rex"). Then, in irb,

Rex.wants("x") 

it runs (=> nil) perfectly.

My question: why do I am having to capitalize (the first letter of) my object/receiver? Convention is that they begin with lowercase.

Maybe the book I'm using (Head First Into Rudy) tackles this later on? IDK...the book hasn't touched IRB. I just wanted to experiment. Hopefully others with benefit...google didn't really help me today ;(.

The code runs...but the instance should be lowercase, shouldn't it? What am I missing here?

Is it possible that my machine is screwed up. The codepiece works on ideone when others test it. But it fails for me on my machine and c9? I'm at a loss....


Solution

  • Your trouble here is that any local variable defined in an included file will be out of scope within IRB. By defining a constant, you were able to get around this. Declaring an explicit global variable $rex or instance variable @rex are alternatives:

    @rex = Dog.new
    @rex.wants("play")
    

    @rex will now be accessible through IRB...

    @rex.wants "x"
    # Rex wants to x.
    # => nil 
    

    @John Hyland has a suggestion for declaring a constant within a module which you might also look into.


    Using An Instance Variable Globally

    @mudasobwa's accusation of heresy for using an instance variable globally is not without merit. Declaring @rex outside of the context of a class and importing the containing file into IRB will add that instance variable to the main object. This violates Object Oriented design principles, may be confusing, and global variables should generally be avoided.

    That said, a globally scoped instance variable may be appropriate if viewed as a "fish out of water" for experimentation purposes. I prefer it to declaring a constant we intend to mutate. Instance variables may also more closely resemble resemble code you'll eventually deploy; often in rails or sinatra applications, instance variables are declared within the scope of a route handler (e.g. #get) that a view can access.


    About instance variables

    Classically, instance variables should pertain to the class they live within. Here's how that looks:

    # dog.rb
    class Dog             
      @@population = 0    # class variable (shared across all Dogs)
    
      def initialize legs=4
         @legs = legs       # instance variable (for each Dog)
         @@population += 1
      end
      def amputate
        @legs-=1 if @legs > 0
        @legs
      end
    
      # setters and getters (we could DRY this up with attr_accessor...)
      def legs= x
        @legs = x
      end
      def legs
        @legs
      end
      def self.count
        @@population
      end
    end
    

    Our class describes a model of what a Dog looks like in our program. Every dog we create will have its own instance variable @legs and share a class variable @@population with all Dogs. Calling Dog.new will invoke #initialize, which sets @legs to 4, by default, and adds 1 to the total population. From irb, we could test this out:

    # irb terminal
    rex = Dog.new.amputate
    fido = Dog.new
    puts "rex has %i legs, fido has %i legs" % [rex.legs, fido.legs]
    # => rex has 3 legs, fido has 4 legs
    puts "We have %i total dog(s)" % Dog.count
    # => We have 2 total dog(s)