Welcome to the Treehouse Community

The Treehouse Community is a meeting place for developers, designers, and programmers of all backgrounds and skill levels to get support. Collaborate here on code errors or bugs that you need feedback on, or asking for an extra set of eyes on your latest project. Join thousands of Treehouse students and alumni in the community today. (Note: Only Treehouse students can comment or ask questions, but non-students are welcome to browse our conversations.)

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and a supportive community. Start your free trial today.

Ruby

Travis Lindsey
Travis Lindsey
10,224 Points

Accessing variables in other class method

Hello, I finished one of the ruby blocks and started playing around with the bank account app we created. Then i ran into a problem with understanding how to access variables in other class methods.

Here is example of me playing around with 1 file accessing 2 others:

#/////////////////////////////////////////////////////////////
class Name
  attr_accessor :first_name, :last_name

  def new_user
    name
    puts "\nHello #{@first_name} #{@last_name}"
    puts @first_name.class
  end

  def name
    print "Enter your First name: "
    @first_name = "John" 
    print "Enter your Last name: "
    @last_name = "Doe"
  end
end

#/////////////////////////////////////////////////////////////
class BankAccount

  def print_register
    puts Name.new.first_name.class
  end
end


#/////////////////////////////////////////////////////////////
class Interface

  def start
    Name.new.new_user
  end
end

puts "\nWelcome to Bank Account."
Interface.new.start
BankAccount.new.print_register

After entering first and last name in console and print_register is evaluated, I get Nilclass even though it was just defined.

What am i missing about accessing declared variables in methods from other classes?

3 Answers

Travis Lindsey
Travis Lindsey
10,224 Points

I think the problem is with understanding the scope of objects and the lifetime of variables in Ruby.

The lifetime of instance variables are limited to the lifetime of the object they are stored in. I don't store the name as part of the class in the original program.

In the original post, the start method in the Interface class...

class Interface

  def start
    Name.new.new_user
  end
end

instantiates the Name class and the scope of all objects (including my @first_name and @last_name instance variables) are restricted to the instance of Name class. Since that instance isn't stored locally, i lose it when the code exits the Name class and i make this call: BankAccount.new.print_register and goes here

BankAccount.new.print_register

which creates a new instance of Name class with ( puts Name.new.first_name.class ) and see's the default values (nil) for @first_name and @last_name.

As far as I've been able to find in my research, the conventional method to do what is in the original code is to create and store objects in the main class I'm working from and all store all the method functions in different classes based on their category. Which includes initializing the variables in the Name class as Luke points out, and setting the initialization up to accept first and last name as arguments.. Here is a corrected example based on the original post. When i applied the changes to the bigger program i am working with offline, everything worked correctly.

There is likely another way to do this, but I could not find any code online that did not follow a convention similar to what is below.

#/////////////////////////////////////////////////////////////
class Name
  #this class holds user name (first, last, greetings)
  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  def welcome
    puts "\nHello #{@first_name} #{@last_name}"
  end
end

#/////////////////////////////////////////////////////////////
class BankAccount
  #this class holds bank account methods (deposits, withdrawas, etc)
  #if i want to see user name again, call it from Interface class that is holding the most recent instance of user
  def print_register
  end
end

#/////////////////////////////////////////////////////////////
class Interface
  def user
    print "Enter your First name: ";   first_name  = gets.chomp 
    print "Enter your Last name: ";    last_name   = gets.chomp
    @user = Name.new(first_name, last_name)
    # instance of @user is stored in this class until execution of code in this class is done.
    # instance of @bankaccount would also be stored in this class if it were being used
    see_user_again
  end

  def see_user_again
    #We can see @user again because we are still in the original instance of Interface class
    @user.welcome
  end
end

puts "\nWelcome to Bank Account."
Interface.new.user
Luke Buśk
Luke Buśk
21,598 Points

You commented the "require_relative" in all places so You cant call these classes because they are not imported.

