How to print an object - ruby

How do you change what is printed with puts when an object is referenced?
Consiser the following code:
class MyClass
attr_accessor :var
def initialize(var)
#var = var
end
# ...
end
obj = MyClass.new("content")
puts obj # Prints #<MyClass:0x0000011fce07b4a0> but I want it to print "content"
I imagine that there is an operator that you can overload (or something similar), but I don't know what it's called so I have no clue what to search for to find the answer.

Quote from the documentation of puts:
puts(*objects) → nil
Writes the given objects to the stream, which must be open for writing; returns nil. Writes a newline after each that does not already end with a newline sequence. [...]
Treatment for each object:
String: writes the string.
Neither string nor array: writes object.to_s.
Array: writes each element of the array; arrays may be nested.
That means: The object you pass to puts is not a string, therefore, Ruby will call to_s on that object before outputting the string to IO. Because your object has no to_s method implemented, the default implementation from Object#to_s.
To return a customize output, just add your own to_s method to your class like this:
class MyClass
attr_accessor :var
def initialize(var)
#var = var
end
def to_s
var
end
end

class MyClass
attr_accessor :var
def initialize(var)
#var = var
end
def to_s
#var
end
end
obj = MyClass.new("content")
puts obj # Prints "content"

Related

How to get parent instance value in to child class in ruby?

