Ruby variable not accessible in classes - ruby

I'm trying to pass a variable to numerous classes but get an 'undefined local variable or method' error.
I've created a 'Player' class which has a method to initialise an object with two parameters, and have a 'Board' class where I want to use one of these parameters (I've just included a 'puts' statement for simplicity below) but this is where the error occurs. Outside of the 'Board' class, the same statement works (currently commented out).
How can I use the player1.name value inside of the 'Board' class please? Thanks
class Player
attr_accessor :name, :symbol
def initialize(name, symbol)
#name = name
#symbol = symbol
end
end
class Board
puts player1.name
end
player1 = Player.new("Player1","X")
#puts player1.name

When you open a class with the class keyword you open a new scope and the current bindings are thrown out of the window. Furthermore your code would never work since you reference player1 before defining the variable.
To solve these two issues we have to move the player1 definition above the class. Then instead of using the class keyword to define Board class we can use the Class constructor which keeps the current bindings.
This results in:
player1 = Player.new("Player1","X")
Board = Class.new do
puts player1.name
end
Although this might solve your problem, I doubt that this is the functionality you'll actually need. The question reeks of a XY-problem. The above will bind the class as a whole to what the player1 value is at the moment of class definition.
In turn this means that every instance of Board is in some way bound to player1. I assume that every instance of Board can have its own player(s), in which case the following would be a better fit:
class Board
def initialize(player1)
#player1 = player1
end
end

It seems that you're trying to call the instance of player1 inside the Board class before it is created.
This means player1 does not exist in that context.
I guess you could either instantiate it inside the Board class or pass it as an option. Here's an example:
class Board
def initialize(options={})
# Uses the player passed to it.
#player = options[:player]
end
# Not often seen since you can get it from the player class
# but can be done here inside the Board class too if you want to.
def player_name
puts #player.name
end
end
# Instantiate your player as usual.
player1 = Player.new("Player1","X")
# Adding it to an options hash
# this step is optional, you can add it directly to board
# like you did for adding the name and symbol to the player class.
options = {player: player1}
# Instantiate a board with the player you've created.
board = Board.new(options)
# To puts the player name run the method.
board.player_name # "Player1"
See Ruby Classes and Modules if you would like more examples and learn more.

Related

Ruby - How to access instance variables from classes with "self" methods?

