Why isn't the puts method calling my .to_s method? - ruby

I thought that defining a to_s method for a custom class meant that calling the puts method on that class would return an output as specified by to_s. In this program, however, I only get the result I crave if I write puts bingo_board.to_s. What is going on?
class BingoBoard < Array
##letters = %w[B I N G O]
def initialize
# populates an 5x5 array with numbers 1-100
# to make this accessible across your methods within this class, I made
# this an instance variable. # = instance variable
#bingo_board = Array.new(5) {Array.new(5)}
#bingo_board.each_with_index do |column, i|
rangemin = 15 * i + 1
#bingo_board[i] = (rangemin..(rangemin+14)).to_a.sample(5)
end
#bingo_board[2][2] = "X" # the 'free space' in the middle
#game_over = false
end
def game_over?
#game_over
end
def generate_call
....
end
def compare_call(call)
#bingo_board[##letters.index(call[0])].include? call[1]
end
def react_to_call(call)
...
end
def check_board
...
end
def show_column(num)
...
end
def to_s
result = ""
0.upto(4) do |val|
result += " " + ##letters[val] + " "
end
result += "\n\n"
0.upto(4) do |row|
0.upto(4) do |col|
val = #bingo_board[col][row]
result += " " if val.to_i < 10
result += val.to_s + " "
end
result += "\n"
end
result
end
end
my_board = BingoBoard.new
counter = 0
until my_board.game_over?
puts my_board.to_s # renders the board in accordance with my to_s method
call = my_board.generate_call
counter += 1
puts "\nThe call \# #{counter} is #{call[0]} #{call[1]}"
my_board.react_to_call(call)
gets.chomp
end
puts my_board # renders bubkes (i.e., nothing)
puts "\n\n"
puts "Game over"

Its because you'r extending from Array. That's why you're getting the wierd behavior. I don't see where you need the extending from so just remove that and things will work as you expect.
Here's a more detaled answer if you'd like to know why this is happening. Basically puts makes an exception for arrays so when an array is passed puts is called on each member. Ruby Array#puts not using overridden implementation?

As #jörgwmittag said, this is a special case. The IO#puts method treats arrays - which means anything that responds to to_ary - differently. It calls to_ary first and then iterates over each element of the resulting array, and only calls to_s on them. It never calls to_s on the array itself.
If you delegate to a member array instead of subclassing from Array, you have finer-grained control over what gets "inherited" (delegated). Then you can exclude to_ary from the delegation, which will prevent puts from seeing your object as an Array and triggering this behavior.
Other general solutions:
Use string interpolation or explicit to_s calls so that what puts receives is already a string:
puts "#{bingo_board}"
puts bingo_board.to_s
Use print or printf instead of puts:
print bingo_board,"\n"
printf "%s\n",bingo_board

If the object is an Array or can be converted to one (i.e. it implements to_ary), then puts will not call to_s on the object, but rather iterate over the object and print each object within by calling to_s on it.
See:
puts [1, 2]
# 1
# 2
[1, 2].to_s
# => '[1, 2]'
This is actually documented, although somewhat implicitly:
If called with an array argument, writes each element on a new line.

Looks like it runs Array#inspect method instead of your custom to_s. Adding alias_method :inspect, :to_s just after ending of the to_s definition will help you.
But it'll work only with p, because puts runs each(&:inspect).

Related

How to get a list of used methods in Ruby?