Remember that everything after the # sign is just a comment and invisible by Ruby.

Travis Lindsey
Travis Lindsey
10,224 Points

In my actual program, they are not commented out (my whole program is multiple .rb files but the same issue can be simulated easier in a single file with multiple classes). The post was small bits copied from my original program. I formatted it so anyone could copy and paste what I wrote, put it in a file.rb, save it and run it from a command line and see the exact error.

Travis Lindsey
Travis Lindsey
10,224 Points

for clarity. I will remove them from the original post so it doesn't cause confusion.

Luke Buśk
Luke Buśk
21,598 Points

And why would You want to print name of the first_name class? Its not a class so You are getting the "NilClass". If You for example remove first_name from the print_register function and just leave "puts Name.new.class" then You will get "name" as a result.

Travis Lindsey
Travis Lindsey
10,224 Points

I was trying to get it to return string. I updated the main post so you could run it and see the following: Welcome to Bank Account. Enter your First name: Enter your Last name: Hello John Doe String NilClass

So if user entered "John" for example at the "Enter first name" prompt, then @first_name.class would return string if the program was reading the same value stored when the name method from Name class was originally called. But when I leave the 'Name' class and go to 'BankAccount' class 'print_register' method, I don't know why the value is returning nil. (main post example shows this now)

Is the extent/lifetime of a variable in a class limited to the instance it is evaluated when called from another class? For example if i am running file1.rb and in that program i call a method in a class in file2.rb and set a instance variable (@var1) to equal the string "John" in the method, then on the next line of code go to a different method in file1.rb and then return back to file2.rb to read the @var1 and get the string "John" or is it lost as soon as I leave the class in file2.rb even though im not done executing the code in file1.rb?

I know a simpler way to code the problem to work (move all the code into the same class), but my goal isn't to just make the code work. I am trying to get and confirm a deeper understanding of the problem so that I can understand why the above method won't work if it won't, or understand what to change to make it work. That way i can understand how to use multiple classes together

Luke Buśk
Luke Buśk
21,598 Points

Hey, yes now i understand Your purpose.

Im no expert and im still learning but i was having a little fun with Your code and checking things and this is my conclusion:

I think You are getting NilClass returned because You did not specify "initialize" method in the Name class, hence it is unable to create any instances. So what happens? When You call Name.new.new_user You are creating a temporary execution that has no way to store variables for longer, You just type them, print them and they get discarded. Thats why when calling bankAccount class You get a NilClass because first_name is already empty and there is nothing there.

I was tweaking with code a little bit and maybe its not the same as Yours but for now i feel im close, i will paste the code below and let me know what You think. I have removed Bankaccount.new because after adding initialize method (which gets rid of NilClass problem) it will create another instance and repeat the whole process with first and last name which is unnecessary. I will try to play with Class inheritance now and maybe come up with better solution but for now this is what i managed to do:

class Name
  attr_accessor :first_name, :last_name

  def initialize
    print "Give Your name: "
    @first_name = gets.chomp
    print "Give me Your last name: "
    @last_name = gets.chomp
    new_user
  end


  def new_user
    puts "\nHello #{@first_name} #{@last_name}"
  end
end

##########################################

require_relative "./name"

class BankAccount

  def print_register
    puts Name.new.first_name.class
  end
end

##########################################

require_relative "./name"
require_relative "./bankaccount"

class Interface

  def start
    BankAccount.new.print_register
  end
end

puts "\nWelcome to Bank Account."
Interface.new.start
Travis Lindsey
Travis Lindsey
10,224 Points

The BankAccount.new.print_register helps see if the value that was set during the last Interface.new.start was instantiated is still there. Thanks for trying to help though.

I think I have found the answer and will post it shortly. The hardest problem with trying to find answers is not knowing the proper way to ask because I don't really understand what's wrong...i just know something is wrong.

It looks like the problem I was having was from a lack of understanding of the scope and lifetime of variables and how to store values when I don't want to write to a text file or separate database.