#try_convert was next on my method write-up list, but I came to realize that I needed to better understand #to_a and #to_ary first. It took me more than a few Stack Overflow answers and blog posts to understand a bit better, so I’m especially excited to get this out there.
to_a
This is an explicit conversion wherein two objects can presumably be converted between each other. I’ve used either #to_s or #to_i to go between strings and integers, but Arrays have their own function that attempts to describe one object as an Array.
Hash described as an Array
>>{snow: "ball", ball: "snow"}.to_a => [[:snow, "ball"], [:ball, "snow"]]
Converting Structs (not objects)
>>Snowball = Struct.new(:status, :color) => Snowball >>puddle = Snowball.new("Melted", "Yellow") => #<struct Snowball status="Melted", color="Yellow"> >>puddle.to_a => ["Melted", "Yellow"]
This is the best way that #to_a knows how to describe a Struct as an array: it simply returns its attributes. If this were an actual object, it would return an “undefined method”, though.
Instead of using #to_a explicitly, we can use the splat operator (*) in front of the object, which I’ll use on the puddle variable below. It is simply another way to perform the explicit array conversion done by #to_a.
>>puddle_details = *puddle => ["Melted", "Yellow"] >> puddle_details => ["Melted", "Yellow"]
Again, this is for explicit converting, so it’s used when Ruby is expecting an actual Array object. It will attempt to convert the object and will break if it cannot be converted.
#to_ary
This method is for implicit conversion, which I think of as converting to something passable as an array for a situation. It won’t convert the actual object, though. This method is a bit like a lie detector: if something can’t act like an array, things aren’t going to go well.
When given Hashes
>>{snow: "ball", ball: "snow"}.to_ary NoMethodError: undefined method `to_ary' for {:snow=>"ball", :ball=>"snow"}:Hash
This fails because #to_ary does not convert the Array first and a hash doesn’t quack like a duck enough to be treated like one.
Converting Objects or Structs
>>Snowball = Struct.new(:status, :color) => Snowball >>puddle = Snowball.new("Melted", "Yellow") => #<struct Snowball status="Melted", color="Yellow"> >>puddle.to_ary NoMethodError: undefined method `to_ary' for #<struct Snowball status="Melted", color="Yellow">
Calling #to_ary straight out gives us an error. #to_a at least tries to describe what it is given as an Array, but #to_ary throws an error when it feels you’re lying about this being able to be treated as an Array.
Let’s use parallel assignment (which uses #to_ary to implement it) to side step Ruby’s help and give ourselves a headache.
>>condition, pigment = puddle => #<struct Snowball status="Melted", color="Yellow"> >>condition => #<struct Snowball status="Melted", color="Yellow"> >> pigment => nil
We aren’t looking to get an array right now, we just want the object to be treated as one for purposes of this variable assignment. Since to_ary did not actually convert the object to an array when it did the variable assignment, there were not two “things” to assign to the variables. The “condition” variable is first in line to accept all there is to give while “pigment” is given the boot nil. Inevitably, we’re going to have problems that will make us wish we had simply done #to_ary.
Bringing together to_a and to_ary
Let’s go back to that last example for both of them to join forces to complete a parallel assignment on the Snowball struct.
>>puddle = Snowball.new("Melted", "Yellow") => #<struct Snowball status="Melted", color="Yellow">
We know #to_a has the power to actually convert objects to arrays and #to_ary to just returning the object itself pretending to be an array.
>>puddle_details = puddle.to_a => ["Melted", "Yellow"]
We also know that #to_ary is behind the implicit conversion that makes parallel assignment possible. When the two are combined, that assignment goes a bit better.
>>condition, pigment = puddle.to_a => ["Melted", "Yellow"] >>condition => "Melted" >> pigment => "Yellow"
One final and fairly important note on these two before moving on: #to_ary uses #to_a . This means that if a class has not already defined #to_ary or it is not a sub-class of Array, then #to_ary will look to #to_a for answers. If #to_a doesn’t have any, then we get a nil, which is actually what is happening
But Why?
My best understand is that this gives you a nice way to manage the converting of an object to an array without overwriting the method doing the real conversions. Let’s make a snowball class with our own #to_ary method.
class Snowball attr_accessor :status, :color def initialize(status, color) @status = status @color = color end def to_ary ["Snowybally", @status, @color] end end
Now when we call #to_ary or do parallel assignment, it will return what we’ve set up. We’re confident it will be able to be treated like an array because it actually is one.
>>snowy = Snowball.new("Melted", "Yellow") >> snowy.to_ary => ["Snowbally", "Melted", "Yellow"] >> name, condition, pigment = snowy => #<Snowball:0x007ffec1e5cd70 @status="Melted", @color="Yellow"> >> name => "Snowbally" >> condition => "Melted" >> pigment => "Yellow"
Whether we put random strings in the array we set up or attributes, all returns are as we expected. If our #to_ary method returned a Hash or String instead then it would have broken our attempts to use parallel assignment.
Whew, that was a doozy.
Today’s lorem ipsum is brought to us by the late Shel Silverstein and his poem, “SNOWBALL”
I made myself a snowball
As perfect as could be.
I thought I’d keep it as a pet
And let it sleep with me.
I made it some pajamas
And a pillow for its head.
Then last night it ran away,
But first—it wet the bed.