Can't call puts with subclass of Ruby Hash - ruby

I have built a subclass of Hash, and whenever I call puts with an instance of my class as an argument, I get the following:
> puts data
TypeError: can't convert Reporting::Search::Data to Array (Reporting::Search::Data#to_ary gives Reporting::Search::Data)
Does anyone have any idea what to do? I'd like it to output a string representation of a Hash, just like calling puts with an ordinary Hash would do.
My class is really simple:
class Data < HashWithIndifferentAccess
def method_missing meth, *args, &block
if meth.to_s =~ /=$/
send :[]=, meth.slice(0...-1), *args
elsif args.empty?
fetch meth, Data.new
else
super meth, *args, &block
end
end
def compact!
delete_if do |k,v|
v.compact! if v.is_a?(Data)
v.blank?
end
end
end

Looks like the issue is to do with your use of method_missing.
When you call puts, it tries to output the object in a human-readable form, so it calls to_ary on the object. However, you haven't defined to_ary on your class, so it falls back to method_missing where it gets confused and fails.
Define, to_ary as a stub, and I'm not getting the error anymore.
def to_ary
end
This is one of the many pitfalls of metaprogramming in Ruby!

You have two options, either write a to_s method. Or you could see what the .inspect instance method gives you, though it may not be very useful!
Have you tried adding a to_s method like this?
def to_s
super.to_s
end
Also, it may not be the only reason you're having issues, but you've named your subclass Data which is already a class in Ruby.

You are violating Ruby's type conversion protocols in various ways.
First off, your object responds to to_int, to_float, to_str, to_ary etc. without actually being an integer, a float, a string or an array. The three letter (ahem f-l-o-a-t ahem) versions of those type conversion methods are supposed to be used only in the case where the object in question really absolutely positively is an integer, float, string or array and is just not represented as an instance of Integer, Float, String or Array.
However, your object isn't an Array, it's a map. So, it should only respond to to_hash, if at all.
And secondly, the return value of to_int, to_float, to_str, to_ary etc. should be an instance of the Integer, Float, String or Array class.
So, you are implementing methods that you shouldn't even be implementing in the first place and you are returning the wrong value from them. Doing that for something as deeply ingrained in the core of Ruby as its type conversion protocols is bound to wreak all sorts of havoc of which you are only seeing the beginning.

Related

In Ruby, how do you write a simple method that can be used with &:symbol?

