I'm trying to convert Celsius temperatures to Fahrenheit and vice versa. As in the following, instance variable #temperature is defined in the methods celsius= and fahrenheit= respectively.
class Temperature
def self.ctof(temp)
(temp * 9 / 5.0) + 32
end
def self.ftoc(temp)
(temp - 32) * (5 / 9.0)
end
def initialize(options)
if options[:f]
self.fahrenheit = options[:f]
else
self.celsius = options[:c]
end
end
def fahrenheit=(temp)
#temperature = self.class.ftoc(temp)
end
def celsius=(temp)
#temperature = temp
end
def in_fahrenheit
self.class.ctof(#temperature)
end
def in_celsius
#temperature
end
end
It is confusing to me because I've never seen instance variables defined outside of the initialize method. I'm hoping someone can help me understand what is going on here.
When you call Temperature.new(c: 0), this will set the celsius= accessor, which sets the instance variable #temperature (which is meant to always be in Celsius) to 0.
When you call Temperature.new(f: 32), this will set the fahrenheit= accessor, which sets the instance variable #temperature to Temperature.ftoc(32), or 0.0.
Calling in_celsius simply returns #temperature, or 0 in the example.
Calling in_fahrenheit returns Temperature.ctof(0), or 32.0.
There is nothing magical about an instance variable being defined outside the constructor. The key point is that it is a variable that is available throughout the instance methods.
It is perfectly fine to set instance variables outside of initialize. This is exactly what setters do. You probably already know attr_accessor – when calling attr_accessor :foo, it creates a getter foo and a setter foo= for the instance variable #foo, i.e. two methods equivalent to:
def foo
#foo
end
def foo=(value)
#foo = value
end
In your code, in_celsius and celsius= and just that: getters and setters for #temperature.
But your code does indeed look a bit convoluted. I think this is because Temperature has to handle both, Fahrenheit and Celsius. You can simplify it by providing separate classes for each temperature scale:
class Celsius
attr_reader :value
def initialize(value)
#value = value
end
def to_celsius
self
end
def to_fahrenheit
Fahrenheit.new((value * 9 / 5.0) + 32)
end
end
class Fahrenheit
attr_reader :value
def initialize(value)
#value = value
end
def to_celsius
Celsius.new((value - 32) * (5 / 9.0))
end
def to_fahrenheit
self
end
end
Now each class has a single instance variable #value which is being set within initialize.
temperature = Celsius.new(0)
#=> #<Celsius:0x007fb83d8b33a8 #value=0>
temperature.to_fahrenheit
#=> #<Fahrenheit:0x007fb83d8b3128 #value=32.0>
Related
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.
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
To achieve a DSL like attribute assignment,a dual-purpose accessor was utilized. However, I was seeking a way to refactor the obvious code duplication.
class Layer
def size(size=nil)
return #size unless size
#size = size
end
def type(type=nil)
return #type unless type
#type = type
end
def color(color=nil)
return #color unless color
#color = color
end
end
I was thinking define those method in a class method by using define_method along with other methods to get/set the instance variables. However, the dilemma is how can I access the instance from class method?
def self.createAttrMethods
[:size,:type,:color].each do |attr|
define_method(attr) do |arg=nil|
#either use instance.send() or
#instance_variable_get/set
#But those method are instance method !!
end
end
end
Inside of define_method block, self will be pointing to current instance of class. So use instance_variable_get.
class Foo
def self.createAttrMethods
[:size,:type,:color].each do |attr|
define_method(attr) do |arg = nil|
name = "##{attr}"
return instance_variable_get(name) unless arg
instance_variable_set(name, arg)
end
end
end
createAttrMethods
end
f = Foo.new
f.size # => nil
f.size 3
f.size # => 3
This question already has answers here:
When to use `self.foo` instead of `foo` in Ruby methods
(3 answers)
Closed 9 years ago.
When do you use self.property_name in Ruby?
Use self when calling a class's mutator. For example, this won't work:
class Foo
attr_writer :bar
def do_something
bar = 2
end
end
The problem is that 'bar = 2' creates a local variable named 'bar', rather than calling the method 'bar=' which was created by attr_writer. However, a little self will fix it:
class Foo
attr_writer :bar
def do_something
self.bar = 2
end
end
self.bar = 2 calls the method bar=, as desired.
You may also use self to call a reader with the same name as a local variable:
class Foo
attr_reader :bar
def do_something
bar = 123
puts self.bar
end
end
But it's usually better to avoid giving a local variable the same name as an accessor.
self references the current object. This lends itself to many uses:
calling a method on the current object
class A
def initialize val
#val = val
end
def method1
1 + self.method2()
end
def method2
#val*2
end
end
Here running A.new(1).method1() will return 3. The use of self is optional here - the following code is equivalent:
class A
def initialize val
#val = val
end
def method1
1 + method2()
end
def method2
#val*2
end
end
self is not redundant for this purpose though - operator overloading makes it neccessary:
class A
def initialize val
#val = val
end
def [] x
#val + x
end
def method1 y
[y] #returns an array!
end
def method2 y
self.[y] #executes the [] method
end
end
This shows how self must be used if you want to call the current object's [] method.
referencing attributes
You can generate the methods to read and write to instance variables using attr_accessor and co.
class A
attr_accessor :val
def initialize val
#val = val
end
def increment!
self.val += 1
end
end
Using self is redundant here because you can just reference the variable directly, eg. #val.
Using the previous class, A.new(1).increment! would return 2.
method chaining
You can return self to provide a form of syntactical sugar known as chaining:
class A
attr_reader :val
def initialize val
#val = val
end
def increment!
#val += 1
self
end
end
Here, because we are returning the current object, methods can be chained:
A.new(1).increment!.increment!.increment!.val #returns 4
creating class methods
You can define class methods using self:
class A
def self.double x
x*2
end
def self.quadruple x
self.double(self.double(x))
end
end
This will enable you to call A.double(2) #= 4 and A.quadruple(2) #=8. Note that in a class method, self references that class because the class is the current object.
how the value of self is determined
The current value of self in a particular method is set to the object that that method was called upon. Normally this uses the '.' notation. When you run some_object.some_method(), self is bound to some_object for the duration of some_method, meaning that some_method can use self in one of the ways mentioned above.
Using self is used will reference the current object accessible within a program. Therefore, self.property is used when accessing a variable through a attr_accessor of some sort. In must cases, it can be used in place of #property from within an object.
Right now I have:
module A
class B
def initialize
#y = 'foo'
end
end
end
module A
class C < B
def initialize
#z = 'buzz'
end
end
end
How can I have it so when I instantiate C #y is still set equal to 'foo'? Do I have to repeat that in the initialize under C? I am a following a bad pattern? Should #y be a class variable or just a constant under the module? Any help would be appreciated!
class A::C < B
def initialize( x, y )
super # With no parens or arguments, this passes along whatever arguments
# were passed to this initialize; your initialize signature must
# therefore match that of the parent class
#z = 'buzz'
end
end
Or, as #EnabrenTane pointed out, you can explicitly pass along whatever arguments you know the super class will be expecting.
For more on inheritance, see the section on Inheritance and Messages in the old-but-free online version of the Pickaxe book.
You need the super keyword. It calls your parents definition of the same method.
I added params just in case. Note, to pass params B#initialize will have to take optional params as well.
module A
class C < B
def initialize(params = nil)
super(params) # calls B#initialize passing params
#z = 'buzz'
end
end
end