I'm not able to get the instance values of parent class to the child class, My code is like this.
class TimeLine
attr_accessor :tweets
def initialize(tweets=[])
#tweets = tweets
end
def print
puts tweets.join("\n")
end
end
class AuthenticateTimeLine < TimeLine
def print
authenticate!
super
end
def authenticate!
puts "authenticated!"
end
end
TimeLine.new([1,2,3,4,5])
authenticate_timeline = AuthenticateTimeLine.new
authenticate_timeline.print
When I call super on the child class, I'm getting empty array.
It's because you initialize it with empty array, you don't pass any argument to AuthenticateTimeLine.new, so default [] is taken (compare your TimeLine#initialize method). If you passed your array as argument, it would work:
authenticate_timeline = AuthenticatateTimeLine.new([1,2,3,4,5])
authenticate_timeline.print
# 'Works' now!

How to implement to_str or to_s

I have a class that should be used as a string and will always be a string (even if empty). The object will always have a string representation. The following is an example of my class:
class Something
def initialize
#real_string_value = "hello"
end
def to_s
return #real_string_value
end
def to_str
return #real_string_value
end
end
text = Something.new
puts("#{text}") #=> #<Something:0x83e008c>
I ran this test on minitest:
assert_equal(
"",
"#{#text}",
"Unchanged class should convert to empty by default"
)
The test above fails. Why isn't my class converted to a string?
The code print hello as expected if it's run as a script.
If you run the code in irb or similar interactive shell, it uses inspect instead of to_s method for the following line:
text = Something.new
You need to define inspect:
class Something
...
def inspect
to_s
end
end

Ruby make 2 different constructors with same ammount of parameters [duplicate]

This question already has answers here:
In Ruby is there a way to overload the initialize constructor?
(7 answers)
Closed 9 years ago.
I've got a class just like this:
class Equipment
attr_reader :text
attr_reader :name
attr_reader :array1
attr_reader :number
end
then, I want to make 2 constructors with 3 parameters each:
1º one -> (text,name,array1)
2º one -> (text, name,number)
The first one as an argument has an array and the other one has an integer (1,2...), so I need to define both constructors so when I create an object of this class it makes a difference between array or integer as the 3º argument.
Any ideas?
EDIT.: I thought this:
def initialize(text = "", name = "", array = array.new, number =0)
#text = text
#name = name
#array1 = array
#number = number
end
(initializing all of them) then:
def Equipment.newc_witharray(sometext, somename, somearray)
#text = sometext
#name = somename
#array1 = somearray
end
def Equipment.newc_withint(sometext, somename, somenumber)
#text = text
#name = name
#number = somenumber
end
and finally calling objects like this:
character1 = Equipment.newc_withint("Barbarian", "conan", 3)
shouldn't this work?
You can create as many constructors as you want on the class with whatever name you want. There is one constructor new, which is inherited from Object, and that can be used to write other constructors. What other answers mention as the constructor, namely the instance method initialize is not a constructor. That is the method called by the constructor method new by default.
class Foo
def self.new1 text, name, array1
obj = new
# do something on obj with text, name, array1
obj
end
def self.new2 text, name, number
obj = new
# do something on obj with text, name, number
obj
end
end
Foo.new1(text, name, array1)
Foo.new2(text, name, number)
There are various ways to achieve this.
Hash arguments
You could pass a hash and extract the values you're interested in:
def initialize(options={})
#text = options.fetch(:text) # raises KeyError if :text is missing
#name = options.fetch(:name) # raises KeyError if :name is missing
#array = options.fetch(:array, []) # returns [] if :array is missing
#number = options.fetch(:number, 0) # returns 0 if :number is missing
end
Keyword arguments
In Ruby 2.0 you can use keyword arguments with default values:
def initialize(text: text, name: name, array: [], number: 0)
#text = text
#name = name
#array = array
#number = number
end
Switching on argument type
This makes the method harder to read, but would work, too:
def initialize(text, name, number_or_array)
#text = text
#name = name
#number = 0
#array = []
case number_or_array
when Integer then #number = number_or_array
when Array then #array = number_or_array
else
raise TypeError, "number_or_array must be a number or an array"
end
end
Built into the language, no, Ruby does not give you that ability.
However, if you want that ability, I would create an initialize method which takes a hash as its parameter. Then you could create an instance of the class using any number of parameters.
E.g:
class Equipment
attr_reader :text, :name, :array1, :number
def initialize(options)
[:text, :name, :array1, :number].each do |sym|
self.send(sym) = options[sum]
end
end
end
The ruby interpreter wouldn't be able to differentiate between the constructors, as the types are not known until runtime :(
However, you can use a very nice workaround:
class Foobar
def initialize(h) # <-- h is a hash
# pass combination of params into the hash, do what you like with them
end
end
and then, using this pattern, you can pass any combination of params into the constructor:
foobar = Foobar.new(:foo => '5', :bar => 10, :baz => 'what?')

Boolean methods in Ruby?

In order to ask something like:
MyClass::create().empty?
How would I set up empty within MyClass?
Empty (true/false) depends on whether a class variable #arr is empty or not.
The question mark is actually part of the method name, so you would do this:
class MyClass
def empty?
#arr.empty? # Implicitly returned.
end
end
Exactly the same as I showed in the last post, but with a different method name.
First, create must return something with an empty? method. For example:
class MyClass
def self.create
[]
end
end
If you want to be operating on instances of MyClass as per your last question:
class MyClass
def self.create
MyClass.new
end
def initialize
#arr = []
end
def empty?
#arr.empty?
end
def add x
#arr << x
self
end
end
Here MyClass acts as a simple wrapper around an array, providing an add method.
pry(main)> MyClass.create.empty?
=> true
You might also need to check whether #arr is nil or not. This depends on your class definition of empty.
def empty?
!#arr || #arr.empty?
end
You could use Forwardable to delegate empty? from your class to the array:
require "forwardable"
class MyClass
extend Forwardable
def_delegators :#arr, :empty?
def initialize(arr)
#arr = arr
end
end
my_object = MyClass.new([])
my_object.empty? # => true

Creating an Expando object in Ruby

Is there a better way to write this Expando class? The way it is written does not work.
I'm using Ruby 1.8.7
starting code quoted from https://gist.github.com/300462/3fdf51800768f2c7089a53726384350c890bc7c3
class Expando
def method_missing(method_id, *arguments)
if match = method_id.id2name.match(/(\w*)(\s*)(=)(\s*)(\.*)/)
puts match[1].to_sym # think this was supposed to be commented
self.class.class_eval{ attr_accessor match[1].to_sym }
instance_variable_set("#{match[1]}", match[5])
else
super.method_missing(method_id, *arguments)
end
end
end
person = Expando.new
person.name = "Michael"
person.surname = "Erasmus"
person.age = 29
The easiest way to write it is to not write it at all! :) See the OpenStruct class, included in the standard library:
require 'ostruct'
record = OpenStruct.new
record.name = "John Smith"
record.age = 70
record.pension = 300
If I was going to write it, though, I'd do it like this:
# Access properties via methods or Hash notation
class Expando
def initialize
#properties = {}
end
def method_missing( name, *args )
name = name.to_s
if name[-1] == ?=
#properties[name[0..-2]] = args.first
else
#properties[name]
end
end
def []( key )
#properties[key]
end
def []=( key,val )
#properties[key] = val
end
end
person = Expando.new
person.name = "Michael"
person['surname'] = "Erasmus"
puts "#{person['name']} #{person.surname}"
#=> Michael Erasmus
If you're just trying to get a working Expando for use, use OpenStruct instead. But if you're doing this for educational value, let's fix the bugs.
The arguments to method_missing
When you call person.name = "Michael" this is translated into a call to person.method_missing(:name=, "Michael"), so you don't need to pull the parameter out with a regular expression. The value you're assigning is a separate parameter. Hence,
if method_id.to_s[-1,1] == "=" #the last character, as a string
name=method_id.to_s[0...-1] #everything except the last character
#as a string
#We'll come back to that class_eval line in a minute
#We'll come back to the instance_variable_set line in a minute as well.
else
super.method_missing(method_id, *arguments)
end
instance_variable_set
Instance variable names all start with the # character. It's not just syntactic sugar, it's actually part of the name. So you need to use the following line to set the instance variable:
instance_variable_set("##{name}", arguments[0])
(Notice also how we pulled the value we're assigning out of the arguments array)
class_eval
self.class refers to the Expando class as a whole. If you define an attr_accessor on it, then every expando will have an accessor for that attribute. I don't think that's what you want.
Rather, you need to do it inside a class << self block (this is the singleton class or eigenclass of self). This operates inside the eigenclass for self.
So we would execute
class << self; attr_accessor name.to_sym ; end
However, the variable name isn't actually accessible inside there, so we're going to need to single out the singleton class first, then run class_eval. A common way to do this is to out this with its own method eigenclass So we define
def eigenclass
class << self; self; end
end
and then call self.eigenclass.class_eval { attr_accessor name.to_sym } instead)
The solution
Combine all this, and the final solution works out to
class Expando
def eigenclass
class << self; self; end
end
def method_missing(method_id, *arguments)
if method_id.to_s[-1,1] == "="
name=method_id.to_s[0...-1]
eigenclass.class_eval{ attr_accessor name.to_sym }
instance_variable_set("##{name}", arguments[0])
else
super.method_missing(method_id, *arguments)
end
end
end

Resources