Ruby Beginner Class Question - ruby

I'm trying to learn ruby more in depth before I move on to rails dev, but I'm having some issues learning classes. I can't seem to understand why the following doesn't work.
#point.rb
class Point
attr_accessor :x, :y
def initialize(p = [0,0])
#x = p[0]
#y = p[1]
end
end
#shape.rb
require_relative 'point.rb'
class Shape
attr_accessor :points
def initialize *the_points
for p in the_points
#points.append Point.new(p)
end
end
end
s = Shape.new([3,2])
puts s.points
When I call the function I get a no method error for NilClass, which I'm assuming is referring to #point.append.

First, try this:
def initialize *the_points
#points = []
for p in the_points
#points << Point.new(p)
end
end
You get NilClass error because #points instance variable is Nil, and NilClass, which does not have append() method.

Better than creating an array and populating it in a loop would be to initialize it like so:
class Shape
attr_accessor :points
def initialize *the_points
#points = the_points.map{ |p| Point.new(p) }
end
end

If you had warnings on (ruby -w or $VERBOSE = true), it'd warn you that #points didn't exist.
See some other debugging tips in How do I debug Ruby scripts?

You need to initialize #points to be a new array. It starts off as nil.
def initialize *the_points
#points = [];
for p in the_points
#points.append Point.new(p)
end
end

Related

Singleton example from "The Ruby Programming Language" results in 'undefined method' or uninitialized constant