hey I want that my method logify puts each method with its parameters and return value of my class A. I wrote for example a simple class A with two methods add and sub and the output should look like that:
Output:
Method add(1, 2) called
return value 3
Method sub(1, 2) called
return value -1
I know that I can get each method with self.instance_methods(false) but can someone please help me further?
require_relative "log"
class A
extend Log
def add(a, b)
a + b
end
def sub(a, b)
a - b
end
logify
end
a = A.new
a.add(2,1)
a.sub(2,1)
module Log
def logify
puts self.instance_methods(false)
end
end
You can use Module#prepend and Module#prepended to help with this like so:
module Log
def self.prepended(base)
base.instance_methods(false).each do |m|
define_method(m) do |*args, &block|
puts "Method #{m}(#{args.join(',')}) called"
val = super(*args, &block)
puts "return value #{val}"
val
end
end
end
end
class A
def add(a, b)
a + b
end
def sub(a, b)
a - b
end
end
A.prepend(Log)
What this does is it defines a method in the prepended module with the same name as the original then builds your output and delagets to the original method in the middle (super) to obtain the return value.
Examples
a = A.new
a.add(2,1)
# Method add(2,1) called
# return value 3
#=> 3
a.sub(2,1)
# Method sub(2,1) called
# return value 1
#=> 1
Caveat: this will only show the provided arguments and will not output default arguments in the method signature
The ruby core library includes the class TracePoint, which can be used to trace just about anything - from methods being defined, or invoked, or exceptions being raised, ...
Here is an example usage, which will perform the tracking you desired:
class A
def add(a, b)
a + b
end
def sub(a, b)
a - b
end
end
TracePoint.trace(:call, :return) do |tp|
next unless tp.defined_class == A
case tp.event
when :call
params = tp.parameters.map { |arg| eval(arg[1].to_s, tp.binding) }
puts "Method #{tp.method_id}(#{params.join(', ')}) called"
when :return
puts "return value #{tp.return_value}"
end
end
# The trace has been enabled! Any time one of those events occurs, the block is evaluated.
a = A.new
a.add(2,1)
a.sub(2,1)
Output:
Method add(2, 1) called
return value 3
Method sub(2, 1) called
return value 1
Fetching the params data is, as you can see, a little troublesome. TracePoint has access to the method signature, but you need to make use of the trace's binding to see what values it's actually been called with.

ruby calling one method from another method returns nil instead of a value

