I'm trying to follow a lesson with the ruby class below, I just don't understand how the results of the output statement is "John Smith" from calling the player function followed by new variable?
Is there any simpler way for this? the coder did it in a way that i got confused
Lastly, Can you tell me how to debug Ruby Class or any ruby code on TextMate? I mean debug just like debugging in Visual C++ shows me what first line gets called and excauted and then jumps to the next etc... to see how it works?
class Dungun
attr_accessor :player
def initialize(player_name)
#player = Player.new(player_name)
#rooms = []
end
class Player
attr_accessor :name, :location
def initialize(player_name)
#name = player_name
end
end
class Room
attr_accessor :reference, :name, :description, :connection
def initialize(reference,name,description,connection)
#reference = reference
#name = name
#description = description
#connection = connection
end
end
end
my_dungun = Dungun.new("John Smith")
puts my_dungun.player.name
Execution order
# 1. Called from my_dungun = Dungun.new("John Smith")
Dungun.new("John Smith")
# 2. Inside Dungun it will call the initialize from Dungun class
initialize("John Smith")
# 3. The initialize method, from Dungun class, will have this statement saying
# that instance variable #player will receive the
# result of Player.new("John Smith")
#player = Player.new("John Smith")
# 4. The Player's 'new' method will call the
# inner class Player's initialize method
initialize("John Smith")
# 5. The Player's initialize should assign "Jonh Smith" to #player's name
#name = "John Smith"
# 6. Then head back to where we stopped, and continue to the other
# statement at second line inside Dungun's 'new' method
#rooms = []
And read Mastering the Ruby Debugger for a ruby debug gem and some lessons!
Related
Consider the following class:
class Person
attr_accessor :first_name
def initialize(&block)
instance_eval(&block) if block_given?
end
end
When I create an instance of Person as follows:
person = Person.new do
first_name = "Adam"
end
I expected the following:
puts person.first_name
to output "Adam". Instead, it outputs only a blank line: the first_name attribute has ended up with a value of nil.
When I create a person likes this, though:
person = Person.new do
#first_name = "Adam"
end
The first_name attribute is set to the expected value.
The problem is that I want to use the attr_accessor in the initialization block, and not the attributes directly. Can this be done?
Ruby setters cannot be called without an explicit receiver since local variables take a precedence over method calls.
You don’t need to experiment with such an overcomplicated example, the below won’t work as well:
class Person
attr_accessor :name
def set_name(new_name)
name = new_name
end
end
only this will:
class Person
attr_accessor :name
def set_name(new_name)
# name = new_name does not call `#name=`
self.name = new_name
end
end
For your example, you must explicitly call the method on a receiver:
person = Person.new do
self.first_name = "Adam"
end
If the code is run with warnings enabled (that is ruby -w yourprogram.rb)
it responds with : "warning: assigned but unused variable - first_name", with a line-number pointing to first_name = "Adam". So Ruby interprets first_name as a variable, not as a method. As others have said, use an explicit reciever: self.first_name.
Try this:
person = Person.new do |obj|
obj.first_name = "Adam"
end
puts person.first_name
I want to use the attr_accessor in the initialization block, and not the attributes directly
instance_eval undermines encapsulation. It gives the block access to instance variables and private methods.
Consider passing the person instance into the block instead:
class Person
attr_accessor :first_name
def initialize
yield(self) if block_given?
end
end
Usage:
adam = Person.new do |p|
p.first_name = 'Adam'
end
#=> #<Person:0x00007fb46d093bb0 #first_name="Adam">
class Cat
SUPERSTARS = [Cat.new('John'), Cat.new('Alfred')]
attr_accessor :name
def initialize(name)
#name = name
end
end
I get an error
ArgumentError: wrong number of arguments(1 for 0)
because initialize is not again defined.
If I put the definition on the end:
class Cat
attr_accessor :name
def initialize(name)
#name = name
end
SUPERSTARS = [Cat.new('John'), Cat.new('Alfred')]
end
it works.
Is it possible to keep constant declaration on the top of the file?
I think, you can use this trick:
class Cat
def self.const_missing(name)
[Cat.new('John'), Cat.new('Alfred')] if name == :SUPERSTARS
end
attr_accessor :name
def initialize(name)
#name = name
end
end
Avdi Grimm in his free episode of rubytapas called "Barewords" recommends that you don't use constants directly. Instead, wrap them in a method. And when you have a method, you don't even need the constant (which in ruby is not really constant anyway).
So, you can do something like this:
class Cat
def self.superstars
#superstars ||= [Cat.new('John'), Cat.new('Alfred')]
end
attr_accessor :name
def initialize(name)
#name = name
end
end
The only difference is that you now call it Cat.superstars instead of Cat::SUPERSTARS. Your code now works and looks better too! I call it a win-win. :)
The problem is that class definitions are just code that gets executed when the definition is reached. It's like asking if you can access the value of a local variable that you have not yet defined.
A possible workaround is to delay the evaluation of the constant until the class body is executed. Note that it's not really worth it and it shows a working solution that shouldn't be used in practice:
# BasicObject for minimal amount of methods
class Retarded < BasicObject
def initialize(&value)
#value = value
end
# Make sure we remove as many methods as we can to
# make the proxy more transparent.
(instance_methods - %i(__id__ __send__ __binding__)).each do |name|
undef_method name
end
def method_missing(*args, &block)
# Get the actual value
value = #value.call
# Suppress warnings while we traverse constants
warn_level, $VERBOSE = $VERBOSE, nil
traversed_modules = []
constant_finder = -> (root) do
constant_name = root.constants.find do |constant|
# We undefed ==, so we need a different way to compare.
# Given that this class should be used for constant values in place,
# comparing object ids does the job.
root.const_get(constant).__id__ == __id__
end
if constant_name
# Just set the constant to the actual value
root.const_set(constant_name, value)
else
# Recursively search for the containing module of the constant
root.constants.each do |child_name|
child_constant = root.const_get(child_name)
if child_constant.is_a?(::Module) &&
!traversed_modules.include?(child_constant)
# Handle circular references
traversed_modules.push child_constant
constant_finder.(child_constant)
end
end
end
end
# ::Object is the root module where constants are contained
constant_finder.(::Object)
# Bring back warnings
$VERBOSE = warn_level
# We have already found the constant and set its value to whatever was
# passed in the constructor. However, this method_missing was called
# in an attempt to call a method on the value. We now should make that
# invocation.
value.public_send(*args, &block)
end
end
# I know, the example is mononic.
class EdwardKing
DEAD = Retarded.new { [new('I'), new('II'), new('III')] }
def initialize(number)
#number = number
end
def whoami
"Edward #{#number} of England"
end
end
p EdwardKing::DEAD
# [#<EdwardKing:0x007f90fc26fd10 #number="I">, #<EdwardKing:0x007f90fc26fc70 #number="II">, #<EdwardKing:0x007f90fc26fc20 #number="III">
p EdwardKing::DEAD.map(&:whoami)
# ["Edward I of England", "Edward II of England", "Edward III of England"]
So I have the following setup
class foo
:attr_accessor :textBoxInFoo
#textBoxInFoo
def appendText
//appends text to textBoxInFoo
end
def getLastPartOfText
//gets the last line of text in textBoxInFoo
end
end
class bar
def UseFoo
//Declares instance of textBoxInFoo
end
end
class snafu
def runInBackground
//Needs to read and write from instance of textBoxInFoo in bar class
end
end
What I'm failing to grasp is how to do what I need to do in runInBackground to get it to read and write to textBoxInFoo, I've been reading through various explanations of instance and class methods, and none of them have really clicked with me yet, so I'm wondering if anyone knows what I'm messing up.
This is a small example of how you can create a user object and send it to student object as an argument. run() method calls user's run method.
class User
attr_accessor :name
def initialize(name) # it is similar to constructor
#name = name
end
#run method
def run
puts "I am running"
end
#getter for name
def get_name
#name
end
#setter for name
def set_name=(name)
#name = name
end
end
class Student
attr_accessor :obj
def initialize(obj)
#obj = obj
end
def run
obj.run
puts "inside of Student"
end
end
user = User.new("John")
stud = Student.new(user)
stud.run # shows I am running
# inside of student
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?