Simple 'or' isn't working as expected
Question
I'm running into an interesting issue with the relatively simple assignment below. Each of the parenthesized chunks in the beginning evaluate to nil, leaving Rubygame::Surface.new
as the value that @image
ought to be assigned to. Unfortunately on the next line where I set @rect
, it throws a NoMethodError because @image
is nil.
@image = (image unless image.nil?) or
(Rubygame::Surface.autoload(image_file) unless image_file.nil?) or
(Rubygame::Surface.autoload("#{@name}.png") unless @name.nil?) or
Rubygame::Surface.new([16, 16])
@rect = Rubygame::Rect.new [0, 0], [@image.width, @image.height]
Similar tests run through IRB work as expected, so I'm pretty sure the 'or' statement is well-formed, but I can't figure out why it isn't returning the new Surface when everything else is nil.
Solution
The or
and and
keywords in Ruby have very, very low precedence. Even lower than the assignment operator =
. So simply replace them with ||
and &&
respectively (both binding tighter than =
), and it should work as you expect. Ruby's operator precedence is listed here.
In addition to that, I would say your code is very dense. Consider refactoring it to something like the following, which I think conveys the intent of your code much better.
@image = case
when image then image
when image_file then Rubygame::Surface.autoload(image_file)
when @name then Rubygame::Surface.autoload("#{@name}.png")
else Rubygame::Surface.new([16, 16])
end
@rect = Rubygame::Rect.new [0, 0], [@image.width, @image.height]
OTHER TIPS
Have you tried further levels of parentheses?
@image = ((image unless image.nil?) or
(Rubygame::Surface.autoload(image_file) unless image_file.nil?) or
(Rubygame::Surface.autoload("#{@name}.png") unless @name.nil?) or
Rubygame::Surface.new([16, 16]))
Why are you using RubyGame? The Gosu game development framework for Ruby is faster and more popular.