This article touches on the issues but doesn't give a solution.
This started when I wanted to write a method and optionally pass it an argument which could be null or a ???? (proc, lambda, method, block, ???). Lets call it, for now, a block because a block works. The block takes one required argument. An example of the method and a call to it would be:
#!/usr/bin/env ruby
def foo(&proc)
puts "before"
if proc
yield "passed to proc"
end
puts "after"
end
def add_message(s)
puts "from add_message #{s}"
end
foo { |s| add_message(s) }
foo
And the output is:
before
from add_message passed to proc
after
before
after
Great. But, what I'd like to do is be able to call foo like this: foo(&:add_message). But I can't. Changing line 15 above I get:
before
./temp.rb:11:in `add_message': wrong number of arguments (given 0, expected 1) (ArgumentError)
from ./temp.rb:6:in `foo'
from ./temp.rb:15:in `<main>'
And, as the article above mentions, the arity is now -2. So, how do I write a simple method like add_message that I can use with &:add_message. OR!!! as is the case 99.99% of the time, please set me on the proper track on how to do this.
The problem is that Symbol#to_proc does not create a proc that calls add_message method correctly.
# `yield` will pass its arguments to proc
>> :add_message.to_proc.call('passed to proc')
# => ArgumentError
This calls 'passed to proc'.add_message, because our method is defined in Object it works when called on String, however it is missing the required argument.
The solution is to make a proc that can accept the same arguments as add_message method and pass them along to that method. We can use Object#method that returns Method object that implements its own to_proc and has the same arity as the method.
>> method(:add_message).to_proc.arity
=> 1
>> method(:add_message).to_proc.call('passed to proc')
from add_message passed to proc
>> foo(&method(:add_message))
before
from add_message passed to proc
after
From the Ruby docs
Conversion of other objects to procs
Any object that implements the to_proc method can be converted into a proc by the & operator, and therefore can be consumed by iterators.
class Greeter
def initialize(greeting)
#greeting = greeting
end
def to_proc
proc {|name| "#{#greeting}, #{name}!" }
end
end
hi = Greeter.new("Hi")
hey = Greeter.new("Hey")
["Bob", "Jane"].map(&hi) #=> ["Hi, Bob!", "Hi, Jane!"]
["Bob", "Jane"].map(&hey) #=> ["Hey, Bob!", "Hey, Jane!"]
Of the Ruby core classes, this method is implemented by Symbol, Method, and Hash.
So when you pass an argument with a unary ampersand before it, to_proc gets called on it. The &: "syntax" is actually & being called on a symbol literal, i.e. &(:foobar), and Symbol.to_proc has the behavior of converting a symbol into a method call on its first argument, i.e. these two are roughly equivalent (modulo named argument forwarding)
:foobar.to_proc
proc { |x, *args| x.foobar(*args) }
Ruby's Method type also implements to_proc, so if you have a standalone method called foobar (on a module, say, Example), then you can call Example.method(:foobar) and get an &-compatible object. If you have a "top-level" method, then it's probably being defined on the main object and calling method with no explicit receiver will work.
The other type mentioned in that quote is hashes, which can be turned into a function mapping their keys to their values (and returning nil if no matching key exists). And, of course, you can always implement a method called to_proc on your own classes and it'll work just as well as any built-in type.
class Integer
def set
return self + 1
end
end
p [1,2,3,4,5,6].map(&:set)
I think when you can use &: syntax that a method have been defined for a class like above

Method named `hash` in main module overrides some object's `hash` method

Given this script
def hash
puts "why?"
end
x = {}
x[[1,2]] = 42
It outputs the following
why?
/tmp/a.rb:6:in `hash': no implicit conversion of nil into Integer (TypeError)
from /tmp/a.rb:6:in `<main>'
It seems that the hash function defned in the script is overriding Array#hash in that case. Since the return value of my hash method is nil and not an Integer, it throws an exception. The following script seems to confirm this
puts [1,2,3].hash
def hash
puts "why?"
end
puts [1,2,3].hash
The output is
-4165381473644269435
why?
/tmp/b.rb:6:in `hash': no implicit conversion of nil into Integer (TypeError)
from /tmp/b.rb:6:in `<main>'
I tried looking into the Ruby source code but could not figure out why this happens. Is this behavior documented?
You're not overriding Array#hash, you're shadowing Kernel#hash by creating Object#hash:
puts method(:hash)
def hash
puts "why?"
end
puts method(:hash)
That prints:
#<Method: Object(Kernel)#hash>
#<Method: Object#hash>
Fix it so we can see more:
def hash
puts "why?"
super
end
x = {}
x[[1,2]] = 42
Now the output is:
why?
why?
And no error. Try it with x[[1,2,3,4,5,6,7]] = 42 and you'll instead see why? printed seven times. Once for each array element, since the array's hash method uses the hashes of its elements. And Integer#hash doesn't exist, it inherits its hash method from Object/Kernel, so yours gets used.
This is due to a kind of hack in Ruby top level. Have you ever wondered how this works?
def foo
end
p self
foo
class Bar
def test
p self
foo
end
end
Bar.new.test # no error
How are two totally different objects (main and a Bar) able to call foo like it's a private method call? The reason is because... it is a private method call.
When you define a method at the top level of your Ruby script, it gets included (via Object) in every object. That's why you can call top-level methods like they are global functions.
But why does this break only hash and not other common methods? def to_s;end won't break to_s, for example. The reason is because hash is recursive: most* class implementations ultimately call down to Object#hash for their implementations. By redefining that base case, you break it globally. For other methods like to_s you won't see a global change because it's way up the inheritance chain and doesn't get invoked.
* the only objects this doesn't break are a few literals that probably have hard-coded hash values e.g. [] {} "" true etc.

In Ruby, how do I define a class constructor method with the same name as the class?

I'm not sure where I have seen this, or if I just think I have seen it, but I would like to be able to call a method that creates an instance of a class with the same name. So, instead of:
# The class is called 'Vector3', for example:
Vector3.new(x,y,z)
I would like to have an eponymous method that instantiates the class, like so:
Vector3(x,y,z) #<- returns instance of Vector3 class
How would you define that in Ruby?
As #Eli mentioned, you can define a method in the kernel:
def Kernel.Vector3(*args)
Vector3.new(*args)
end
Ruby does this for Array, Complex, Float, Hash, Integer, Rational and String where you probably saw it.
The reason it works is that Object includes Kernel, hence all objects in your program (except ones directly inheriting from BasicObject) will have that method.
However, doing so is unidiomatic, unnecessary (you clutter all objects with an additional method), confusing (capitalized identifiers should be constants) and generally looked down upon.
AFAIK you can't. You can do something similar, Vector3[x, y, z].
class Vector3
def initialize(x, y, z)
# ...
end
def self.[](*args)
self.new(*args)
end
end
Note that the Ruby library uses this device as well. There's Hash.new(...) and Hash[...] , but no Hash(...). This parallels how Proc objects are invoked:
greet = Proc.new { |name| puts "Hello, #{name}" }
greet["Amadan"]
EDIT: I stand corrected:
module Kernel
def Vector3(*args)
Vector3.new(*args)
end
end
But, as Eli Sadoff said, it is impractical, violates encapsulation, and Ruby style (functions and methods should be lowercase).
This answers what I understood the question to be from the title. (Somehow I overlooked the example that contradicts that.) I will leave my answer because I think it includes some interesting elements.
class MyClass
def hi
"hi"
end
my_alias = to_s
singleton_class.class_eval { alias_method(my_alias, :new) }
end
MyClass.methods(false)
#=> [:MyClass]
my_instance = MyClass.MyClass
#=> #<MyClass:0x007fadc2092320>
my_instance.hi
#=> "hi"
Note that this works when the alias of new is passed arguments and/or a block.
See Object#singleton_class and Module#class_eval.

Ruby - reference of self in the class method

Went over this code on RubyMonk:
class Item
def initialize(item)
#item = item
end
def show
puts "The item name is: #{self}"
end
def to_s
"#{#item}"
end
end
Item.new("potion").show
The code passes but the use of the self variable is a bit ambiguous to me. You could've easily supplanted to_s with self in the show method and gotten the same results. Can somebody explain the difference between both interpolations and why/how self is used here?
Additionally, without the the method to_s, the code returns a proxy. What is the significance of defining the to_s here?
String interpolation implicitly calls the to_s method on an object. So, when you define the to_s method on Item, you are explicitly telling that object how to represent itself with respect to a string. self is used in this case because there is an implicit call to to_s within the interpolation of the Item object. Defining to_s explicitly tells Item how to render itself within a string.
For some additional details, check out this excellent post on explicit vs. implicit conversion methods.
While it's true that, in the example you provided, you could have just written "The item name is: #{#item}", that's not always the case.
As CDub points out, string interpolation implicitly calls to_s. If an object doesn't define a to_s method, Ruby returns an object reference in its place. In the example you gave us, writing "The item name is: #{#item}" only works because String implements to_s. If it didn't, or if you use Item to hold an object that doesn't implement to_s, you'll end up with the object's reference.
Now for the difference between using self and #item in your interpolation. self refers to the current object. When you interpolate self, you're calling the current object's to_s method. When you interpolate #item, you're calling #item's to_s method. That's not a problem in this simple case, but let's look at something a little bit more complex. Say we have two classes, Item and OtherItem (creative names, I know).
class Item
def initialize(item)
#item = item
end
def show
puts "The item name is: #{self}"
end
def to_s
"I'm a chunky monkey!"
end
end
class OtherItem
def initialize(item)
#otherItem = item
end
def to_s
"#{#otherItem}"
end
end
In this scenario, Item's show method uses self, so if we were to write:
Item.new(OtherItem.new("potion")).show
Ruby would call Item.show, which, in turn, would call self.to_s. Since self in that context is an Item, our output would be:
"The item name is: I'm a chunky monkey!"
If, however, we redefined Item.show like this:
def show
puts "The item name is: #{#item}"
end
And tried calling Item.new(OtherItem.new("potion")).show again, Item.show would call #item.to_s, and fill that in instead, so we'd get:
"The item name is: potion"

Ruby Koans and Strings

I'm trying to understand something from the Ruby Koans. In one lesson, we make two classes as follows:
class CanNotBeTreatedAsString
def to_s
"non-string-like"
end
end
not_like_a_string = CanNotBeTreatedAsString.new
not_like_a_string == "non-string-like"
class CanBeTreatedAsString
def to_s
"string-like"
end
def to_str
to_s
end
end
like_a_string = CanBeTreatedAsString.new
like_a_string.to_str == "string-like"
def acts_like_a_string?(string)
string = string.to_str if string.respond_to?(:to_str)
string.is_a?(String)
end
assert_equal false, acts_like_a_string?(CanNotBeTreatedAsString.new)
assert_equal true, acts_like_a_string?(CanBeTreatedAsString.new)
So the two classes and last two "assert" statements are what I'm not clear about. The two classes are nearly identical, except for the fact that the second class simply has another function to_str that makes a call to to_s. I don't see why the second assert statement is true (and thus the second class can be treated as a string) simply because there's a second function making a call to the first function.
There's no magic here. The second test is checking for the presence of to_str method. This is not defined for CanNotBeTreatedAsString but it is defined for CanBeTreatedAsString.
The function of respond_to? is to test if a method is defined, and in Ruby 2.0 it will further indicate if it can be called. Protected methods, which cannot be called, will no longer count. For now if respond_to?(:method_name) returns true then send(:method_name) with any required arguments would theoretically work.
The second class could use alias :to_str, :to_s to achieve the same result with less code.
The point of this lesson is to illustrate the principle known as 'duck-typing.' Basically, if it looks like a duck and quacks like a duck, then it is a duck. In this case, the only factor that determines if something is a string (or rather acts like a string) is if it responds to the to_str method.
Try running this code in an interactive ruby (irb) session and experimenting with the two classes. You will find that an instance of each class will respond to to_s but only CanBeTreatedAsString will respond to to_str. That means that, as far as Ruby is concerned, CanBeTreatedAsString is as much of a String as anything else that responds to to_str.

Resources