I'm trying to access a class variable from a method outside of the class.
This is my class:
class Book
##bookCount = 0
##allBooks = []
def self.allBooks
##allBooks
end
def self.bookCount
##bookCount
end
attr_accessor :name,:author,:date,:genre,:rating
def initialize(name, author, date, genre, rating)
#name = name
#author = author
#date = date
#genre = genre
#rating = rating
##bookCount += 1
##allBooks << self
end
end
This is the method trying to access the class variable ##bookCount
def seeBookShelf
if ##bookCount == 0
puts "Your bookshelf is empty."
else
puts "You have " + #bookCount + " books in your bookshelf:"
puts allBooks
end
end
When I try to execute the method I get this:
undefined local variable or method `bookCount' for main:Object (NameError)
How can I access bookCount from the outside?
Use class_variable_get to access a class variable outside of a class:
class Foo
##a = 1
end
Foo.class_variable_get(:##a)
=> 1
For most cases, class instance variables are preferred to class variables. The latter are prone to all manner of strange behaviour when used with inheritance.
Consider:
class Book
#book_count = 0
#all_books = []
class << self
attr_reader :book_count
attr_reader :all_books
end
# further code omitted.
end
With this code Book.book_count and Book.all_books get the expected data.
You can use class_eval to evaluate a block of code within the scope of a specific class:
class Book
##bookCount = 1
end
Book.class_eval '##bookCount'
# => 1
And just for fun... you can actually do all kinds of trickery with class_eval such as define a new method in the class without monkey patching:
Book.class_eval { ##bookCount = 5 }
Book.class_eval '##bookCount'
# => 5
Book.class_eval do
def self.hey_look_a_new_method
return "wow"
end
end
Book.hey_look_a_new_method
# => "wow"
You need a getter to access the class variable, try this code.
See http://www.railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/ for an explanation.
You are also better to use string interpolation otherwise you get a Type error, also it is more Rubyesque.
class Book
##bookCount = 0
def self.bookCount
##bookCount
end
end
def seeBookShelf
if Book.bookCount == 0
puts "Your bookshelf is empty."
else
puts "You have #{Book.bookCount} books in your bookshelf:"
end
end
seeBookShelf # Your bookshelf is empty.
You have to specify the Class of the variable :
def seeBookShelf
if Book.bookCount == 0
puts "Your bookshelf is empty."
else
puts "You have " + Book.bookCount + " books in your bookshelf:"
puts Book.allBooks
end
end
Related
Working on a Ruby program I was looking to move some state data from instance variables to class variables, it dawned on me that while instance variables are auto-vivified (if you try to read them "without initializing" them, they are automatically initialized to nil), class variables are not - and this looks very inconsistent to me (compared to most Ruby syntax which is very consistent).
Sample program:
class Test
def id
#id.to_i
end
def id=(i)
#id = i
end
def nextid
self.id = id + 1
end
end
t = Test.new
puts t.nextid #=> 1
puts t.nextid #=> 2
In which case, when calling Test::id, if #id was not initialized, Ruby will auto-vivify it to nil (after which I to_i it to get 0).
Now I decide that I want the running ID to be shared across Test instance, so I rewrite it like this:
class Test
def id
##id.to_i
end
def id=(i)
##id = i
end
def nextid
self.id = id + 1
end
end
t = Test.new
puts t.nextid
puts t.nextid
Should work the same, I thought, but no:
NameError: uninitialized class variable ##id in Test
But this workaround works (!?) :
class Test
def id
(##id ||= 0).to_i
end
def id=(i)
##id = i
end
def nextid
self.id = id + 1
end
end
t = Test.new
puts t.nextid #=> 1
puts t.nextid #=> 2
(granted, after doing lazy init to 0 I can drop the to_i, but I left it for consistency).
It looks like Ruby understands "lazy initialization" and treats it as the magic needed to not throw NameError - even though ||= is supposedly just syntactic sugar to x = x || val (which BTW doesn't work for initing class variables, thanks for asking).
How come?
Class variables initialization
Here's a possible explanation why #a is nil but ##a is a NameError.
But if you want to use class variables, you should initialize them inside the class, not inside instance methods :
class Test
##id = 0
def id
##id
end
def id=(i)
##id = i
end
def nextid
self.id = id + 1
end
end
t = Test.new
puts t.nextid
puts t.nextid
Please note that it doesn't make much sense to have an instance setter method for a class variable.
Class instance variables
To avoid mixing instance methods and class variables, you could define everything at the class level with a "class instance variable". It's an instance variable defined at the class level:
class Test
#id = 0
class << self
def id
#id
end
def id=(i)
#id = i
end
def nextid
self.id = id + 1
end
end
end
puts Test.id
# 0
puts Test.nextid
# 1
puts Test.nextid
# 2
It means you could just use attr_accesor:
class Test
#id = 0
class << self
attr_accessor :id
def nextid
self.id = id + 1
end
end
end
class Player
attr_accessor :card_pile
def initialize
#bust = false
#card_pile = []
end
def bust?
return #cards.inject(:+) > 21
end
end
I have this Player class and have initazlied card_pile variable
class Game
def initialize
#players = []
end
def playing_game
puts "How many players are playing? "
players_amount = gets.chomp.to_i
(0...players_amount).each do
puts ("What is the players name? ")
name = gets.chomp
#players.push(name)
end
puts #players
player = Player.new
player.initialize
while #card_pile.length < 2 do
new_card = Card.new
#card_pile.push(new_card.value)
end
end
I wish to use this variable in the while loop below. Why cannot this be accessed in the way I am hoping it will be?
The error message is: ``playing_game': private method initialize' called for #<Player:0x007fda53073f48 #bust=false, #card_pile=[]> (NoMethodError)
initialize is called automatically when you make a new instance of a class using Player.new. You don't currently have any arguments being passed in to your initialize method, but you have set the instance variable card_pile with attr_accessor, so you can do this:
player = Player.new
while player.card_pile.length < 2 do
new_card = Card.new
player.card_pile.push(new_card.value)
end
I am writing the Ruby program found below
class Animal
attr_reader :name, :age
def name=(value)
if value == ""
raise "Name can't be blank!"
end
#name = value
end
def age=(value)
if value < 0
raise "An age of #{value} isn't valid!"
end
#age = value
end
def talk
puts "#{#name} says Bark!"
end
def move(destination)
puts "#{#name} runs to the #{destination}."
end
def report_age
puts "#{#name} is #{#age} years old."
end
end
class Dog < Animal
end
class Bird < Animal
end
class Cat < Animal
end
whiskers = Cat.new("Whiskers")
fido = Dog.new("Fido")
polly = Bird.new("Polly")
polly.age = 2
polly.report_age
fido.move("yard")
whiskers.talk
But when I run it, it gives this error:
C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `initialize': wrong number of arguments (1 for 0) (ArgumentError)
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `new'
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `<main>'
My investigations shows that I should create objects like this
whiskers = Cat.new("Whiskers")
Then there should be an initialize method in my code which will initialize the instance variable with the value "Whiskers".
But if I do so then what is the purpose of attribute accessors that I am using? Or is it like that we can use only one and if I have to use attribute accessors then I should avoid initializing the instance variables during object creation.
initialize is the constructor of your class and it runs when objects are created.
Attribute accessors are used to read or modify attributes of existing objects.
Parameterizing the constructor(s) gives you the advantage of having a short and neat way to give values to your object's properties.
whiskers = Cat.new("Whiskers")
looks better and it's easier to write than
whiskers = Cat.new
whiskers.name = "Whiskers"
The code for initialize in this case should look like
class Animal
...
def initialize(a_name)
name = a_name
end
...
end
All attr_reader :foo does is define the method def foo; #foo; end. Likewise, attr_writer :foo does so for def foo=(val); #foo = val; end. They do not do assume anything about how you want to structure your initialize method, and you would have to add something like
def initialize(foo)
#foo = foo
end
Though, if you want to reduce boilerplate code for attributes, you can use something like Struct or Virtus.
You should define a method right below your class name, something like
def initialize name, age
#name = name
#age = age
end
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
#name = name,
#book_id = book_id
end
end
class BookCollection
def intialize
#book_names = []
end
def add_to_books(book_name)
book_name.push(book_names)
end
end
book1 = Books.new("catch22", "12345")
book_collection1 = BookCollection.new
book_collection1.add_to_books(book1.name)
puts book_collection1
end
That is my code and the error I'm getting is "undefined local variable or method `book_names'". I tried adding " attr_accessor :book_names" and when I do that the printed output doesn't make sense.
There are a few mistakes in your code:
line 4 should not end with a comma.
initialize in class BookCollection is misspelled, resulting in #book_names not being initialized. #book_names therefore equals nil when you attempt to add an element to it with push. nil does not have a method push; hence the exception, and the message printed with the exception.
book_name.push(book_names) should be #book_name.push(book_name). (#book_name must be an instance_variable, as opposed to a local variable, to be visible outside a method, within the class definition.
puts book_collection1 prints the class instance; you want to print #book_names.
Here I've fixed your code. I've used << instead of push. Either is OK, but the former seems to be favored my most.
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
puts "name = #{name}, book_id = #{book_id}"
#name = name
#book_id = book_id
end
end
class BookCollection
attr :book_names
def initialize
#book_names = []
end
def add_to_books(book_name)
#book_names << book_name
end
end
book_collection1 = BookCollection.new
book1 = Books.new("Catch22", "12345")
book2 = Books.new("Hawaii", "67890")
book_collection1.add_to_books(book1.name)
book_collection1.add_to_books(book2.name)
book_collection1.book_names # => ["Catch22", "Hawaii"]
Probably just a typo at
book_name.push(book_names)
Should have been
book_names.push(book_name)
With attr_accessor :book_names
I'm studying Ruby and my brain just froze.
In the following code, how would I write the class writer method for 'self.total_people'? I'm trying to 'count' the number of instances of the class 'Person'.
class Person
attr_accessor :name, :age
##nationalities = ['French', 'American', 'Colombian', 'Japanese', 'Russian', 'Peruvian']
##current_people = []
##total_people = 0
def self.nationalities #reader
##nationalities
end
def self.nationalities=(array=[]) #writer
##nationalities = array
end
def self.current_people #reader
##current_people
end
def self.total_people #reader
##total_people
end
def self.total_people #writer
#-----?????
end
def self.create_with_attributes(name, age)
person = self.new(name)
person.age = age
person.name = name
return person
end
def initialize(name="Bob", age=0)
#name = name
#age = age
puts "A new person has been instantiated."
##total_people =+ 1
##current_people << self
end
You can define one by appending the equals sign to the end of the method name:
def self.total_people=(v)
##total_people = v
end
You're putting all instances in ##current_people you could define total_people more accurately:
def self.total_people
##current_people.length
end
And get rid of all the ##total_people related code.
I think this solves your problem:
class Person
class << self
attr_accessor :foobar
end
self.foobar = 'hello'
end
p Person.foobar # hello
Person.foobar = 1
p Person.foobar # 1
Be aware of the gotchas with Ruby's class variables with inheritance - Child classes cannot override the parent's value of the class var. A class instance variable may really be what you want here, and this solution goes in that direction.
One approach that didn't work was the following:
module PersonClassAttributes
attr_writer :nationalities
end
class Person
extend PersonClassAttributes
end
I suspect it's because attr_writer doesn't work with modules for some reason.
I'd like to know if there's some metaprogramming way to approach this. However, have you considered creating an object that contains a list of people?