Working through "The Ruby Programming Language" and attempting to implement the Singleton example from the end of Chapter 7, section "Object Creation and Initialization"
The chapter works with a "Point" class, which is gradually extended to include class instance variables and Class methods to allow for the recording of "point stats" - the number of points created and their average values.
Able to get this to work fine: working without singleton
When I refactored to have a PointStats Singleton class and a Point class, I find I either get a undefined method x error when PointStats class is first, or a uninitialized constant PointStats error when Point class is first.
Running 1.8 on OSX, though also tried on another machine running 2.0 via rvm - same results from lines of interest.
I must be missing something very basic about how to have multiple classes in Ruby work together.
What am I doing incorrect? As near as I can tell I am following the example from the book exactly, yet each class seems to require that the other be defined first.
refactored code with PointStats class first, as suggested by text:
#!/usr/bin/ruby -w
require 'singleton'
class PointStats
include Singleton
def initialize
#n, #totalX, #totalY = 0, 0.0, 0.0
end
def record(point)
#n += 1
#totalX += point.x
#totalY += point.y
end
def report
puts "#{#n} -- points"
puts "#{#totalX/#n} -- Average x"
puts "#{#totalY/#n} -- Average y"
end
end
class Point
def initialize(x,y)
#x,#y = x,y
PointStats.instance.record(self)
end
ORIGIN = Point.new(0,0)
UNIT_X = Point.new(1,0)
UNIT_Y = Point.new(0,1)
include Enumerable
include Comparable
attr_reader :x, :y
def to_s
"(#{#x},#{#y})"
end
def +(other)
Point.new(#x + other.x, #y + other.y)
rescue
raise TypeError,
"Point like argument expected"
end
def -#
Point.new(-#x, -#y)
end
def *(scalar)
Point.new(#x*scalar, #y*scalar)
end
def coerce(other)
[self, other]
end
def [](index)
case index
when 0, -2: #x
when 1, -1: #y
when :x, "x": #x
when :y, "y": #y
else nil
end
end
def each
yield #x
yield #y
end
def ==(o)
if 0.is_a? Point
#x==o.x && #y==o.y
else
false
end
end
def eql?(o)
if o.instance_of? Point
#x.eql?(o.x) && #y.eql?(o.y)
else
false
end
end
def hash
code = 17
code = 37*code + #x.hash
code = 37*code + #y.hash
code
end
def <=>(other)
return nil unless other.instance_of? Point
#x**2 + #y**2 <=> other.x**2 + other.y**2
end
def self.sum(*points)
x = y = 0
points.each { |p| x+=p.x; y+=p.y }
Point.new(x,y)
end
end
Edit: thanks #cozyconemotel -- went with two public methods, leaving the new to behave in a more expected fashion and adding unrecorded for other uses:
class Point
attr_reader :x, :y
def initialize(x,y)
#x,#y = x,y
PointStats.instance.record(self)
end
def self.unrecorded(x,y)
instance = new(x,y)
end
ORIGIN = Point.unrecorded(0,0)
UNIT_X = Point.unrecorded(1,0)
UNIT_Y = Point.unrecorded(0,1)
# and the rest follows ...
As #guitarman has pointed out, because a call to PointStats.instance.record requires Point#x and Point#y, you have to move attr_reader :x, :y to before initialize to make it work.
class Point
attr_reader :x, :y
def initialize(x,y)
#x,#y = x,y
PointStats.instance.record(self)
end
#(rest of the class def..)
end
I found another problem in your code though. In your definition for Point you are creating 3 instances:
ORIGIN = Point.new(0,0)
UNIT_X = Point.new(1,0)
UNIT_Y = Point.new(0,1)
The problem is that these also trigger calls to PointStats.instance.record, and therefore this happens
> p1 = Point.new(10,10)
=> #<Point:0x007ff7935d6f78 #x=10, #y=10>
> p2 = Point.new(20,20)
=> #<Point:0x007ff7935f4b68 #x=20, #y=20>
> PointStats.instance.report
5 -- points
6.2 -- Average x
6.2 -- Average y
=> nil
One thing you should do is make new private and create a factory method that calls record. Like this:
class Point
attr_reader :x, :y
private_class_method :new
def initialize(x,y)
#x,#y = x,y
end
def self.generate(x,y)
instance = new(x,y)
PointStats.instance.record(instance)
instance
end
ORIGIN = new(0,0)
UNIT_X = new(1,0)
UNIT_Y = new(0,1)
#(here goes the rest..)
end
You don't have to make new private if you simply want to use both new (create new instance without recording) and generate (create new instance that is recorded).
That's because you deliver the new point in the initialize method of the Point class to the record(self) method of the PointStats class where you access point.x before your point defines the getter methods via attr_reader :x, :y.
Move attr_reader :x, :y to the first line within class Point, because your script will be interpreted from top to bottom.

Assigning value to self in Ruby

I have the following class in Ruby:
class TOMatrix < Matrix
def initialize
self = Matrix.build(8, 8){|r, c| 0}
end
But, this gives the error:
Cannot assign to a keyword
Does anyone know how to achive what a I need ?
It seems you need to write a wrapper instead of subclassing the Matrix (subclassing in your case might break Liskov's substitution principle):
require 'matrix'
class TOMatrix
def initialize
#matrix = Matrix.build(8, 8){|r, c| 0}
end
def method_missing(*args, &block)
#matrix.send(*args, &block)
end
def respond_to?(method)
super || #matrix.respond_to?(method)
end
end
m = TOMatrix.new
m.hermitian? #=> true
You cannot change the value of self directly. You probably want to do something like this:
class Matrix # I defined it so that people can test the code right away
def initialize
#rows = []
self
end
def build rows = 0, columns = 0 #Use your own version of build instead
# It should modify Matrix instead of returning a new one
#rows = Array.new(rows, Array.new(columns, 0))
end
end
class TOMatrix < Matrix
def initialize
super.build 8, 8 #Initializes Matrix and calls build on it
end
end

Ruby add initialized object to class array

I am unable to figure out or find any information on how to push the initialized object pointer to an array accessed from a class level variable. Here is an example.
Class Color
##colors = Array.new
def initialize
##colors << red
end
def self.list
##colors.each do |color|
puts color.to_hex
end
end
end
red = Color.new
Thanks guys for your help.
I would do it this way:
class Color
#colors = []
def self.new(*args, &blk)
#colors << super
end
def self.list
puts #colors.map(&:to_hex)
end
end
red = Color.new
Color.list
Personally, I feel uncomfortable doing class-level stuff in the instance initializer, it just doesn't feel right. The class is a completely independent object, having the instance know too much about the class smells of bad OO.
You can use self to reference the current instance of the class:
class Color
##colors = Array.new
def initialize
##colors << self
end
def self.list
##colors.each do |color|
puts color.to_hex
end
end
end
You should prefer class instance variables over class variables. Class variables are like globals - if you change it in a subclass it will also change the variable in the superclass. This is rarely the wanted effect. Here's #JKillian's code rewritten with class instance variables:
class Color
class << self
attr_accessor :colors
end
#colors = Array.new
def initialize
Color.colors << self
end
def self.list
#colors.each do |color|
puts color.to_hex
end
end
end

Ruby passing hash to class variable

I'm not understanding how to pass a hash to a class then access it from a class method. When the hash is displayed it is nil. If I try to iterate through it using .each I get a 'nil:nilClass' error. What am I missing here?
Is this not possible or am I approaching it wrong?
#bin file
#my_hash = YAML.load_file(#filename)
#tester = TestClass.new(#my_hash)
#tester.show
#lib file
class TestClass
attr_accessor :my_hash
def initialize(my_hash={})
#my_hash
end
def show
puts #my_hash.inspect
end
end
You forgot to assign the instance variable in the initializer.
def initialize(my_hash={})
#my_hash = my_hash
end
You wrote
def initialize(my_hash={})
#my_hash
end
In your code, the value of #my_hash is set to nil. In fact, your code is equivalent to
def initialize(my_hash={})
#my_hash = nil
end
In your initialize method, you aren't actually assigning my_hash to the instance variable. Try this:
def initialize(my_hash={})
#my_hash = my_hash
end

Why are my instance variables not existing across methods of the same class in ruby?

I am doing the ruby koans and I am on the DiceSet project. I've made the DiceSet class but my instance variables don't seem to persist with the instance like I thought they would. My code is
class DiceSet
attr_reader :values
#values = []
puts #values.class
def roll(number_of_rolls)
(1..number_of_rolls).each do |roll|
puts #values.class
#values << (1..6).to_a.sample
end
return #values
end
end
The koan then uses my DiceSet class with
dice = DiceSet.new
dice.roll(5)
puts dice.values.class
assert dice.values.is?(Array)
I put the puts commands in there to follow whats happening with the #values instance variable and only the first puts #values.class says its an Array class. All the others are returning NilClass. Am I using instance variables incorrectly or is there something else I am missing? Do instance variables get deallocated after a method call?
EDIT: My class works correctly now that I have put #values = [] in the roll method as suggested below. My only question now, is why the roll method thinks that #values is a NilClass instead of an array when I put #values = [] in an initialize method
In Ruby everything are objects. The Ruby interpreter assumes that all instance variables belong to the current object self. This is also true in a class definition. The role of self belongs to the class itself, so the instance variable #values belongs to the class. Don’t get confused! Instance variables of the class are different from instance
variables of that class’s objects. Also you don't need specify return keyword explicitly Try this:
class DiceSet
attr_accessor :values
def roll(number_of_rolls)
#values = []
(1..number_of_rolls).each do |roll|
#values << (1..6).to_a.sample
end
#values
end
end
dice = DiceSet.new
dice.roll(5)
puts dice.values.class
assert dice.values.is_a?(Array)
Each DiceSet instance has its own #values, and furthermore, the class DiceSet also has its own #values. They are all different from one another. If you want the instances and the class to share the same variable, you should use a class variable ##values.
Just put the declaration of #values = [] in the initialized method and your code should work as expected.
class DiceSet
attr_reader :values
def initialize()
#values = []
end
def roll(number_of_rolls)
(1..number_of_rolls).each do |roll|
puts #values.class
#values << (1..6).to_a.sample
end
return #values
end
end
Try this:
class Cat
attr_accessor :age
def initialize
#age = 12
end
#age = 6
def meow
puts "I'm #{#age}"
end
def self.meow
puts "I'm #{#age}, going on #{#age+1}"
end
end
Cat.age = 4 # => NoMethodError: undefined method `age=' for Cat:Class
p Cat.age # =? NoMethodError: undefined method `age' for Cat:Class
Cat.meow # => I'm 6, going on 7
cat = Cat.new
p cat.age # => 12
cat.meow # => I'm 12
cat.age = 20 # => 20
cat.meow # => I'm 20
Were I to add
class << self
attr_accessor :age
end
the first three lines of output would become:
Cat.age = 4 # => 4
p Cat.age # => 4
Cat.meow # => I'm 4, going on 5

Resources