Sorry that I have no clue how to title this, I'm having a hard time looking this up because I don't know how to say this. Anyway...
Let's say I have a class that looks like this for example:
class Run
def self.starting
print "starting..."
end
def self.finished
print "Finished!"
end
end
All of the methods in Run have self before them, meaning that I don't have to do run = Run.new and I can just do Run.starting. Now let's say that I wanted to add some instance variables...
class Run
attr_accessor :starting, :finished
def self.starting
print "starting..."
#starting = true
#finished = false
end
def self.finished
print "finished!"
#starting = false
#finished = true
end
end
What if I wanted to access those instance variables from outside the class? I know that something like print "#{Run.finished}" or print "#{Run.starting}" won't do anything. Can I do that without run = Run.new? Or should I just remove self and then use run = Run.new? (Sorry if this question is a mess.)
All of the methods in Run have self before them, meaning that I don't have to do run = Run.new and I can just do Run.starting
There's much more to it than this. In your case you're calling class methods. If you did runner = Runner.new - then you'd be calling instance methods (those are defined without self.
In general, if you need "the thing" to hold some kind of state (like #running = true) then you'd rather want to instantiate an object, and call those methods.
Now, #whatever are instance variables, and you don't have the access to them in class methods.
class Run
attr_reader :running
def start
#running = true
end
def stop
#running = false
end
end
runner = Run.new
runner.running # nil
runner.start
runner.running # true
runner.stop
runner.running # false
I'd recommend you doing some tutorial or basic level book on rails programming, find a chapter about objects and classes. Do some exercises.
In Ruby instance variables are just lexical variables scoped to an instance of a class. Since they are scoped to the instance they always act like a private variable.
If you want to provide access to an instance variable from the outside you create setter and getter methods. Thats what attr_accessor does.
class Person
attr_accessor :name
def initialize(name:)
#name = name
end
def hello
"Hello my name is #{#name}"
end
end
john = Person.new(name: 'John')
john.name = "John Smith"
puts john.hello # "Hello my name is John Smith"
puts john.name # "John Smith"
Methods defined with def self.foo are class methods which are also referred to as singleton methods. You can't access variables belonging to an instance from inside a class method since the recipient when calling the method is the class itself and not an instance of the class.
Ruby also has class variables which are shared by a class and its subclasses:
class Person
##count = 0
def initialize
self.class.count += 1
end
def self.count
##count
end
def self.count=(value)
##count = value
end
end
class Student < Person
end
Person.new
Student.new
puts Person.count # 2 - wtf!
And class instance variables that are not shared with subclasses:
class Person
#count = 0 # sets an instance variable in the eigenclass
def initialize
self.class.count += 1
end
def self.count
#count
end
def self.count=(value)
#count = value
end
end
class Student < Person
#count = 0 # sets its own class instance variable
end
Person.new
Student.new
puts Person.count # 1
Class variables are not used as often and usually hold references to things like database connections or configuration which is shared by all instances of a class.
You can't access instance variables from outside the instance. That is the whole point of instance variables.
The only thing you can access from outside the instance are (public) methods.
However, you can create a public method that returns the instance variable. Such a method is called an attribute reader in Ruby, other languages may call it a getter. In Ruby, an attribute reader is typically named the same as the instance variable, but in your case that is not possible since there are already methods with the names starting and finished. Therefore, we have to find some other names for the attribute readers:
class Run
def self.starting?
#starting
end
def self.finished?
#finished
end
end
Since this is a common operation, there are helper methods which generate those methods for you, for example Module#attr_reader. However, they also assume that the name of the attribute reader method is the same as the name of the instance variable, so if you were to use this helper method, it would overwrite the methods you have already written!
class << Run
attr_reader :starting, :finished
end
When you do this, you will get warnings (you always have warning turned on when developing, do you?) telling you that you have overwritten your existing methods:
run.rb:19: warning: method redefined; discarding old starting
run.rb:2: warning: previous definition of starting was here
run.rb:19: warning: method redefined; discarding old finished
run.rb:5: warning: previous definition of finished was here

How to access variable from outside class in another file

My file structure is as follows:
Main/
Games/
roulette.rb
casino.rb
wallet.rb
player.rb
I have a wallet class that holds a money value in the class like so.
class Wallet
attr_accessor :money
def initialize
#money = 0
end
end
I then have a player class that inherits from the Wallet class
class Player < Wallet
attr_accessor :name
def initialize
super()
#name = nil
get_user_info
end
I then have a Casino class that inherits from Player like so
class Casino < Player
def initialize
binding.pry
puts #money, #name
end
I have also used require_relative to pull in both files thinking that would give me access to their global variables #money, #name.
If I am inside the roulette.rb file here is my code I wrote just to see if it would have a value.
require_relative '../wallet.rb'
class Roulette
def initialize
puts #wallet
end
end
How would I go about getting access to these variables in the casino class? Thanks for the help.
Those are not global variables. They are called "instance variables" and to access them you need to create instances of your casinos and players. Looks like this.
player = Player.new
player.money # => 0
player.money += 10
player.money # => 10
In your Casino class you don't call parent initializers (a simple oversight, I think), so it doesn't initialize #name and #money.
And Roulette doesn't do anything at all to obtain a wallet. So it stays at default value nil.

Test to check if attribute is assigned fails

I'm trying out some of the exercises over on exercism. Each exercise comes with a set of pre-written tests that we need to make pass. The problem I'm currently working on asks us to write a Robot class. Each robot should come with a method called name that sets a default name. I'm doing that like this:
class Robot
attr_accessor :name
def self.name
#name = DateTime.now.strftime("%y%^b%k%M%S")
#name
end
end
The problem is that the first test (I'm skipping over the rest for now) keeps failing. Here's the test:
def test_has_name
# rubocop:disable Lint/AmbiguousRegexpLiteral
assert_match /^[A-Z]{2}\d{3}$/, Robot.new.name
# rubocop:enable Lint/AmbiguousRegexpLiteral
end
I'm not using the rubocop gem so I've left the commented lines as is. The test fails with this error:
1) Failure:
RobotTest#test_has_name [robot-name/robot_name_test.rb:7]:
Expected /^[A-Z]{2}\d{3}$/ to match nil.
I suppose the biggest problem is that I don't really understand the error and I don't know if I need to install rubocop and uncomment those lines above or of my code is just plain wrong. Any help at all with this would be much appreciated.
There is number of issues with your code.
First, you define accessor :name, but you don't have initialize method.
What you have defined, is a class method name, which would work if you call Robot.name.
To make your class work, it should look like this:
class Robot
def initialize
#name = DateTime.now.strftime("%y%^b%k%M%S")
end
end
Robot.new.name
#=> "15MAY150035"
Or You would do
class Robot
def name
DateTime.now.strftime("%y%^b%k%M%S")
end
end
Robot.new.name
#=> "15MAY150649"
Also, in Ruby last line in method is already what would be returned, so you don't have to write #name here:
def self.name
#name = DateTime.now.strftime("%y%^b%k%M%S")
#name # useless
end
Furthermore, variable #name is useless here, since method name either way will return DateTime object.
You have to make sure you understand what is what in Ruby.
Your code defines name to be a method on the Robot class (that's what the self indicates; and #name here will be setting a member variable on the class object). You need to provide a way for each instance to have a name.
As a secondary concern, your method changes the name everytime name is called. You probably want to set it once (when the robot is initialized, probably!), then return that each time.
Your method is a class method. Which means that Robot.name will give you the name, while Robot.new.name is nil.
You want to use:
def name
#code
end
Instead of self.name.
You can also set name in the initialize method:
def initialize
#name = 'RB123'
end

Does defining initialize for a singleton class make sense in Ruby?

I anticipate that I am not trying to do anything practical here, just trying to understand some deeper Ruby concepts.
Supppose I have the following code
class Bookshelf
#book_qty = 100 # class instance var
class << self
attr_accessor :books_qty
end
def initialize
#book = "This book is in every object as an object instance variable"
end
# so far so good, but what happens with...
def self.initialize # what would be this called on ?
puts " and at what step would this be printed, if ever?"
# I thought it would be printed when the class code is parsed first time,
# but no
end
# or also
class << self
def initialize
puts "same here"
end
end
end
I know it might not make sense or might be too intricately related on how Ruby internals work, but, if by chance anyone has been puzzled too by this and knows the answer... please share it :)
There is no purpose to defining initialize for the singleton class (whether you use def self. or class << self). initialize is only called by Class#new and...
Bookshelf.singleton_class.new
# TypeError: can't create instance of singleton class
that's not allowed.
If you want code to be executed the first time a class is parsed, just put it in the class
class Bookshelf
puts "class defined!"
end
In some cases, it does make sense to define a custom constructor, but I wouldn't call the method initialize, since the instance method you override to customise initialisation is also called initialize. That would be a little confusing.
def self.initialize # what would be this called on ?
If you define a method like this, you can invoke it by sending the method directly to the class:
Bookshelf.initialize
Same thing applies for methods defined inside class << self.
As mentioned, it does make sense to define custom constructors for a class. Sometimes just for readability's sake:
class Bookshelf
attr_accessor :book_qty
def self.with_quantity(quantity)
new(quantity)
end
def initialize(quantity)
self.book_qty = quantity
end
end
Now you could instantiate a Bookshelf like this:
bs = Bookshelf.with_quantity 100
bs.quantity # => 100
You actually call .new on the singleton class of Bookshelf. And #initialize is basically just a hook for the instance to tweak its initialisation.
How you could access class instance variables
class Bookshelf
#book_qty = 1000
class << self
attr_accessor :book_qty
end
end
Bookshelf.book_qty # => 1000
Bookshelf.book_qty = 2000
Bookshelf.book_qty # => 2000

using attributes of different objects in different objects methods

what i need is basically to use variable from one file, in the method. let me explain
lets say we have
class Game
attr_accessor :number, :object
end
where number is just some number and object is object of some other class defined by me, lets name it Player class. now we make another file, which requires class Game, and which goes like this:
require './Game.rb'
require './Player.rb'
myGame = Game.new
myGame.number = 1
myGame.object = Player.new
and now the big moment. in method defined in Player class, i would like to use myGame.number attribute. eg like this
class Player
attr_accessor :some_var
def method
#some_var = myGame.number
end
end
How can i achieve this?
Your player should have a reference to the game is playing. For instance
class Game
attr_accessor :number
attr_reader :my_player
def my_player=(player)
player.my_game = self
#my_player = player
end
end
class Player
attr_accessor :some_var, :my_game
def method
#some_var = #my_game.number if #my_game
end
end
myGame = Game.new
myGame.number = 1
myGame.my_player = Player.new()
myGame.my_player.method
puts myGame.my_player.some_var
Alternatively to toch's answer, you can keep a reference to the game object when you set the player accessor. Instead of using automatic accessors you can use the get_ set_ accessor syntax to have custom code in the accessor, which would set the reference on the rvalue.

Resources