I have a ruby class which is shown in its entirety at the end of this question. It has 2 public instance methods. The 1st public instance method is named plus and the 2nd is named multiply.
When I call just Adder.new(3, 4).plus it returns 7 which is the expected value from adding 3 and 4. However, in the multiply instance method, I first call the plus instance method and it constantly returns nil but if I call the plus instance method on its own it returns the correct value as shown below.
Why does plus instance method return nil when called from inside the multiply instance method?
Calling the plus instance method
Adder.new(3, 4).plus
# adding figures
# I am the added value 7.0
calling the multiply instance method
Adder.new(3, 4).multiply
# called from multiply value for plus is nil
The entire class is pasted below:
class Adder
def self.plus( first, second)
new(first, second).multiply
end
def initialize(first, second)
#first = first
#second = second
end
def plus
puts 'adding figures'
#k = add_them(#first, #second)
puts "I am #k = #{#k}"
end
def multiply(third = 10)
##results = #k
#result = plus
##result = add_them(#first, #second)
puts "called from multiply method value for plus is #{#results.inspect}"
end
private
def add_them(first, second)
#added = (first + second).round(4)
puts "I am the added value #{#added}"
end
end
puts "I am #k = #{#k}"
The above returns nil - puts always returns nil. You can use tap to print the value you are returning:
def plus
puts 'adding figures'
(#k = add_them(#first, #second)).tap do
puts "I am #k = #{#k}"
end
end
puts does not return a value;
def plus
puts 'adding figures'
#k = add_them(#first, #second)
puts "I am #k = #{#k}"
end
should be
def plus
add_them #first, #second
end
Because the last method executed in plus is puts, which returns nil.

is it possible to call yield within a block?

I am interested whether this can be done and what the syntax would be. I'm at:
def say_it
puts "before"
yield("something here")
puts "after"
end
say_it do |val|
puts "here is " + val
yield("other things") # ???
end
thinking probably no but maybe if block gets converted to a Proc?
thx in advance
A yield only makes sense from within a method that takes a block.
And yes, they can nest. Note that:
Traversal still happens along the stack; and
Blocks (and yield) are strictly tied to methods.
Example:
def double(x)
yield x * 2
end
def square_after_double(x)
double(x) do |r|
# Yields to the block given to the current method.
# The location of the yield inside another block
# does not change a thing.
yield r * r
end
end
square_after_double(3) do |r|
puts "doubled and squared: " + r.to_s
end

Is there a way to redefine []=+ in ruby

I am trying to write a simple DSL (against Redis) and I would like to define []+= myself
I have
def []=(key,val)
#redis.zadd(#name,val,key)
end
and I would like to define
def []+=(key,val)
#redis.zincrby(#name,val,key)
end
But my understanding is that Ruby provides the "[]+=" operator automaticallygiven []=
Is there a way to over-ride this behavior
Obviously I don't want this because I would not be able to, say, run this in pipeline mode
No, <operator>= can not be redefined in Ruby.
You can try to get really fancy and wrap your return values in classes that delegate to the actual value. This way, they behave like the actual value, but you can play tricks, for instance with +.
Here's a simple example:
require 'delegate'
module Redis
class Set
class Value < SimpleDelegator
def +(val)
Increment.new(self, val)
end
end
class Increment < SimpleDelegator
attr_reader :increment
def initialize(source, increment)
super(source.__getobj__ + increment)
#increment = increment
end
end
def [](key)
Value.new(#redis.not_sure_what(#name, key))
end
def []=(key,val)
if val.is_a?(Increment)
#redis.zincrby(#name,val.increment,key)
else
#redis.zadd(#name,val,key)
end
end
end
end
This is just a starting point. You'll have to be more careful than this, for example by checking the key is the same. In my simplistic example, redis[:foo] = redis[:bar] + 1 would actually be equivalent to redis[:foo] += 1...
Nope. x[y] += z expands to exactly x[y] = x[y] + z:
class << (object = Object.new)
def [](key)
puts "[#{key.inspect}]"
key
end
def []=(key, value)
puts "[#{key.inspect}] = #{value.inspect}"
value
end
end
# These are all equivalent
object['See?'] += " It's impossible."
object['See?'] = object['See?'] + " It's impossible."
object.[]=('See?', object.[]('See?').+(" It's impossible."))
# They all produce the same output:
# ["See?"]
# ["See?"] = "See? It's impossible."
# => "See? It's impossible."
You will have to create a separate method.

Ruby: what method is called?

Assume, I have an object x of MyClass. What method is called, when I do puts x? I need to override it with my own one.
I thought it was .inspect, but somehow overridden inspect isn't being called.
For example, I have a class Sum:
class Sum
initiazlie a, b
#x = a + b
end
end
And I wanna access the result like this:
s = Sum.new(3,4)
puts s #=> 7 How do I do this?
puts 10 + s #=> 17 or even this...?
It calls: to_s
class Sum
def to_s
"foobar"
end
end
puts Sum.new #=> 'foobar'
Or if you want, you can just call inspect from to_s, so that you have a consistent string representation of your object.
class Sum
def to_s
inspect
end
end
First, your Sum class is invalid. The definition should be.
class Sum
def initialize(a, b)
#x = a + b
end
end
By default, the method called to get a human readable representation is inspect. Try this in irb
$ s = Sum.new(3, 4)
# => #<Sum:0x10041e7a8 #x=7>
$ s.inspect
# => "#<Sum:0x10041e7a8 #x=7>"
But in your case, you use the puts method which forces a string conversion. For this reason, the Sum object is first converted to string using the to_s method.
$ s = Sum.new(3, 4)
# => #<Sum:0x10041e7a8 #x=7>
$ puts s
# => #<Sum:0x10041e7a8>
$ puts s.to_s
# => #<Sum:0x10041e7a8>
Also note your last example fall into a third case. Because you sum a Fixnum + an other object, the result is expected to be a Fixnum and the method called is to_s but the one defined in the Fixnum class.
In order to use the one in your Sum class, you need to switch the items in the sum and define the + in your object.
class Sum
def initialize(a, b)
#x = a + b
end
def +(other)
#x + other
end
def to_s
#x.to_s
end
end
s = Sum.new(3, 4)
s + 10
puts s
# => 17

Resources